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
@@ -13,6 +13,11 @@ export const BULLET_OPERATOR = '\u00b7'; // · - inline separator
13
13
  export const UP_ARROW = '\u2191'; // ↑ - history/scroll up
14
14
  export const DOWN_ARROW = '\u2193'; // ↓ - history/scroll down
15
15
 
16
+ // Agent surface markers — dispatch (call out) vs response (result back).
17
+ // Basic Arrows block renders reliably in Cascadia/Consolas (same block as ↑↓).
18
+ export const RIGHT_ARROW = '\u2192'; // → - agent call/dispatch marker
19
+ export const LEFT_ARROW = '\u2190'; // ← - agent response marker
20
+
16
21
  // Tool-result tree gutter. \u2514 (└, BOX DRAWINGS LIGHT UP AND RIGHT) renders
17
22
  // in virtually every monospace font; the previous \u23bf (bracket extension)
18
23
  // had no glyph in common terminal fonts (Cascadia/Consolas) and fell back to a
package/src/tui/index.jsx CHANGED
@@ -141,6 +141,101 @@ function resolveTuiStderrLogPath() {
141
141
  || join(process.env.MIXDOG_RUNTIME_ROOT || join(tmpdir(), 'mixdog'), 'mixdog-tui.stderr.log');
142
142
  }
143
143
 
144
+ /** `rgb(r,g,b)` → truecolor SGR prefix ('' when unparsable). */
145
+ function ansiFg(rgb) {
146
+ const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ''));
147
+ return m ? `\x1b[38;2;${m[1]};${m[2]};${m[3]}m` : '';
148
+ }
149
+
150
+ /**
151
+ * Paint a static boot splash into the (already-entered) alternate screen so
152
+ * the seconds between alt-screen entry and the first ink frame are not a blank
153
+ * void. The logo is drawn at the SAME rows/columns as App.jsx's welcome banner
154
+ * (marginTop 3 + 5 logo rows + blank + subtitle), so ink's first real frame
155
+ * overprints it in place with no visible jump. The bottom panel silhouette
156
+ * carries an animated "Loading projects…" spinner so the boot reads as an
157
+ * explicit loading state instead of an empty box that later fills with the
158
+ * list. Returns { stop } — call it right before ink mounts.
159
+ */
160
+ function paintBootSplash() {
161
+ try {
162
+ const cols = Math.max(1, Number(process.stdout.columns) || 80);
163
+ const rows = Math.max(1, Number(process.stdout.rows) || 24);
164
+ const center = (s) => `${' '.repeat(Math.max(0, Math.floor((cols - s.length) / 2)))}${s}`;
165
+ const logo = [
166
+ '███╗ ███╗██╗██╗ ██╗██████╗ ██████╗ ██████╗ ',
167
+ '████╗ ████║██║╚██╗██╔╝██╔══██╗██╔═══██╗██╔════╝ ',
168
+ '██╔████╔██║██║ ╚███╔╝ ██║ ██║██║ ██║██║ ███╗',
169
+ '██║╚██╔╝██║██║ ██╔██╗ ██║ ██║██║ ██║██║ ██║',
170
+ '██║ ╚═╝ ██║██║██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝',
171
+ ];
172
+ const textFg = ansiFg(theme.text);
173
+ const logoFg = ansiFg(theme.logo ?? theme.claude) || textFg;
174
+ const subtleFg = ansiFg(theme.inactive);
175
+ const bold = '\x1b[1m';
176
+ const reset = '\x1b[0m';
177
+ let out = '\x1b[4;1H'; // row 4 == App banner's marginTop={3}
178
+ for (let i = 0; i < logo.length; i++) {
179
+ const fg = i < 2 ? textFg : logoFg;
180
+ out += `${bold}${fg}${center(logo[i])}${reset}\r\n`;
181
+ }
182
+ out += '\r\n';
183
+ out += `${subtleFg}${center(`mixdog coding agent · ${process.cwd()}`)}${reset}`;
184
+ // Bottom placeholder: the first REAL screen is the project picker — a tall
185
+ // floating panel sitting directly above the 3-row statusline — not the
186
+ // 3-row prompt box. Painting a short prompt silhouette here made the panel
187
+ // look like it "unfolded" from 3 rows to full height on the first ink
188
+ // frame. Mirror App.jsx's startup math instead:
189
+ // floatingPanelRows = min(17, rows - 15), bottom row = rows - 3.
190
+ const floating = Math.min(17, rows - 15);
191
+ let spinnerRow = 0;
192
+ let spinnerFg = '';
193
+ if (floating >= 3) {
194
+ const borderFg = ansiFg(theme.promptBorder) || subtleFg;
195
+ // Leave the last COLUMN untouched: writing the bottom-right cell makes
196
+ // Windows Terminal/conhost scroll the alt screen one row (same reason
197
+ // App.jsx reserves rightSafetyColumns), which made the whole splash jump
198
+ // when the box was painted at full width on the final row.
199
+ const boxW = Math.max(4, cols - 1);
200
+ const innerW = boxW - 2;
201
+ const topRow = rows - 3 - floating + 1;
202
+ out += `\x1b[${topRow};1H${borderFg}╭${'─'.repeat(innerW)}╮`;
203
+ for (let i = 1; i < floating - 1; i++) {
204
+ out += `\x1b[${topRow + i};1H│${' '.repeat(innerW)}│`;
205
+ }
206
+ out += `\x1b[${rows - 3};1H╰${'─'.repeat(innerW)}╯${reset}`;
207
+ // "Loading projects…" line inside the panel silhouette. Picker.jsx
208
+ // renders border, then (paddingX=1, no paddingY) title/help row as its
209
+ // very first interior row — not vertically centered. Match that rhythm
210
+ // (topRow + 1) instead of centering, so ink's first frame doesn't jump
211
+ // the text down/up when it overprints this splash.
212
+ spinnerRow = topRow + 1;
213
+ spinnerFg = subtleFg;
214
+ out += `\x1b[${spinnerRow};3H${subtleFg} Loading projects…${reset}`;
215
+ }
216
+ // Park the cursor at home so ink's first frame paints top-down over the
217
+ // splash instead of starting at the bottom row and scrolling the screen.
218
+ out += '\x1b[H';
219
+ process.stdout.write(out);
220
+ // Animate only the single spinner glyph cell — never reflow anything else,
221
+ // so stopping mid-frame can't leave torn output for ink to overwrite.
222
+ if (spinnerRow > 0) {
223
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
224
+ let tick = 0;
225
+ const timer = setInterval(() => {
226
+ try {
227
+ process.stdout.write(`\x1b[${spinnerRow};3H${spinnerFg}${frames[tick % frames.length]}${reset}\x1b[H`);
228
+ tick += 1;
229
+ } catch { /* ignore */ }
230
+ }, 80);
231
+ timer.unref?.();
232
+ return { stop: () => clearInterval(timer) };
233
+ }
234
+ return { stop: () => {} };
235
+ } catch { /* cosmetic only — never block boot */ }
236
+ return { stop: () => {} };
237
+ }
238
+
144
239
  /** Drain stdin so queued key/mouse bytes do not leak into the shell after exit. */
145
240
  function drainStdin(stdin = process.stdin) {
146
241
  if (!stdin.isTTY) return;
@@ -277,17 +372,27 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
277
372
  try { await loadThemeSettingFromConfig(); } catch { /* default theme stays */ }
278
373
  emitTerminalBackground(theme.background);
279
374
 
375
+ // Static splash + loading spinner while createEngineSession() warms up
376
+ // (config, providers, sessions). Without this the user stares at an empty
377
+ // alt screen for the whole boot, then the UI "pops in" — the splash makes
378
+ // the first ink frame an in-place repaint instead.
379
+ const splash = paintBootSplash() || { stop: () => {} };
380
+
280
381
  let store;
281
382
  try {
282
383
  store = await createEngineSession({ provider, model, toolMode, remote });
283
384
  bootProfile('store:ready', { ms: (performance.now() - startedAt).toFixed(1) });
284
385
  } catch (error) {
386
+ splash.stop();
285
387
  restoreTerminal();
286
388
  try { process.off('exit', restoreTerminal); } catch { /* ignore */ }
287
389
  restoreStderr();
288
390
  process.stderr.write(`mixdog: ${error?.message || error}\n`);
289
391
  return 1;
290
392
  }
393
+ // Stop the spinner BEFORE ink mounts so no stray splash write can land
394
+ // between (or after) ink's first frames.
395
+ splash.stop();
291
396
 
292
397
  // Keep mouse handling app-owned by default: native terminal selections are
293
398
  // cleared by the fullscreen redraws that happen while a turn streams. Users
@@ -1,4 +1,4 @@
1
- import stringWidth from 'string-width';
1
+ import { displayWidth } from './display-width.mjs';
2
2
 
3
3
  const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
4
4
 
@@ -122,32 +122,79 @@ export function lineEnd(text, offset) {
122
122
  return end === -1 ? value.length : end;
123
123
  }
124
124
 
125
- export function caretPosition(text, offset, width) {
125
+ // Return the [start,end) run of same-class characters (word-like vs
126
+ // non-word-like) surrounding `offset`, used by double-click word selection.
127
+ // Mirrors typical desktop double-click behavior: clicking inside a run of
128
+ // letters/digits selects that whole run; clicking on a run of punctuation or
129
+ // whitespace selects that run instead (still deterministic, never empty).
130
+ export function wordRangeAt(text, offset) {
131
+ const value = String(text || '');
132
+ const units = graphemeUnits(value);
133
+ if (units.length === 0) return { start: 0, end: 0 };
134
+ const off = clampOffset(value, offset);
135
+ let idx = units.findIndex((u) => off >= u.start && off < u.end);
136
+ if (idx === -1) {
137
+ idx = units.length - 1;
138
+ for (let i = 0; i < units.length; i += 1) {
139
+ if (units[i].start >= off) { idx = i > 0 ? i - 1 : 0; break; }
140
+ }
141
+ }
142
+ const targetIsWord = wordLike(units[idx].segment);
143
+ let start = idx;
144
+ while (start > 0 && wordLike(units[start - 1].segment) === targetIsWord) start -= 1;
145
+ let end = idx;
146
+ while (end < units.length - 1 && wordLike(units[end + 1].segment) === targetIsWord) end += 1;
147
+ return { start: units[start].start, end: units[end].end };
148
+ }
149
+
150
+ // `hasTrailingContent` (default derived from the full text) decides whether a
151
+ // caret that lands FLUSH on the last column (col === w) rolls to the next row.
152
+ // It must reflect whether ANY rendered cell follows this offset in the FULL
153
+ // text — not just the sliced prefix — otherwise a caret at a mid-text soft-wrap
154
+ // boundary reports {row N, col w} while ink already rendered the next glyph on
155
+ // row N+1, dropping the caret outside the box. PromptInput also appends a
156
+ // trailing space cell when the cursor sits at end-of-input, so "end of text"
157
+ // still has a following cell there; callers pass hasTrailingContent=true for
158
+ // that case.
159
+ export function caretPosition(text, offset, width, hasTrailingContent = undefined) {
126
160
  const value = String(text || '');
127
161
  const before = value.slice(0, offset);
128
162
  const w = safeWidth(width);
163
+ const followsInFullText = hasTrailingContent === undefined
164
+ ? offset < value.length
165
+ : hasTrailingContent === true;
129
166
  let row = 0;
130
167
  let col = 0;
131
-
132
- for (const { segment } of graphemeSegmenter.segment(before)) {
168
+ const segments = [...graphemeSegmenter.segment(before)];
169
+ for (let i = 0; i < segments.length; i += 1) {
170
+ const { segment } = segments[i];
133
171
  if (segment === '\n') {
134
172
  row += 1;
135
173
  col = 0;
136
174
  continue;
137
175
  }
138
176
 
139
- const segmentWidth = stringWidth(segment);
177
+ const segmentWidth = displayWidth(segment);
140
178
  if (segmentWidth === 0) continue;
141
179
 
180
+ // Mirror ink's wrap-ansi wrapWord (wrap="hard", wordWrap:false): a glyph
181
+ // that would overflow the row is pushed WHOLE to the next row (col never
182
+ // exceeds w for wide chars — no half-column left behind), and a glyph that
183
+ // lands flush on the last column starts a new row ONLY when more rendered
184
+ // content follows. Whether content follows is decided from the FULL text
185
+ // (followsInFullText), not the sliced prefix, so a caret at a mid-text wrap
186
+ // boundary rolls to row N+1 exactly as ink renders it. The old
187
+ // `col >= w → row += floor(col/w)` over-counted a row for width-2 glyphs at
188
+ // odd widths (col could reach w+1), landing the cursor one row too low.
142
189
  if (col > 0 && col + segmentWidth > w) {
143
190
  row += 1;
144
191
  col = 0;
145
192
  }
146
-
147
193
  col += segmentWidth;
148
- if (col >= w) {
149
- row += Math.floor(col / w);
150
- col %= w;
194
+ const moreFollows = i < segments.length - 1 || followsInFullText;
195
+ if (col === w && moreFollows) {
196
+ row += 1;
197
+ col = 0;
151
198
  }
152
199
  }
153
200
 
@@ -185,13 +232,16 @@ function boundaryPositions(text, width) {
185
232
  for (const unit of graphemeUnits(value)) offsets.push(unit.end);
186
233
 
187
234
  const seen = new Set();
188
- return offsets
235
+ const positions = offsets
189
236
  .filter((offset) => {
190
237
  if (seen.has(offset)) return false;
191
238
  seen.add(offset);
192
239
  return true;
193
240
  })
241
+ // Decide the flush-boundary advance from the FULL text so a soft-wrap
242
+ // offset reports {row N+1, col 0} (matching ink) instead of {row N, col w}.
194
243
  .map((offset) => ({ offset, ...caretPosition(value, offset, width) }));
244
+ return positions;
195
245
  }
196
246
 
197
247
  export function verticalOffset(text, offset, width, direction, preferredColumn = null) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * src/tui/keyboard-protocol.mjs — terminal extended-keys capability gate.
3
3
  *
4
- * We do NOT query the terminal. Instead, mirroring Claude Code, we enable the
4
+ * We do NOT query the terminal. Instead, we enable the
5
5
  * kitty keyboard protocol AND xterm modifyOtherKeys SYNCHRONOUSLY at the moment
6
6
  * raw mode turns on (see App.jsx's mount effect), gated by the allowlist below.
7
7
  * Writing both enables unconditionally is safe — a terminal honors whichever it
@@ -22,7 +22,7 @@ export const POP_KITTY = '\x1b[<u';
22
22
  export const DISABLE_MODIFY_OTHER_KEYS = '\x1b[>4;0m';
23
23
 
24
24
  // Allowlist of terminals known to honor kitty and/or xterm modifyOtherKeys.
25
- // Mirrors Claude Code's supportsExtendedKeys allowlist. VS Code's xterm.js
25
+ // VS Code's xterm.js
26
26
  // integrated terminal mishandles these sequences, so it is excluded first.
27
27
  // Honors the MIXDOG_TUI_EXTENDED_KEYS opt-out (=0) / override (=1).
28
28
  export function supportsExtendedKeys() {
@@ -80,28 +80,42 @@ export function isVoiceEnabled() {
80
80
  }
81
81
 
82
82
  // Coalesce ensure*'s onProgress ({ phase, downloaded, total } | { phase:'extra', ... })
83
- // into a throttled (2s) human-readable pushNotice call so a multi-hundred-MB
84
- // model download doesn't spam the notice toast on every chunk.
85
- function makeThrottledProgressNotice(pushNotice, intervalMs = 2000) {
83
+ // into a sticky, in-place input-hint-line progress bar (setProgressHint) so a
84
+ // multi-hundred-MB model download doesn't spam the notice toast on every
85
+ // chunk. Falls back to the old throttled (2s) toast-spam behavior when the
86
+ // caller (e.g. the channels pipeline) doesn't have a progress-hint slot to
87
+ // render into — only pushNotice is guaranteed to exist everywhere.
88
+ const PROGRESS_BAR_CELLS = 6;
89
+ function renderProgressBarText(phase, downloaded, total) {
90
+ const label = phaseLabel(phase);
91
+ if (total > 0) {
92
+ const ratio = Math.max(0, Math.min(1, downloaded / total));
93
+ const filled = Math.round(ratio * PROGRESS_BAR_CELLS);
94
+ const bar = '▓'.repeat(filled) + '░'.repeat(PROGRESS_BAR_CELLS - filled);
95
+ return `⬇ Voice ${label} ${bar} ${Math.round(ratio * 100)}%`;
96
+ }
97
+ return `⬇ Voice ${label} …`;
98
+ }
99
+ function phaseLabel(phase) {
100
+ if (phase === 'model') return 'model';
101
+ if (phase === 'ffmpeg') return 'ffmpeg';
102
+ if (phase === 'extra') return 'extra data';
103
+ return 'whisper runtime';
104
+ }
105
+ function makeThrottledProgressNotice({ pushNotice, setProgressHint } = {}, intervalMs = 500) {
86
106
  let lastEmitAt = 0;
87
- const phaseLabel = (phase) => {
88
- if (phase === 'model') return 'model';
89
- if (phase === 'ffmpeg') return 'ffmpeg';
90
- if (phase === 'extra') return 'extra data';
91
- return 'whisper runtime';
92
- };
93
107
  return (progress = {}) => {
94
- if (typeof pushNotice !== 'function') return;
95
108
  const now = Date.now();
96
109
  if (now - lastEmitAt < intervalMs) return;
97
110
  lastEmitAt = now;
98
111
  const total = Number(progress.total) || 0;
99
112
  const downloaded = Number(progress.downloaded) || 0;
100
- const label = phaseLabel(progress.phase);
101
- const text = total > 0
102
- ? `Voice: downloading ${label}… ${Math.min(100, Math.round((downloaded / total) * 100))}%`
103
- : `Voice: downloading ${label}…`;
104
- pushNotice(text, 'info');
113
+ const text = renderProgressBarText(progress.phase, downloaded, total);
114
+ if (typeof setProgressHint === 'function') {
115
+ setProgressHint(text, 'info');
116
+ return;
117
+ }
118
+ if (typeof pushNotice === 'function') pushNotice(text, 'info');
105
119
  };
106
120
  }
107
121
 
@@ -112,12 +126,12 @@ function makeThrottledProgressNotice(pushNotice, intervalMs = 2000) {
112
126
  * runtime descriptor (resolveVoiceRuntime shape) once `installed` is true;
113
127
  * throws on any ensure* failure (manifest fetch, sha256 mismatch, etc.).
114
128
  */
115
- export async function ensureVoiceRuntimeReady({ dataDir = resolvePluginData(), pushNotice } = {}) {
129
+ export async function ensureVoiceRuntimeReady({ dataDir = resolvePluginData(), pushNotice, setProgressHint } = {}) {
116
130
  const fetcher = await loadVoiceRuntimeFetcher();
117
131
  let runtime = fetcher.resolveVoiceRuntime(dataDir);
118
132
  if (runtime.installed) return runtime;
119
133
 
120
- const onProgress = makeThrottledProgressNotice(pushNotice);
134
+ const onProgress = makeThrottledProgressNotice({ pushNotice, setProgressHint });
121
135
  if (!runtime.binary || !runtime.serverCmd) {
122
136
  await fetcher.ensureWhisperRuntime(dataDir, onProgress);
123
137
  }
@@ -147,7 +161,7 @@ export async function ensureVoiceRuntimeReady({ dataDir = resolvePluginData(), p
147
161
  * Returns the NEW enabled state (true/false) on success, or null when the
148
162
  * toggle could not run (install already in flight) or failed.
149
163
  */
150
- export async function toggleVoice({ pushNotice } = {}) {
164
+ export async function toggleVoice({ pushNotice, setProgressHint } = {}) {
151
165
  const dataDir = resolvePluginData();
152
166
  if (isVoiceEnabled()) {
153
167
  // Med-5: updateSection is a synchronous file write (readAllForRmW +
@@ -172,11 +186,13 @@ export async function toggleVoice({ pushNotice } = {}) {
172
186
  }
173
187
  _voiceInstallBusy = true;
174
188
  try {
175
- await ensureVoiceRuntimeReady({ dataDir, pushNotice });
189
+ await ensureVoiceRuntimeReady({ dataDir, pushNotice, setProgressHint });
176
190
  updateSection('voice', (current) => ({ ...current, enabled: true }));
191
+ setProgressHint?.('');
177
192
  pushNotice?.('Voice ON — Ctrl+Space to record', 'info');
178
193
  return true;
179
194
  } catch (err) {
195
+ setProgressHint?.('');
180
196
  pushNotice?.(`Voice setup failed: ${err?.message || err}`, 'error');
181
197
  return null;
182
198
  } finally {
@@ -108,8 +108,8 @@ function decodeEntities(s) {
108
108
 
109
109
  // ── Fenced code block rendering ─────────────────────────────────────────────
110
110
  // Gutter-indented, syntax-highlighted body with NO background band and no ```
111
- // fences (codex/claude-code convention). Diff/patch languages (and bodies that
112
- // look like unified diffs) keep the diff highlighter; other languages use
111
+ // fences. Diff/patch languages (and bodies that look like unified diffs) keep
112
+ // the diff highlighter; other languages use
113
113
  // cli-highlight (highlight.js) themed from our syntax* palette; unknown
114
114
  // languages fall back to flat `mdCodeBlock` body color.
115
115
  const DIFF_LANGS = new Set(['diff', 'patch', 'udiff', 'git-diff', 'gitdiff']);
@@ -567,7 +567,7 @@ function collectHighlightedBodyLines(text, hljsLang, bandWidth) {
567
567
  const CODE_GUTTER = ' ';
568
568
 
569
569
  /**
570
- * Render a fenced `code` token (codex/claude-code convention): an optional
570
+ * Render a fenced `code` token: an optional
571
571
  * subtle language label row, then the wrapped, syntax/diff/flat-highlighted
572
572
  * body. Every row is left-indented by a 2-col gutter. NO background band, no
573
573
  * full-width padding, and no visible ``` fence lines.
@@ -653,7 +653,7 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
653
653
  const ex = extraColorizers();
654
654
  switch (token.type) {
655
655
  case 'blockquote': {
656
- // Block structure carries no color (claude-code policy): a dim bar plus
656
+ // Block structure carries no color: a dim bar plus
657
657
  // body-colored italic text. Color is reserved for inline codespan/link.
658
658
  const bar = chalk.dim(BLOCKQUOTE_BAR);
659
659
  const quotePrefix = `${BLOCKQUOTE_BAR} `;
@@ -673,12 +673,11 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
673
673
  return renderCodeBlock(token, width);
674
674
  case 'codespan':
675
675
  // inline code — accent color only (no background tint; a bg box behind
676
- // inline spans reads as awkward against body text, matches claude-code's
677
- // codespan = color-only treatment).
676
+ // inline spans reads as awkward against body text).
678
677
  return accent(decodeEntities(token.text));
679
678
  case 'em':
680
- // Italic only — no color tint (matches codex/claude-code; a colored em
681
- // clashes per-theme and reads loud against body prose).
679
+ // Italic only — no color tint (a colored em clashes per-theme and
680
+ // reads loud against body prose).
682
681
  return chalk.italic((token.tokens ?? []).map((t) => formatToken(t, 0, null, parent)).join(''));
683
682
  case 'strong':
684
683
  // Bold only — no color tint (stays body-colored, just heavier weight).
@@ -723,6 +722,9 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
723
722
  return `${OSC8_OPEN(href)}${ex.link(href)}${OSC8_CLOSE}`;
724
723
  }
725
724
  case 'list':
725
+ // Honor loose lists (blank line between items in the source markdown):
726
+ // items are joined with one extra EOL so multi-line items keep the
727
+ // author's breathing room. Tight lists render adjacent as before.
726
728
  return token.items
727
729
  .map((item, index) =>
728
730
  formatToken(
@@ -734,7 +736,7 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
734
736
  depth,
735
737
  ),
736
738
  )
737
- .join('');
739
+ .join(token.loose && depth === 0 ? EOL : '');
738
740
  case 'list_item':
739
741
  return formatListItem(token, listBaseIndent, orderedListNumber, parent, depth, width);
740
742
  case 'paragraph':
@@ -67,6 +67,44 @@ export function imageReferenceIds(input) {
67
67
  return new Set([...String(input || '').matchAll(re)].map((m) => Number(m[1]) || 0).filter(Boolean));
68
68
  }
69
69
 
70
+ // --- Large pasted-text folding ------------------------------------------
71
+ // A big paste is replaced in the prompt by a compact token so the editor does
72
+ // not choke on thousands of raw characters; the original text is stored in an
73
+ // App-level map (mirroring pastedImages) and expanded back on submit.
74
+ const PASTE_TOKEN_MIN_LINES = 3;
75
+ const PASTE_TOKEN_MIN_CHARS = 200;
76
+
77
+ export function shouldFoldPastedText(text) {
78
+ const value = String(text ?? '');
79
+ if (!value) return false;
80
+ const lineCount = value.split('\n').length;
81
+ return lineCount >= PASTE_TOKEN_MIN_LINES || value.length > PASTE_TOKEN_MIN_CHARS;
82
+ }
83
+
84
+ export function formatPastedTextRef(id, text) {
85
+ const lines = String(text ?? '').split('\n').length;
86
+ return `[Pasted text #${id} +${lines} lines]`;
87
+ }
88
+
89
+ export function pastedTextReferenceIds(input) {
90
+ const re = /\[Pasted text #(\d+) \+\d+ lines\]/g;
91
+ return new Set([...String(input || '').matchAll(re)].map((m) => Number(m[1]) || 0).filter(Boolean));
92
+ }
93
+
94
+ // Expand every intact pasted-text token in `value` back to its original text.
95
+ // Broken / partially-deleted tokens simply do not match and are left as-is.
96
+ export function expandPastedTextTokens(value, pastedTexts = {}) {
97
+ let out = String(value ?? '');
98
+ if (!pastedTexts || typeof pastedTexts !== 'object') return out;
99
+ const re = /\[Pasted text #(\d+) \+\d+ lines\]/g;
100
+ out = out.replace(re, (match, idRaw) => {
101
+ const entry = pastedTexts[Number(idRaw)] || pastedTexts[idRaw];
102
+ if (entry && typeof entry.text === 'string') return entry.text;
103
+ return match;
104
+ });
105
+ return out;
106
+ }
107
+
70
108
  export function buildPromptContentWithImages(text, pastedImages = {}) {
71
109
  const value = String(text ?? '');
72
110
  const refs = imageReferenceIds(value);
@@ -42,11 +42,16 @@ export function isResetStatsState(stats) {
42
42
  /**
43
43
  * True when the footer should snap local / invalidate cached async output.
44
44
  * Keep in sync with `shouldSnapLocalStatusline` in StatusLine.jsx.
45
+ * `agentRevision` is intentionally NOT checked here: agent worker/job churn
46
+ * must only kick off the async full-render refresh (the render effect already
47
+ * depends on `agentRevision`), never snap the footer back to the usage-less
48
+ * `localBootStatusLine` before/after the first full render — that was the
49
+ * source of the boot-entry flicker. `agentRevision` still participates in
50
+ * `statuslineFooterCacheKey` below so the async cache key churns correctly.
45
51
  */
46
52
  export function statuslineFooterIdentityChanged(args, lastArgs) {
47
53
  if (!args) return false;
48
54
  if (!lastArgs) return true;
49
- if (args.agentRevision !== lastArgs.agentRevision) return true;
50
55
  if (args.sessionId !== lastArgs.sessionId) return true;
51
56
  if (args.provider !== lastArgs.provider || args.model !== lastArgs.model) return true;
52
57
  if (args.effort !== lastArgs.effort || args.fast !== lastArgs.fast) return true;
@@ -60,8 +65,11 @@ export function statuslineFooterIdentityChanged(args, lastArgs) {
60
65
 
61
66
  /**
62
67
  * Cache key for the last async full statusline (identity-changing props only).
63
- * Must stay aligned with `shouldSnapLocalStatusline` in StatusLine.jsx.
64
- * Stats reset is not keyed here; `statuslineFooterIdentityChanged` clears cache instead.
68
+ * Includes `agentRevision` as a churn key (so a stale async result is not kept
69
+ * forever), but `agentRevision` is deliberately excluded from
70
+ * `statuslineFooterIdentityChanged` above — it must not itself force a local
71
+ * snap-back. Stats reset is not keyed here; `statuslineFooterIdentityChanged`
72
+ * clears cache instead.
65
73
  */
66
74
  export function statuslineFooterCacheKey({
67
75
  agentRevision = '',
package/src/tui/theme.mjs CHANGED
@@ -168,10 +168,16 @@ import {
168
168
  BLACK_CIRCLE,
169
169
  RESULT_GUTTER_GLYPH,
170
170
  RESULT_GUTTER_CONT_GLYPH,
171
+ RIGHT_ARROW,
172
+ LEFT_ARROW,
171
173
  } from './figures.mjs';
172
174
 
173
175
  /** Turn marker — BLACK_CIRCLE (`⏺` on macOS; `●` elsewhere). */
174
176
  export const TURN_MARKER = BLACK_CIRCLE;
177
+ /** Agent call marker — `←` (request going out to a sub-agent). */
178
+ export const AGENT_CALL_MARKER = LEFT_ARROW;
179
+ /** Agent response marker — `→` (result coming back in to the transcript). */
180
+ export const AGENT_RESPONSE_MARKER = RIGHT_ARROW;
175
181
  /** Result-tree gutter — `└` (U+2514) padded to a 2-col hanging indent. */
176
182
  export const RESULT_GUTTER = ` ${RESULT_GUTTER_GLYPH} `;
177
183
  /**
@@ -35,8 +35,8 @@ export const basePalette = {
35
35
  // Picker/list focus row. This is intentionally stronger than text selection.
36
36
  selectionText: 'rgb(230,238,255)', // readable on selectionBackground
37
37
  selectionBackground: 'rgb(38,79,120)', // classic dark-mode selection blue
38
- // Mouse/drag text-selection overlay. Claude Code keeps this role separate:
39
- // it replaces the cell background while text remains copy-highlight readable.
38
+ // Mouse/drag text-selection overlay: a separate role from selectionBackground
39
+ // so it replaces the cell background while text remains copy-highlight readable.
40
40
  selectionHighlightText: 'rgb(255,255,255)',
41
41
  selectionHighlightBackground: 'rgb(38,79,120)',
42
42
  inactive: 'rgb(132,139,152)', // light_grey
@@ -13,10 +13,10 @@ import { basePalette } from './base.mjs';
13
13
  export const kanagawaPalette = {
14
14
  ...basePalette,
15
15
  background: 'transparent', // sumiInk3 #1F1F28
16
- claude: 'rgb(210,126,153)', // sakuraPink accent (opencode)
16
+ claude: 'rgb(210,126,153)', // sakuraPink accent
17
17
  logo: 'rgb(232,156,182)', // welcome banner — brighter than accent
18
18
  claudeShimmer: 'rgb(156,171,202)', // springViolet2 #9CABCA (lighter accent)
19
- mixdogOrange: 'rgb(210,126,153)', // sakuraPink accent (opencode)
19
+ mixdogOrange: 'rgb(210,126,153)', // sakuraPink accent
20
20
  mixdogAmber: 'rgb(156,171,202)', // lighter accent
21
21
  mixdogIvory: 'rgb(220,215,186)', // fujiWhite #DCD7BA
22
22
  spinnerGlyph: 'rgb(228,104,118)', // most-emphasized: waveRed
@@ -57,7 +57,7 @@ export const kanagawaPalette = {
57
57
  mdLink: 'rgb(126,156,216)', // crystalBlue
58
58
  mdLinkText: 'rgb(118,148,106)', // waveAqua #76946A
59
59
  mdStrong: 'rgb(215,166,87)', // roninYellow #D7A657
60
- mdEmph: 'rgb(195,141,157)', // carpYellow opencode #C38D9D
60
+ mdEmph: 'rgb(195,141,157)', // carpYellow #C38D9D
61
61
  mdDiffAdded: 'rgb(152,187,108)', // springGreen
62
62
  mdDiffRemoved: 'rgb(255,93,98)', // peachRed
63
63
  mdDiffHunk: 'rgb(45,79,103)', // waveBlue #2D4F67
@@ -71,7 +71,7 @@ export const kanagawaPalette = {
71
71
  syntaxVariable: 'rgb(220,215,186)', // fujiWhite
72
72
  syntaxString: 'rgb(152,187,108)', // springGreen
73
73
  syntaxNumber: 'rgb(215,166,87)', // roninYellow #D7A657
74
- syntaxType: 'rgb(195,141,157)', // carpYellow opencode #C38D9D
74
+ syntaxType: 'rgb(195,141,157)', // carpYellow #C38D9D
75
75
  syntaxOperator: 'rgb(210,126,153)', // sakuraPink
76
76
  syntaxPunctuation: 'rgb(220,215,186)', // fg
77
77
  userMessageBackground: 'rgb(42,42,55)', // sumiInk4 #2A2A37
@@ -1,16 +1,15 @@
1
1
  /**
2
- * src/tui/themes/teal.mjs — Teal (pi style).
2
+ * src/tui/themes/teal.mjs — Teal.
3
3
  *
4
- * Faithful port of pi's `dark.json` (refs/pi .../theme/dark.json). Values are
5
- * taken verbatim: accent teal #8abeb7, body text #d4d4d4, page bg #18181e,
4
+ * Values: accent teal #8abeb7, body text #d4d4d4, page bg #18181e,
6
5
  * green #b5bd68, red #cc6666, warning pure yellow #ffff00, blue #81a2be,
7
- * mdHeading #f0c674, gray #808080. Syntax uses pi's VS Code Dark+ palette
6
+ * mdHeading #f0c674, gray #808080. Syntax uses a VS Code Dark+-style palette
8
7
  * (keyword #569CD6, string #CE9178, function #DCDCAA, etc.). Spreads
9
8
  * `basePalette` first so the full key set is always present.
10
9
  */
11
10
  import { basePalette } from './base.mjs';
12
11
 
13
- /** Teal — pi-style teal accent with soft body text. */
12
+ /** Teal — teal accent with soft body text. */
14
13
  export const tealPalette = {
15
14
  ...basePalette,
16
15
  background: 'transparent', // export.pageBg #18181e
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * src/tui/themes/utils.mjs — palette post-processing helpers.
3
3
  *
4
- * Reference pattern (Claude Code / opencode / pi):
4
+ * Design goals:
5
5
  * - keep body text neutral and highly readable;
6
6
  * - keep strong live-state colors for spinners/status;
7
7
  * - use accent colors sparingly for headings, links and syntax, preferably