mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  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/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,671 @@
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, { columns = 80, toolOutputExpanded = false } = {}) {
566
+ const allItems = Array.isArray(items) ? items : [];
567
+ const rows = new Array(allItems.length);
568
+ const prefixRows = new Array(allItems.length + 1);
569
+ prefixRows[0] = 0;
570
+ for (let i = 0; i < allItems.length; i++) {
571
+ const item = allItems[i];
572
+ const measured = measuredTranscriptRows(item, columns, toolOutputExpanded);
573
+ const rowCount = measured != null
574
+ ? measured
575
+ : estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded);
576
+ rows[i] = rowCount;
577
+ prefixRows[i + 1] = prefixRows[i] + rowCount;
578
+ }
579
+ return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
580
+ }
581
+
582
+ // Stable signature for the transcript row-index / window memos. Changes only
583
+ // when transcript STRUCTURE changes or the streaming item's estimated height
584
+ // changes — not on every character. Per-item sigParts are identity-memoized.
585
+ const transcriptSigPartCache = new WeakMap();
586
+
587
+ export function transcriptStructureSignature(items, columns, toolOutputExpanded) {
588
+ const list = Array.isArray(items) ? items : [];
589
+ let hA = fnvStepA(0x811c9dc5, `${list.length}|${columns}|${toolOutputExpanded ? 1 : 0}`);
590
+ let hB = fnvStepB(0xcbf29ce4, `${list.length}|${columns}|${toolOutputExpanded ? 1 : 0}`);
591
+ for (let i = 0; i < list.length; i++) {
592
+ const it = list[i];
593
+ let sigPart;
594
+ if (!it) {
595
+ sigPart = '_';
596
+ } else if (it.kind === 'assistant' && it.streaming) {
597
+ sigPart = `a${it.id}:${streamingEstimateRows(it, columns, toolOutputExpanded)}`;
598
+ } else {
599
+ const variantKey = transcriptItemVariantKey(it);
600
+ const cached = transcriptSigPartCache.get(it);
601
+ if (cached
602
+ && cached.variantKey === variantKey
603
+ && cached.columns === columns
604
+ && cached.id === it.id
605
+ && cached.kind === it.kind) {
606
+ sigPart = cached.sigPart;
607
+ } else {
608
+ sigPart = `${it.kind?.[0] || '?'}${it.id}:${variantKey}`;
609
+ transcriptSigPartCache.set(it, { id: it.id, kind: it.kind, variantKey, columns, sigPart });
610
+ }
611
+ }
612
+ hA = fnvStepA(hA, `;${i};`);
613
+ hA = fnvStepA(hA, sigPart);
614
+ hB = fnvStepB(hB, `;${i};`);
615
+ hB = fnvStepB(hB, sigPart);
616
+ }
617
+ return `${hA.toString(36)}.${hB.toString(36)}`;
618
+ }
619
+
620
+ export function transcriptRenderWindow(items, { scrollOffset = 0, viewportHeight = 24, columns = 80, toolOutputExpanded = false, rowIndex = null } = {}) {
621
+ const allItems = Array.isArray(items) ? items : [];
622
+ const itemCount = allItems.length;
623
+ const fallbackIndex = rowIndex?.prefixRows?.length === itemCount + 1
624
+ ? rowIndex
625
+ : buildTranscriptRowIndex(allItems, { columns, toolOutputExpanded });
626
+ const totalRows = Math.max(0, fallbackIndex.totalRows || 0);
627
+ const viewRows = Math.max(1, Number(viewportHeight) || 24);
628
+ const maxScrollRows = Math.max(0, totalRows - viewRows);
629
+ const effectiveScrollOffset = Math.min(
630
+ maxScrollRows,
631
+ Math.max(0, Math.ceil(Number(scrollOffset) || 0)),
632
+ );
633
+
634
+ const bypassRowBudget = viewRows + TRANSCRIPT_WINDOW_OVERSCAN_ROWS * 2;
635
+ if (itemCount <= TRANSCRIPT_WINDOW_MIN_ITEMS || totalRows <= bypassRowBudget) {
636
+ return { startIndex: 0, endIndex: itemCount, items: allItems, bottomSpacerRows: 0, totalRows, maxScrollRows, effectiveScrollOffset };
637
+ }
638
+
639
+ const minItems = Math.min(TRANSCRIPT_WINDOW_MIN_ITEMS, itemCount);
640
+ const maxItems = Math.max(minItems, TRANSCRIPT_WINDOW_MAX_ITEMS);
641
+ const prefixRows = fallbackIndex.prefixRows;
642
+ const visibleTop = Math.max(0, totalRows - effectiveScrollOffset - viewRows);
643
+ const visibleBottom = Math.min(totalRows, totalRows - effectiveScrollOffset);
644
+ const desiredTop = Math.max(0, visibleTop - TRANSCRIPT_WINDOW_OVERSCAN_ROWS);
645
+ const desiredBottom = Math.min(totalRows, visibleBottom + TRANSCRIPT_WINDOW_OVERSCAN_ROWS);
646
+
647
+ let startIndex = Math.max(0, upperBound(prefixRows, desiredTop) - 1);
648
+ let endIndex = Math.min(itemCount, Math.max(startIndex + 1, lowerBound(prefixRows, Math.max(desiredBottom, desiredTop + 1))));
649
+
650
+ while (endIndex - startIndex < minItems && startIndex > 0) startIndex--;
651
+ while (endIndex - startIndex < minItems && endIndex < itemCount) endIndex++;
652
+
653
+ if (endIndex - startIndex > maxItems) {
654
+ const visibleStartIndex = Math.max(0, upperBound(prefixRows, visibleTop) - 1);
655
+ const visibleEndIndex = Math.min(itemCount, Math.max(visibleStartIndex + 1, lowerBound(prefixRows, Math.max(visibleBottom, visibleTop + 1))));
656
+ startIndex = Math.max(0, Math.min(visibleStartIndex, itemCount - maxItems));
657
+ endIndex = Math.min(itemCount, Math.max(visibleEndIndex, startIndex + maxItems));
658
+ if (endIndex - startIndex > maxItems) startIndex = Math.max(0, endIndex - maxItems);
659
+ }
660
+
661
+ const bottomSpacerRows = Math.max(0, totalRows - (prefixRows[endIndex] || totalRows));
662
+ return {
663
+ startIndex,
664
+ endIndex,
665
+ items: allItems.slice(startIndex, endIndex),
666
+ bottomSpacerRows,
667
+ totalRows,
668
+ maxScrollRows,
669
+ effectiveScrollOffset,
670
+ };
671
+ }