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,677 @@
1
+ /**
2
+ * transcript-window.mjs — the transcript row-estimate + virtual-window engine,
3
+ * extracted verbatim from App.jsx. Owns ALL module-level caches for this
4
+ * cluster (variant-key, estimated-rows, measured-rows, sig-part). Pure module:
5
+ * no React, no App closures. The App imports the functions and (for the
6
+ * measured-height cache it writes from a layout effect) the shared cache +
7
+ * variant-key helper by name.
8
+ *
9
+ * Behavior is byte-for-byte the same as when these lived in App.jsx; only the
10
+ * home moved. The env-tunable constants and their comments are preserved.
11
+ */
12
+ import {
13
+ measureMarkdownRenderedRows,
14
+ measureStreamingMarkdownRenderedRows,
15
+ } from '../markdown/measure-rendered-rows.mjs';
16
+ import { streamingLayoutText } from '../markdown/streaming-markdown.mjs';
17
+ import { displayWidth } from '../display-width.mjs';
18
+ import { formatToolSurface, normalizeToolName, parseToolArgs, summarizeAgentSurfaceBrief } from '../../runtime/shared/tool-surface.mjs';
19
+ import { isBackgroundErrorOnlyBody } from '../../runtime/shared/err-text.mjs';
20
+ import { formatExpandedResult, wrapExpandedResultLines } from '../components/tool-output-format.mjs';
21
+ import {
22
+ formatHookDenialDetail,
23
+ isHookApprovalDenialToolItem,
24
+ shouldSuppressFullyFailedToolItem,
25
+ toolItemResultText,
26
+ } from '../transcript-tool-failures.mjs';
27
+
28
+ function positiveIntEnv(name, fallback) {
29
+ const value = Number(process.env[name]);
30
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
31
+ }
32
+
33
+ // Per-keystroke render cost is proportional to the number of MOUNTED transcript
34
+ // items: ink's renderNodeToOutput still serializes (squashTextNodes/wrapText/
35
+ // output.write) every child even when an overflow:hidden viewport clips it
36
+ // off-screen — clipping only trims write coordinates, not the serialization. So
37
+ // the only lever for typing latency on a tall transcript is mounting fewer
38
+ // rows. The window keeps a small ITEM floor (so a few items stay mounted for
39
+ // stable scroll/overscan) but is otherwise driven by the viewport+overscan ROW
40
+ // span, not a large fixed item count. All three are env-tunable for A/B / revert.
41
+ export const TRANSCRIPT_WINDOW_MIN_ITEMS = positiveIntEnv('MIXDOG_TUI_TRANSCRIPT_WINDOW_MIN_ITEMS', 12);
42
+ export const TRANSCRIPT_WINDOW_OVERSCAN_ROWS = positiveIntEnv('MIXDOG_TUI_TRANSCRIPT_OVERSCAN_ROWS', 16);
43
+
44
+ // Hard cap on simultaneously MOUNTED transcript items. Every mounted child is
45
+ // fully serialized by ink each frame (clipping only trims write coords, not the
46
+ // serialize pass), so this cap is the dominant lever for per-frame render cost
47
+ // on a tall transcript. The viewport+overscan ROW span already drives the
48
+ // window; this cap only bounds the worst case (many short rows). 180 mounted
49
+ // rows is far more than any viewport needs and made each frame serialize a long
50
+ // tail of off-screen rows, so lower it to a value that still comfortably covers
51
+ // viewport + overscan on a large terminal. Env-tunable for A/B / revert.
52
+ export const TRANSCRIPT_WINDOW_MAX_ITEMS = positiveIntEnv('MIXDOG_TUI_TRANSCRIPT_WINDOW_ITEMS', 80);
53
+ export const SELECTION_PAINT_INTERVAL_MS = positiveIntEnv('MIXDOG_TUI_SELECTION_PAINT_MS', 24);
54
+ // Frame-coalesce edge-drag auto-scroll + wheel scroll: both paths accumulate
55
+ // deltas into one pending total and flush via a single scrollTranscriptRows
56
+ // call per this interval, instead of firing the (expensive: anchor recompute +
57
+ // selection repaint) scrollTranscriptRows on every mousemove/wheel tick.
58
+ export const SCROLL_COALESCE_MS = positiveIntEnv('MIXDOG_TUI_SCROLL_COALESCE_MS', 16);
59
+ export const PROMPT_HISTORY_LIMIT = 50;
60
+
61
+ // Parse a boolean env var that DEFAULTS ON. Any of 0/false/off/no (case-
62
+ // insensitive, trimmed) disables it; everything else (including unset) leaves it
63
+ // on. Used as the kill switch for the app-level measured-height feature below.
64
+ function boolEnvDefaultTrue(name) {
65
+ const raw = process.env[name];
66
+ if (raw == null) return true;
67
+ const v = String(raw).trim().toLowerCase();
68
+ return !(v === '0' || v === 'false' || v === 'off' || v === 'no');
69
+ }
70
+
71
+ export const TRANSCRIPT_MEASURED_ROWS = boolEnvDefaultTrue('MIXDOG_TUI_TRANSCRIPT_MEASURED');
72
+
73
+ export function selectionRectsEqual(a, b) {
74
+ if (a === b) return true;
75
+ if (!a || !b) return false;
76
+ return a.mode === b.mode
77
+ && a.x1 === b.x1
78
+ && a.y1 === b.y1
79
+ && a.x2 === b.x2
80
+ && a.y2 === b.y2
81
+ && a.clipY1 === b.clipY1
82
+ && a.clipY2 === b.clipY2
83
+ && a.captureText === b.captureText;
84
+ }
85
+
86
+ export function shiftSelectionRectY(rect, deltaY) {
87
+ const dy = Math.round(Number(deltaY) || 0);
88
+ if (!rect || dy === 0) return rect || null;
89
+ return { ...rect, y1: rect.y1 + dy, y2: rect.y2 + dy };
90
+ }
91
+
92
+ // Reading-order compare (row then col): -1 if a<b, 1 if a>b, 0 equal.
93
+ export function comparePoints(a, b) {
94
+ if (a.y !== b.y) return a.y < b.y ? -1 : 1;
95
+ if (a.x !== b.x) return a.x < b.x ? -1 : 1;
96
+ return 0;
97
+ }
98
+
99
+ // Count how many terminal rows ONE logical line (no '\n') occupies once ink
100
+ // word-wraps it. Mirror the greedy word-wrap so the row estimate is never lower
101
+ // than what ink actually renders.
102
+ function wrappedLineRows(line, width) {
103
+ const text = String(line);
104
+ const full = displayWidth(text);
105
+ if (full === 0) return 1;
106
+ if (full <= width) return 1;
107
+ let rows = 1;
108
+ let col = 0;
109
+ for (const token of text.split(/(\s+)/)) {
110
+ if (!token) continue;
111
+ const tw = displayWidth(token);
112
+ if (tw === 0) continue;
113
+ if (tw > width) {
114
+ // Over-long unbreakable token: ink hard-splits it across rows.
115
+ if (col > 0) { rows++; col = 0; }
116
+ rows += Math.ceil(tw / width) - 1;
117
+ col = tw % width || width;
118
+ continue;
119
+ }
120
+ if (col + tw > width) { rows++; col = tw; }
121
+ else { col += tw; }
122
+ }
123
+ return Math.max(1, rows);
124
+ }
125
+
126
+ function estimateWrappedRows(text, columns, reserve = 4) {
127
+ const width = Math.max(8, Number(columns || 80) - reserve);
128
+ const lines = String(text ?? '').split('\n');
129
+ return Math.max(1, lines.reduce((sum, line) => sum + wrappedLineRows(line, width), 0));
130
+ }
131
+
132
+ const SKILL_SURFACE_NAMES = new Set([
133
+ 'skill', 'skill_execute', 'skill_view', 'skills_list', 'use_skill',
134
+ ]);
135
+
136
+ function isAgentResponseResultText(text) {
137
+ const value = String(text || '').trim();
138
+ if (!value) return false;
139
+ if (/^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)(?:\s*·\s*task_id:\s*\S+)?$/i.test(value)) return false;
140
+ if (/^(?:background task\b|agent task:|task_id:)/i.test(value) && !/\n\s*\n[\s\S]*\S/.test(value)) return false;
141
+ return true;
142
+ }
143
+
144
+ const BACKGROUND_TASK_TOOL_NAMES = new Set(['explore', 'search', 'shell', 'bash', 'bash_session', 'shell_command', 'task']);
145
+
146
+ function isBackgroundTaskToolName(normalizedName) {
147
+ return BACKGROUND_TASK_TOOL_NAMES.has(String(normalizedName || '').toLowerCase());
148
+ }
149
+
150
+ function parseBackgroundTaskResultForRows(value) {
151
+ const text = String(value || '').trim();
152
+ if (!text) return null;
153
+ const allLines = text.split('\n');
154
+ const start = allLines.findIndex((line) => line.trim() === 'background task');
155
+ if (start < 0) return null;
156
+ const rest = allLines.slice(start + 1);
157
+ const blank = rest.findIndex((line) => !line.trim());
158
+ const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
159
+ const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
160
+ const fields = {};
161
+ for (const line of headLines) {
162
+ const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
163
+ if (match) fields[match[1].toLowerCase()] = match[2].trim();
164
+ }
165
+ const status = String(fields.status || '').toLowerCase();
166
+ const error = String(fields.error || '').trim();
167
+ const errorOnlyBody = isBackgroundErrorOnlyBody(body, error);
168
+ return {
169
+ status,
170
+ body,
171
+ error,
172
+ errorOnlyBody,
173
+ hasResponse: Boolean(body) && !errorOnlyBody && !/^(running|pending|queued)$/i.test(status),
174
+ };
175
+ }
176
+
177
+ function isBackgroundTaskResponseArgsForRows(normalizedName, args = {}) {
178
+ if (!isBackgroundTaskToolName(normalizedName)) return false;
179
+ const type = String(args?.type || args?.action || '').toLowerCase();
180
+ const status = String(args?.status || '').toLowerCase();
181
+ return type === 'result' || type === 'completion' || (/^(completed|cancelled|canceled)$/i.test(status) && Boolean(args?.task_id));
182
+ }
183
+
184
+ // ToolExecution derives its background-task classification from
185
+ // formatToolSurface(name, args).args — parseToolArgs(args). The row estimate /
186
+ // variant key must read the SAME parsed shape. parseToolArgs already guards
187
+ // malformed input (returns {} / { value } without throwing).
188
+ function backgroundArgsForRows(rawArgs) {
189
+ const parsed = parseToolArgs(rawArgs);
190
+ return parsed && typeof parsed === 'object' ? parsed : {};
191
+ }
192
+
193
+ function toolItemPendingForRows(item) {
194
+ const count = Math.max(1, Number(item?.count || 1));
195
+ const done = Math.max(0, Math.min(count, Number(item?.completedCount || (item?.result == null ? 0 : count))));
196
+ return done < count;
197
+ }
198
+
199
+ const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
200
+
201
+ function stripLeadingStatusMarkerFromTextForRows(text) {
202
+ const lines = String(text || '').split('\n');
203
+ if (lines.length > 0 && LEADING_STATUS_MARKER_LINE_RE.test(String(lines[0] ?? '').trim())) lines.shift();
204
+ return lines.join('\n');
205
+ }
206
+
207
+ function toolDisplayedResultTextForRows(item) {
208
+ const rt = item?.result == null ? '' : String(item.result).replace(/\s+$/, '');
209
+ const bgArgs = backgroundArgsForRows(item?.args);
210
+ const backgroundError = String(bgArgs.error || '');
211
+ const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
212
+ const normalizedName = String(normalizeToolName(item?.name) || '').toLowerCase();
213
+ if (!toolItemPendingForRows(item) && isBackgroundTaskToolName(normalizedName)) {
214
+ const meta = parseBackgroundTaskResultForRows(rt);
215
+ if (meta?.hasResponse && String(meta.body || '').trim()) {
216
+ return stripLeadingStatusMarkerFromTextForRows(String(meta.body));
217
+ }
218
+ }
219
+ return stripLeadingStatusMarkerFromTextForRows(errorOnlyResult ? '' : (rt || ''));
220
+ }
221
+
222
+ function toolHasDisplayResultForRows(item) {
223
+ const rt = item.result == null ? '' : String(item.result).replace(/\s+$/, '');
224
+ const trimmed = String(rt || '').trim();
225
+ if (!trimmed) return false;
226
+ const bgArgs = backgroundArgsForRows(item.args);
227
+ if (isBackgroundErrorOnlyBody(trimmed, bgArgs.error || '')) return false;
228
+ const normalizedName = String(normalizeToolName(item.name) || '').toLowerCase();
229
+ if (isBackgroundTaskToolName(normalizedName)) {
230
+ const meta = parseBackgroundTaskResultForRows(trimmed);
231
+ if (meta) return Boolean(meta.hasResponse && String(meta.body || '').trim());
232
+ }
233
+ return true;
234
+ }
235
+
236
+ function toolExpandedRawTextForRows(item, rawRt) {
237
+ if (item?.aggregate) return rawRt;
238
+ const hasDisplayResult = toolHasDisplayResultForRows(item);
239
+ if (hasDisplayResult) return toolDisplayedResultTextForRows(item);
240
+ return stripLeadingStatusMarkerFromTextForRows(rawRt || '');
241
+ }
242
+
243
+ function toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult) {
244
+ if (hasDisplayResult) return false;
245
+ const bgArgs = backgroundArgsForRows(item.args);
246
+ const error = String(bgArgs.error || '').trim();
247
+ if (!error) return false;
248
+ if (normalizedName === 'agent') {
249
+ const pending = toolItemPendingForRows(item);
250
+ const isError = Boolean(item.isError);
251
+ const agentHeaderFailure = !pending && isError && error && !hasDisplayResult;
252
+ if (!agentHeaderFailure) return false;
253
+ const displayedResultText = toolDisplayedResultTextForRows(item);
254
+ const rt = item.result == null ? '' : String(item.result).replace(/\s+$/, '');
255
+ const isAgentResult = !pending && hasDisplayResult;
256
+ const isAgentResponse = isAgentResult && isAgentResponseResultText(rt);
257
+ const briefRaw = summarizeAgentSurfaceBrief(item.name, bgArgs, displayedResultText, {
258
+ isError,
259
+ isResponse: isAgentResponse,
260
+ });
261
+ const agentSurfaceBriefNonempty = Boolean(String(briefRaw || '').trim());
262
+ return !agentSurfaceBriefNonempty;
263
+ }
264
+ if (!isBackgroundTaskToolName(normalizedName) || !bgArgs.task_id) return false;
265
+ if (isBackgroundTaskResponseArgsForRows(normalizedName, bgArgs)) return false;
266
+ const status = String(bgArgs.status || '').toLowerCase();
267
+ return /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status);
268
+ }
269
+
270
+ function toolArgPathForRows(item) {
271
+ const a = backgroundArgsForRows(item?.args);
272
+ return a?.path ?? a?.file_path ?? a?.file ?? '';
273
+ }
274
+
275
+ function isShellSurfaceForRows(normalizedName, label = '') {
276
+ const n = String(normalizedName || '').toLowerCase();
277
+ const l = String(label || '').toLowerCase();
278
+ return n === 'shell' || n === 'bash' || n === 'bash_session'
279
+ || n === 'shell_command' || n === 'job_wait' || l === 'run';
280
+ }
281
+
282
+ function isShellSurfaceForToolItem(item, normalizedName) {
283
+ const label = formatToolSurface(item?.name, item?.args)?.label || '';
284
+ return isShellSurfaceForRows(normalizedName, label);
285
+ }
286
+
287
+ // EXPANDED tool bodies are post-processed by formatExpandedResult; the row
288
+ // estimate MUST run the SAME pipeline. Pass pathArg/isShell so the count matches.
289
+ function estimateToolRenderedResultRows(value, { pathArg = '', isShell = false, columns = 80 } = {}) {
290
+ const text = String(value ?? '').replace(/\s+$/, '');
291
+ if (!text) return 1;
292
+ try {
293
+ const logical = formatExpandedResult(text, { pathArg, isShell });
294
+ const rows = wrapExpandedResultLines(logical, columns, { isShell }).length;
295
+ return Math.max(1, rows);
296
+ } catch {
297
+ return Math.max(1, text.split('\n').length);
298
+ }
299
+ }
300
+
301
+ function estimateTranscriptItemRows(item, columns, toolOutputExpanded) {
302
+ if (!item) return 1;
303
+ switch (item.kind) {
304
+ case 'user':
305
+ return 1 + estimateWrappedRows(item.text, columns, 4);
306
+ case 'assistant':
307
+ // marginTop={1} (AssistantMessage <Box>) + rendered body height.
308
+ return 1 + (item.streaming
309
+ ? measureStreamingMarkdownRenderedRows(item.text, columns, item.id)
310
+ : measureMarkdownRenderedRows(item.text, columns));
311
+ case 'tool': {
312
+ const TOOL_MARGIN_TOP = 1;
313
+ if (shouldSuppressFullyFailedToolItem(item)) return 0;
314
+ if (isHookApprovalDenialToolItem(item)) {
315
+ const detail = formatHookDenialDetail(toolItemResultText(item));
316
+ return TOOL_MARGIN_TOP + 1 + (detail ? 1 : 0);
317
+ }
318
+ const normalizedName = String(normalizeToolName(item.name) || '').toLowerCase();
319
+ const count = Math.max(1, Number(item.count || 1));
320
+ const done = Math.max(0, Math.min(count, Number(item.completedCount || (item.result == null ? 0 : count))));
321
+ const pending = done < count;
322
+ const isSkillSurface = !item.aggregate && SKILL_SURFACE_NAMES.has(normalizedName);
323
+ const isAgentSurface = normalizedName === 'agent';
324
+ const rt = item.result == null ? null : String(item.result).replace(/\s+$/, '');
325
+ const rawRt = item.rawResult == null ? null : String(item.rawResult).replace(/\s+$/, '');
326
+ const hasResult = item.result != null && Boolean(String(rt || '').trim());
327
+ const hasRawResult = item.rawResult != null && Boolean(String(rawRt || '').trim());
328
+ const expanded = toolOutputExpanded || item.expanded;
329
+ if (!expanded || pending) {
330
+ if (isSkillSurface) return TOOL_MARGIN_TOP + 1;
331
+ const hasDisplayResult = toolHasDisplayResultForRows(item);
332
+ if (toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult)) {
333
+ return TOOL_MARGIN_TOP + 1;
334
+ }
335
+ return TOOL_MARGIN_TOP + 1 + 1;
336
+ }
337
+ if (hasRawResult) {
338
+ const estimateText = toolExpandedRawTextForRows(item, rawRt);
339
+ const rawOpts = item.aggregate
340
+ ? {}
341
+ : { pathArg: toolArgPathForRows(item), isShell: isShellSurfaceForToolItem(item, normalizedName) };
342
+ return TOOL_MARGIN_TOP + 1 + estimateToolRenderedResultRows(estimateText, { ...rawOpts, columns });
343
+ }
344
+ if (isAgentSurface && !hasResult) return TOOL_MARGIN_TOP + 1 + 1;
345
+ if (isSkillSurface && !hasResult) return TOOL_MARGIN_TOP + 1;
346
+ if (item.aggregate) {
347
+ if (hasRawResult) {
348
+ const resultRows = estimateToolRenderedResultRows(rawRt, { columns });
349
+ return TOOL_MARGIN_TOP + 1 + resultRows;
350
+ }
351
+ return TOOL_MARGIN_TOP + 1 + 1;
352
+ } else {
353
+ const backgroundMeta = hasResult && isBackgroundTaskToolName(normalizedName)
354
+ ? parseBackgroundTaskResultForRows(rt)
355
+ : null;
356
+ const isBackgroundResult = hasResult && isBackgroundTaskToolName(normalizedName);
357
+ const isBackgroundResponse = isBackgroundResult
358
+ && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgsForRows(normalizedName, backgroundArgsForRows(item.args)));
359
+ const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
360
+ if (isBackgroundMetadataResult) {
361
+ const hasDisplayResult = toolHasDisplayResultForRows(item);
362
+ if (toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult)) {
363
+ return TOOL_MARGIN_TOP + 1;
364
+ }
365
+ return isSkillSurface ? TOOL_MARGIN_TOP + 1 : TOOL_MARGIN_TOP + 1 + 1;
366
+ }
367
+ const resultText = backgroundMeta?.hasResponse ? backgroundMeta.body : rt;
368
+ const resultRows = estimateToolRenderedResultRows(resultText, {
369
+ pathArg: toolArgPathForRows(item),
370
+ isShell: isShellSurfaceForToolItem(item, normalizedName),
371
+ columns,
372
+ });
373
+ return TOOL_MARGIN_TOP + 1 + resultRows;
374
+ }
375
+ }
376
+ case 'notice':
377
+ return 1 + estimateWrappedRows(item.text, columns, 6);
378
+ case 'turndone':
379
+ case 'statusdone':
380
+ return 2;
381
+ default:
382
+ return 1;
383
+ }
384
+ }
385
+
386
+ export function lowerBound(values, target) {
387
+ let lo = 0;
388
+ let hi = values.length;
389
+ while (lo < hi) {
390
+ const mid = Math.floor((lo + hi) / 2);
391
+ if (values[mid] < target) lo = mid + 1;
392
+ else hi = mid;
393
+ }
394
+ return lo;
395
+ }
396
+
397
+ export function upperBound(values, target) {
398
+ let lo = 0;
399
+ let hi = values.length;
400
+ while (lo < hi) {
401
+ const mid = Math.floor((lo + hi) / 2);
402
+ if (values[mid] <= target) lo = mid + 1;
403
+ else hi = mid;
404
+ }
405
+ return lo;
406
+ }
407
+
408
+ // Resolve the absolute scroll offset that keeps a captured reading anchor at the
409
+ // same screen position for the CURRENT prefix table. Pure so it can run during
410
+ // render AND in the post-commit layout effect.
411
+ export function resolveAnchorScrollOffset({ anchor, items, curPrefix, totalRows, viewRows, maxRows }) {
412
+ if (!anchor || anchor.id == null) return null;
413
+ if (!Array.isArray(curPrefix) || curPrefix.length <= 1) return null;
414
+ const list = Array.isArray(items) ? items : [];
415
+ let idx = -1;
416
+ for (let i = list.length - 1; i >= 0; i--) {
417
+ if (list[i] && list[i].id === anchor.id) { idx = i; break; }
418
+ }
419
+ if (idx < 0 || idx > curPrefix.length - 2) return null;
420
+ const itemHeight = Math.max(0, (curPrefix[idx + 1] || 0) - (curPrefix[idx] || 0));
421
+ const clampedOffset = Math.max(0, Math.min(Number(anchor.offset) || 0, itemHeight));
422
+ const anchorRowCur = (curPrefix[idx] || 0) + clampedOffset;
423
+ return Math.max(0, Math.min(maxRows, totalRows - viewRows - anchorRowCur));
424
+ }
425
+
426
+ // Cheap, stable height fingerprint for a text blob (length + newline count +
427
+ // FNV-1a hash), so a same-length edit that changes wrap/newline shape still
428
+ // invalidates the row/signature caches.
429
+ function fnv1a32(str) {
430
+ let h = 0x811c9dc5;
431
+ for (let i = 0; i < str.length; i++) {
432
+ h ^= str.charCodeAt(i);
433
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
434
+ }
435
+ return h >>> 0;
436
+ }
437
+
438
+ // Two INDEPENDENT 32-bit rolling-hash steps folded into a 64-bit signature.
439
+ // fnvStepA is plain FNV-1a; fnvStepB uses a distinct seed/prime + xorshift
440
+ // finalizer so the two chains are decorrelated (see the App.jsx history note).
441
+ export function fnvStepA(hash, str) {
442
+ let h = hash >>> 0;
443
+ for (let i = 0; i < str.length; i++) {
444
+ h ^= str.charCodeAt(i);
445
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
446
+ }
447
+ return h >>> 0;
448
+ }
449
+
450
+ export function fnvStepB(hash, str) {
451
+ let h = hash >>> 0;
452
+ for (let i = 0; i < str.length; i++) {
453
+ h = Math.imul(h ^ str.charCodeAt(i), 0x85ebca77) >>> 0;
454
+ h = (h ^ (h >>> 13)) >>> 0;
455
+ }
456
+ return h >>> 0;
457
+ }
458
+
459
+ function textShapeFingerprint(value) {
460
+ if (value == null) return 'z';
461
+ const text = String(value);
462
+ const len = text.length;
463
+ if (len === 0) return 'e';
464
+ let newlines = 0;
465
+ for (let i = 0; i < len; i++) {
466
+ if (text.charCodeAt(i) === 10) newlines++;
467
+ }
468
+ return `${len}.${newlines}.${fnv1a32(text).toString(36)}`;
469
+ }
470
+
471
+ // Identity-keyed memo for the variant key. Exported: the App's measured-height
472
+ // layout effect validates its cache writes on the SAME variant key.
473
+ const transcriptVariantKeyCache = new WeakMap();
474
+
475
+ export function transcriptItemVariantKey(item) {
476
+ if (item && typeof item === 'object') {
477
+ const cached = transcriptVariantKeyCache.get(item);
478
+ if (cached !== undefined) return cached;
479
+ const key = computeTranscriptItemVariantKey(item);
480
+ transcriptVariantKeyCache.set(item, key);
481
+ return key;
482
+ }
483
+ return computeTranscriptItemVariantKey(item);
484
+ }
485
+
486
+ function computeTranscriptItemVariantKey(item) {
487
+ const expanded = item.expanded ? 1 : 0;
488
+ if (item.kind === 'tool') {
489
+ const resultShape = textShapeFingerprint(item.result);
490
+ const rawShape = textShapeFingerprint(item.rawResult);
491
+ const count = Number(item.count ?? 0);
492
+ const completed = item.completedCount === undefined ? 'u' : Number(item.completedCount);
493
+ const errors = item.errorCount === undefined ? 'u' : Number(item.errorCount);
494
+ const isError = item.isError ? 1 : 0;
495
+ const normalizedName = String(normalizeToolName(item.name) || '').toLowerCase();
496
+ const aggregate = item.aggregate ? 1 : 0;
497
+ const bgArgs = backgroundArgsForRows(item.args);
498
+ const bgType = String(bgArgs.type || bgArgs.action || '');
499
+ const bgStatus = String(bgArgs.status || '');
500
+ const bgTaskId = bgArgs.task_id ? 1 : 0;
501
+ const bgPrompt = textShapeFingerprint(bgArgs.prompt);
502
+ const bgMessage = textShapeFingerprint(bgArgs.message);
503
+ const bgError = textShapeFingerprint(bgArgs.error);
504
+ return `x${expanded}:n${normalizedName}:g${aggregate}:r${resultShape}:R${rawShape}:c${count}:d${completed}:e${errors}:E${isError}:bt${bgType}:bs${bgStatus}:bk${bgTaskId}:bp${bgPrompt}:bm${bgMessage}:be${bgError}`;
505
+ }
506
+ return `x${expanded}:s${textShapeFingerprint(item.text ?? item.result ?? '')}`;
507
+ }
508
+
509
+ // Per-item ESTIMATED ROW COUNT cache for buildTranscriptRowIndex.
510
+ const transcriptRowsCache = new WeakMap();
511
+
512
+ // App-level MEASURED row heights (real per-item height cache). Exported so the
513
+ // App's per-commit layout effect can write/read/prune it; validated on the same
514
+ // (variantKey + columns + toolExpanded) tuple as the estimate caches.
515
+ export const transcriptMeasuredRowsCache = new WeakMap();
516
+
517
+ export function measuredTranscriptRows(item, columns, toolOutputExpanded) {
518
+ if (!TRANSCRIPT_MEASURED_ROWS || !item) return null;
519
+ if (shouldSuppressFullyFailedToolItem(item)) return 0;
520
+ if (item.kind === 'assistant' && item.streaming) return null;
521
+ const entry = transcriptMeasuredRowsCache.get(item);
522
+ if (!entry) return null;
523
+ if (entry.rows <= 0) return null;
524
+ if (entry.columns !== columns) return null;
525
+ if (entry.toolExpanded !== (toolOutputExpanded ? 1 : 0)) return null;
526
+ if (entry.variantKey !== transcriptItemVariantKey(item)) return null;
527
+ return entry.rows;
528
+ }
529
+
530
+ const STREAMING_ROW_QUANTUM = 1;
531
+
532
+ function assistantTextForStreamingRowEstimate(text) {
533
+ return streamingLayoutText(text);
534
+ }
535
+
536
+ function streamingEstimateRows(item, columns, toolOutputExpanded) {
537
+ const trimmedText = assistantTextForStreamingRowEstimate(item.text);
538
+ const estimateItem = trimmedText === item.text ? item : { ...item, text: trimmedText };
539
+ const raw = Math.max(1, Math.ceil(estimateTranscriptItemRows(estimateItem, columns, toolOutputExpanded)));
540
+ return Math.ceil(raw / STREAMING_ROW_QUANTUM) * STREAMING_ROW_QUANTUM;
541
+ }
542
+
543
+ function estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded) {
544
+ if (!item) return Math.max(1, Math.ceil(estimateTranscriptItemRows(item, columns, toolOutputExpanded)));
545
+ if (shouldSuppressFullyFailedToolItem(item)) return 0;
546
+ if (item.kind === 'assistant' && item.streaming) {
547
+ return streamingEstimateRows(item, columns, toolOutputExpanded);
548
+ }
549
+ const variantKey = transcriptItemVariantKey(item);
550
+ const toolExpanded = toolOutputExpanded ? 1 : 0;
551
+ const cached = transcriptRowsCache.get(item);
552
+ if (cached
553
+ && cached.columns === columns
554
+ && cached.toolExpanded === toolExpanded
555
+ && cached.variantKey === variantKey
556
+ && cached.id === item.id
557
+ && cached.kind === item.kind) {
558
+ return cached.rows;
559
+ }
560
+ const rows = Math.max(1, Math.ceil(estimateTranscriptItemRows(item, columns, toolOutputExpanded)));
561
+ transcriptRowsCache.set(item, { id: item.id, kind: item.kind, variantKey, columns, toolExpanded, rows });
562
+ return rows;
563
+ }
564
+
565
+ export function buildTranscriptRowIndex(items, {
566
+ columns = 80,
567
+ toolOutputExpanded = false,
568
+ suppressMeasuredRowHeights = false,
569
+ } = {}) {
570
+ const allItems = Array.isArray(items) ? items : [];
571
+ const rows = new Array(allItems.length);
572
+ const prefixRows = new Array(allItems.length + 1);
573
+ prefixRows[0] = 0;
574
+ for (let i = 0; i < allItems.length; i++) {
575
+ const item = allItems[i];
576
+ const measured = suppressMeasuredRowHeights
577
+ ? null
578
+ : measuredTranscriptRows(item, columns, toolOutputExpanded);
579
+ const rowCount = measured != null
580
+ ? measured
581
+ : estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded);
582
+ rows[i] = rowCount;
583
+ prefixRows[i + 1] = prefixRows[i] + rowCount;
584
+ }
585
+ return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
586
+ }
587
+
588
+ // Stable signature for the transcript row-index / window memos. Changes only
589
+ // when transcript STRUCTURE changes or the streaming item's estimated height
590
+ // changes — not on every character. Per-item sigParts are identity-memoized.
591
+ const transcriptSigPartCache = new WeakMap();
592
+
593
+ export function transcriptStructureSignature(items, columns, toolOutputExpanded) {
594
+ const list = Array.isArray(items) ? items : [];
595
+ let hA = fnvStepA(0x811c9dc5, `${list.length}|${columns}|${toolOutputExpanded ? 1 : 0}`);
596
+ let hB = fnvStepB(0xcbf29ce4, `${list.length}|${columns}|${toolOutputExpanded ? 1 : 0}`);
597
+ for (let i = 0; i < list.length; i++) {
598
+ const it = list[i];
599
+ let sigPart;
600
+ if (!it) {
601
+ sigPart = '_';
602
+ } else if (it.kind === 'assistant' && it.streaming) {
603
+ sigPart = `a${it.id}:${streamingEstimateRows(it, columns, toolOutputExpanded)}`;
604
+ } else {
605
+ const variantKey = transcriptItemVariantKey(it);
606
+ const cached = transcriptSigPartCache.get(it);
607
+ if (cached
608
+ && cached.variantKey === variantKey
609
+ && cached.columns === columns
610
+ && cached.id === it.id
611
+ && cached.kind === it.kind) {
612
+ sigPart = cached.sigPart;
613
+ } else {
614
+ sigPart = `${it.kind?.[0] || '?'}${it.id}:${variantKey}`;
615
+ transcriptSigPartCache.set(it, { id: it.id, kind: it.kind, variantKey, columns, sigPart });
616
+ }
617
+ }
618
+ hA = fnvStepA(hA, `;${i};`);
619
+ hA = fnvStepA(hA, sigPart);
620
+ hB = fnvStepB(hB, `;${i};`);
621
+ hB = fnvStepB(hB, sigPart);
622
+ }
623
+ return `${hA.toString(36)}.${hB.toString(36)}`;
624
+ }
625
+
626
+ export function transcriptRenderWindow(items, { scrollOffset = 0, viewportHeight = 24, columns = 80, toolOutputExpanded = false, rowIndex = null } = {}) {
627
+ const allItems = Array.isArray(items) ? items : [];
628
+ const itemCount = allItems.length;
629
+ const fallbackIndex = rowIndex?.prefixRows?.length === itemCount + 1
630
+ ? rowIndex
631
+ : buildTranscriptRowIndex(allItems, { columns, toolOutputExpanded });
632
+ const totalRows = Math.max(0, fallbackIndex.totalRows || 0);
633
+ const viewRows = Math.max(1, Number(viewportHeight) || 24);
634
+ const maxScrollRows = Math.max(0, totalRows - viewRows);
635
+ const effectiveScrollOffset = Math.min(
636
+ maxScrollRows,
637
+ Math.max(0, Math.ceil(Number(scrollOffset) || 0)),
638
+ );
639
+
640
+ const bypassRowBudget = viewRows + TRANSCRIPT_WINDOW_OVERSCAN_ROWS * 2;
641
+ if (itemCount <= TRANSCRIPT_WINDOW_MIN_ITEMS || totalRows <= bypassRowBudget) {
642
+ return { startIndex: 0, endIndex: itemCount, items: allItems, bottomSpacerRows: 0, totalRows, maxScrollRows, effectiveScrollOffset };
643
+ }
644
+
645
+ const minItems = Math.min(TRANSCRIPT_WINDOW_MIN_ITEMS, itemCount);
646
+ const maxItems = Math.max(minItems, TRANSCRIPT_WINDOW_MAX_ITEMS);
647
+ const prefixRows = fallbackIndex.prefixRows;
648
+ const visibleTop = Math.max(0, totalRows - effectiveScrollOffset - viewRows);
649
+ const visibleBottom = Math.min(totalRows, totalRows - effectiveScrollOffset);
650
+ const desiredTop = Math.max(0, visibleTop - TRANSCRIPT_WINDOW_OVERSCAN_ROWS);
651
+ const desiredBottom = Math.min(totalRows, visibleBottom + TRANSCRIPT_WINDOW_OVERSCAN_ROWS);
652
+
653
+ let startIndex = Math.max(0, upperBound(prefixRows, desiredTop) - 1);
654
+ let endIndex = Math.min(itemCount, Math.max(startIndex + 1, lowerBound(prefixRows, Math.max(desiredBottom, desiredTop + 1))));
655
+
656
+ while (endIndex - startIndex < minItems && startIndex > 0) startIndex--;
657
+ while (endIndex - startIndex < minItems && endIndex < itemCount) endIndex++;
658
+
659
+ if (endIndex - startIndex > maxItems) {
660
+ const visibleStartIndex = Math.max(0, upperBound(prefixRows, visibleTop) - 1);
661
+ const visibleEndIndex = Math.min(itemCount, Math.max(visibleStartIndex + 1, lowerBound(prefixRows, Math.max(visibleBottom, visibleTop + 1))));
662
+ startIndex = Math.max(0, Math.min(visibleStartIndex, itemCount - maxItems));
663
+ endIndex = Math.min(itemCount, Math.max(visibleEndIndex, startIndex + maxItems));
664
+ if (endIndex - startIndex > maxItems) startIndex = Math.max(0, endIndex - maxItems);
665
+ }
666
+
667
+ const bottomSpacerRows = Math.max(0, totalRows - (prefixRows[endIndex] || totalRows));
668
+ return {
669
+ startIndex,
670
+ endIndex,
671
+ items: allItems.slice(startIndex, endIndex),
672
+ bottomSpacerRows,
673
+ totalRows,
674
+ maxScrollRows,
675
+ effectiveScrollOffset,
676
+ };
677
+ }