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
@@ -1,354 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { marked } from 'marked';
4
- import stripAnsi from 'strip-ansi';
5
- import stringWidth from 'string-width';
6
- import {
7
- formatToken,
8
- highlightCodeBlockToLines,
9
- extraColorizers,
10
- _highlightCacheSizeForTests,
11
- } from './format-token.mjs';
12
- import { setThemeSetting, theme } from '../theme.mjs';
13
-
14
- setThemeSetting('basic', { persist: false });
15
-
16
- function rgbSgr(value) {
17
- const m = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(String(value || ''));
18
- return m ? `38;2;${m[1]};${m[2]};${m[3]}` : null;
19
- }
20
-
21
- function lexFirst(md, type) {
22
- const tokens = marked.lexer(md);
23
- return tokens.find((t) => t.type === type) ?? tokens[0];
24
- }
25
-
26
- test('fenced code block has no ``` fences and shows a language label row', () => {
27
- const token = lexFirst('```js\nconst x = 1;\n```\n', 'code');
28
- const out = formatToken(token, 0, null, null, 60);
29
- const plain = stripAnsi(out);
30
- assert.ok(!plain.includes('```'), 'no literal fence markers');
31
- assert.ok(plain.includes('const x = 1;'), 'body present');
32
- // The first non-empty row is the subtle language label; its trimmed text is
33
- // exactly the bare lang token (gutter trims away).
34
- const rows = plain.split('\n').filter((l) => l.trim().length > 0);
35
- assert.equal(rows[0].trim(), 'js', 'first row is the language label');
36
- });
37
-
38
- test('short code line is gutter-indented with no background band', () => {
39
- const width = 60;
40
- const token = lexFirst('```js\nshort\n```\n', 'code');
41
- const out = formatToken(token, 0, null, null, width);
42
- // No background band: codex/claude-code render code with no `48;2;` bg.
43
- assert.ok(!out.includes('48;2;'), 'no background SGR');
44
- const bodyLine = stripAnsi(out).split('\n').find((l) => l.includes('short'));
45
- assert.ok(bodyLine, 'body line present');
46
- assert.ok(bodyLine.startsWith(' short'), 'body is gutter-indented');
47
- // Content width only — NOT padded out to the full render width.
48
- assert.equal(stringWidth(bodyLine), ' short'.length, 'body row follows content width');
49
- });
50
-
51
- test('code block body lines have no background band and follow content width', () => {
52
- const width = 80;
53
- const token = lexFirst('```js\na\n\nlonger line\n```\n', 'code');
54
- const out = formatToken(token, 0, null, null, width);
55
- assert.ok(!out.includes('48;2;'), 'no background SGR anywhere');
56
- const plainLines = stripAnsi(out).split('\n').filter((l) => l.length > 0);
57
- const widths = plainLines.map((l) => stringWidth(l));
58
- assert.ok(widths.length >= 2, 'body lines rendered');
59
- // Widths follow content (gutter + visible text), not forced equal to render
60
- // width: the longest row stays well under the 80-col render width.
61
- assert.ok(Math.max(...widths) < width, 'rows are not padded to the render width');
62
- });
63
-
64
- test('long code line wraps within requested body width', () => {
65
- const long = 'a'.repeat(40);
66
- const token = lexFirst(`\`\`\`js\n${long}\n\`\`\`\n`, 'code');
67
- const width = 20;
68
- const out = formatToken(token, 0, null, null, width);
69
- const bodyLines = stripAnsi(out).split('\n').filter((l) => l.includes('a'));
70
- assert.ok(bodyLines.length > 1, 'long line is wrapped into multiple rows');
71
- for (const line of bodyLines) {
72
- assert.ok(line.length <= width, `wrapped line "${line}" fits width ${width}`);
73
- }
74
- assert.equal(bodyLines.join('').replace(/\s/g, ''), long, 'wrapped segments preserve content');
75
- });
76
-
77
- test('fenced code expands tabs so width math matches the terminal', () => {
78
- // A raw \t counts as ZERO cells in string-width ("Tabs are ignored by
79
- // design"), so without normalization the wrap/row math under-counts a
80
- // tab-bearing line and the terminal-expanded line bleeds past the viewport
81
- // clip. After normalization every rendered row must contain no raw \t and
82
- // its visible width must equal its character length (tabs → spaces).
83
- const token = lexFirst('```\n\tindented\n```\n', 'code');
84
- const out = formatToken(token, 0, null, null, 60);
85
- const plain = stripAnsi(out);
86
- assert.ok(!plain.includes('\t'), 'no raw tab survives into the rendered string');
87
- const bodyLine = plain.split('\n').find((l) => l.includes('indented'));
88
- assert.ok(bodyLine, 'body line present');
89
- assert.equal(stringWidth(bodyLine), bodyLine.length, 'visible width equals char length (no zero-width tab)');
90
- });
91
-
92
- test('fenced code strips stray C0 control chars except newline', () => {
93
- // A bare \x07 (BELL) etc. measures as zero width but can move the terminal
94
- // cursor; replace with a space so ink's row accounting holds.
95
- const token = lexFirst('```\na\x07b\n```\n', 'code');
96
- const out = formatToken(token, 0, null, null, 60);
97
- const plain = stripAnsi(out);
98
- assert.ok(!/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(plain), 'no raw C0/DEL control survives');
99
- const bodyLine = plain.split('\n').find((l) => l.includes('a') && l.includes('b'));
100
- assert.ok(bodyLine, 'body line present');
101
- assert.equal(stringWidth(bodyLine), bodyLine.length, 'control char became a real space cell');
102
- });
103
-
104
- test('list item fenced code block respects narrowed width and has no fences', () => {
105
- const long = 'b'.repeat(36);
106
- const codeTok = lexFirst(`\`\`\`js\n${long}\n\`\`\`\n`, 'code');
107
- const listTok = {
108
- type: 'list',
109
- ordered: false,
110
- items: [{ type: 'list_item', tokens: [codeTok] }],
111
- };
112
- const outerWidth = 22;
113
- const out = formatToken(listTok, 0, null, null, outerWidth);
114
- const plain = stripAnsi(out);
115
- assert.ok(!plain.includes('```'), 'no literal fence markers');
116
- const prefixed = plain.split('\n').filter((l) => l.includes('b'));
117
- assert.ok(prefixed.length > 1, 'nested list code wraps under narrow width');
118
- for (const line of prefixed) {
119
- assert.ok(stringWidth(line) <= outerWidth, `line fits outer width: "${line}"`);
120
- }
121
- });
122
-
123
- test('blockquote fenced code block respects narrowed width and has no fences', () => {
124
- const long = 'c'.repeat(36);
125
- const codeTok = lexFirst(`\`\`\`js\n${long}\n\`\`\`\n`, 'code');
126
- const quoteTok = { type: 'blockquote', tokens: [codeTok] };
127
- const outerWidth = 24;
128
- const out = formatToken(quoteTok, 0, null, null, outerWidth);
129
- const plain = stripAnsi(out);
130
- assert.ok(!plain.includes('```'), 'no literal fence markers');
131
- const lines = plain.split('\n').filter((l) => l.includes('c'));
132
- assert.ok(lines.length > 1, 'blockquote code wraps under narrow width');
133
- for (const line of lines) {
134
- assert.ok(stringWidth(line) <= outerWidth, `line fits outer width: "${line}"`);
135
- }
136
- });
137
-
138
- test('blockquote fenced code with blank line keeps quote prefix on every code row', () => {
139
- const codeTok = lexFirst('```js\na\n\nb\n```\n', 'code');
140
- const quoteTok = { type: 'blockquote', tokens: [codeTok] };
141
- const outerWidth = 40;
142
- const out = formatToken(quoteTok, 0, null, null, outerWidth);
143
- const plain = stripAnsi(out);
144
- assert.ok(!plain.includes('```'), 'no literal fence markers');
145
- const bar = '\u258e';
146
- const visibleLines = plain.split('\n').filter((l) => stringWidth(l) > 0);
147
- assert.ok(visibleLines.length >= 3, 'two body lines + blank padded row');
148
- for (const line of visibleLines) {
149
- assert.ok(line.startsWith(bar), `quote bar on every visible row: "${line}"`);
150
- assert.ok(stringWidth(line) <= outerWidth, `line fits outer width: "${line}"`);
151
- }
152
- const blankPadded = visibleLines.find((l) => !l.includes('a') && !l.includes('b'));
153
- assert.ok(blankPadded, 'background-padded blank code row is rendered');
154
- assert.ok(blankPadded.startsWith(bar), 'blank padded code row keeps quote prefix');
155
- });
156
-
157
- test('two-level nested list code block does not double-subtract list prefix width', () => {
158
- const long = 'd'.repeat(40);
159
- const codeTok = lexFirst(`\`\`\`js\n${long}\n\`\`\`\n`, 'code');
160
- const innerList = {
161
- type: 'list',
162
- ordered: false,
163
- items: [{ type: 'list_item', tokens: [codeTok] }],
164
- };
165
- const outerList = {
166
- type: 'list',
167
- ordered: false,
168
- items: [{ type: 'list_item', tokens: [innerList] }],
169
- };
170
- const outerWidth = 30;
171
- const singleLevelNested = formatToken(innerList, 2, null, null, outerWidth);
172
- const twoLevelNested = formatToken(outerList, 0, null, null, outerWidth);
173
- const codeLines = (plain) => plain.split('\n').filter((l) => l.includes('d'));
174
- const singleLines = codeLines(stripAnsi(singleLevelNested));
175
- const nestedLines = codeLines(stripAnsi(twoLevelNested));
176
- assert.equal(
177
- nestedLines.length,
178
- singleLines.length,
179
- 'nested list code wraps like one-level nested list at the same outer width',
180
- );
181
- assert.ok(nestedLines.length > 1, 'code is wrapped under narrow width');
182
- });
183
-
184
- test('js highlighting colors keyword, string, number distinctly', () => {
185
- const token = lexFirst('```js\nconst n = 42;\nlet s = "hi";\n```\n', 'code');
186
- const out = formatToken(token);
187
- assert.ok(out.includes(rgbSgr(theme.syntaxKeyword)), 'keyword colored');
188
- assert.ok(out.includes(rgbSgr(theme.syntaxNumber)), 'number colored');
189
- assert.ok(out.includes(rgbSgr(theme.syntaxString)), 'string colored');
190
- });
191
-
192
- test('highlight cache serves repeat block highlight without changing output', () => {
193
- const c = extraColorizers();
194
- const text = `const cacheProbe_${Date.now()} = 1;\nlet y = 2;\n`;
195
- const sizeBefore = _highlightCacheSizeForTests();
196
- const first = highlightCodeBlockToLines(text, 'js', c);
197
- assert.equal(_highlightCacheSizeForTests(), sizeBefore + 1, 'first highlight populates cache');
198
- const second = highlightCodeBlockToLines(text, 'js', c);
199
- assert.deepEqual(second, first);
200
- assert.equal(_highlightCacheSizeForTests(), sizeBefore + 1, 'cache hit does not grow entries');
201
- assert.ok(first[0].includes('38;2;'), 'highlighted ANSI present');
202
- });
203
-
204
- test('inline codespan is accent-colored with no background band', () => {
205
- const tokens = marked.lexer('`hello`');
206
- const codespan = tokens[0].tokens[0];
207
- assert.equal(codespan.type, 'codespan');
208
- const out = formatToken(codespan);
209
- // Inline code uses accent color only — no bg box behind the span (a tinted
210
- // band behind inline text reads as awkward against body prose).
211
- assert.ok(!out.includes('48;2;'), 'no background SGR');
212
- assert.ok(stripAnsi(out).includes('hello'));
213
- assert.ok(out.includes(rgbSgr(theme.mdCode)), 'inline code accent');
214
- });
215
-
216
- test('kotlin fence resolves and highlights', () => {
217
- const token = lexFirst('```kotlin\nfun main() {}\n```\n', 'code');
218
- const out = formatToken(token);
219
- assert.ok(out.includes(rgbSgr(theme.syntaxKeyword)) || out.includes(rgbSgr(theme.mdCodeBlock)));
220
- assert.ok(stripAnsi(out).includes('fun main'));
221
- });
222
-
223
- test('typescript fence uses typescript highlighter', () => {
224
- const token = lexFirst('```ts\nconst x: number = 1;\n```\n', 'code');
225
- const out = formatToken(token);
226
- assert.ok(out.includes('38;2;'));
227
- assert.ok(stripAnsi(out).includes('const x'));
228
- });
229
-
230
- test('diff fenced block colors add/remove/hunk/header separately', () => {
231
- const diff = [
232
- '```diff',
233
- 'diff --git a/x b/x',
234
- '--- a/x',
235
- '+++ b/x',
236
- '@@ -1,2 +1,2 @@',
237
- '-old line',
238
- '+new line',
239
- ' context line',
240
- '```',
241
- '',
242
- ].join('\n');
243
- const token = lexFirst(diff, 'code');
244
- const out = formatToken(token);
245
- const added = rgbSgr(theme.mdDiffAdded);
246
- const removed = rgbSgr(theme.mdDiffRemoved);
247
- const hunk = rgbSgr(theme.mdDiffHunk);
248
- const header = rgbSgr(theme.mdDiffHeader);
249
- assert.ok(out.includes(added), 'added line color present');
250
- assert.ok(out.includes(removed), 'removed line color present');
251
- assert.ok(out.includes(hunk), 'hunk line color present');
252
- assert.ok(out.includes(header), 'header line color present');
253
- assert.equal(new Set([added, removed, hunk, header]).size, 4);
254
- assert.ok(out.indexOf('+++ b/x') !== -1);
255
- });
256
-
257
- test('plus/minus file headers are not treated as add/remove', () => {
258
- const diff = '```patch\n--- a/f\n+++ b/f\n@@ -0,0 +1 @@\n+x\n```\n';
259
- const token = lexFirst(diff, 'code');
260
- const out = formatToken(token);
261
- const headerSgr = rgbSgr(theme.mdDiffHeader);
262
- const addedSgr = rgbSgr(theme.mdDiffAdded);
263
- const idxHeader = out.indexOf('+++ b/f');
264
- const before = out.slice(0, idxHeader);
265
- const lastHeader = before.lastIndexOf(headerSgr);
266
- const lastAdded = before.lastIndexOf(addedSgr);
267
- assert.ok(lastHeader > lastAdded, 'plus-plus-plus header uses header color, not added');
268
- });
269
-
270
- test('bare unified diff (no lang) is detected and colored', () => {
271
- const token = lexFirst('```\n@@ -1 +1 @@\n-a\n+b\n```\n', 'code');
272
- const out = formatToken(token);
273
- assert.ok(out.includes(rgbSgr(theme.mdDiffHunk)), 'hunk colored on bare diff');
274
- assert.ok(out.includes(rgbSgr(theme.mdDiffAdded)), 'add colored on bare diff');
275
- });
276
-
277
- test('unknown language falls back to flat code block color', () => {
278
- const token = lexFirst('```foobarlang\nsome text\n```\n', 'code');
279
- const out = formatToken(token);
280
- assert.ok(out.includes(rgbSgr(theme.mdCodeBlock)), 'flat code-block color used');
281
- assert.ok(stripAnsi(out).includes('some text'), 'body is flush-left');
282
- });
283
-
284
- test('inline link emits OSC 8 hyperlink with styled label (URL hidden)', () => {
285
- const token = lexFirst('[label](https://example.com)', 'paragraph');
286
- const out = formatToken(token);
287
- // OSC 8 wraps the visible label; the raw URL lives in the escape, not as
288
- // on-screen text, so stripAnsi keeps only the label.
289
- assert.ok(out.includes('\x1b]8;;https://example.com\x07'), 'OSC 8 link target present');
290
- assert.ok(out.includes('\x1b]8;;\x07'), 'OSC 8 close present');
291
- assert.ok(stripAnsi(out).includes('label'), 'link label text visible');
292
- assert.ok(out.includes(rgbSgr(theme.mdLinkText)), 'link label colored');
293
- });
294
-
295
- test('bare-url link emits OSC 8 with the URL as visible clickable text', () => {
296
- const token = lexFirst('<https://example.com>', 'paragraph');
297
- const out = formatToken(token);
298
- assert.ok(out.includes('\x1b]8;;https://example.com\x07'), 'OSC 8 link target present');
299
- assert.ok(stripAnsi(out).includes('https://example.com'), 'URL visible as text');
300
- assert.ok(out.includes(rgbSgr(theme.mdLink)), 'link URL colored');
301
- });
302
-
303
- test('strong and em use distinct mdStrong / mdEmph colors', () => {
304
- const strongTok = lexFirst('**bold**', 'paragraph');
305
- const emTok = lexFirst('*ital*', 'paragraph');
306
- const strongOut = formatToken(strongTok);
307
- const emOut = formatToken(emTok);
308
- // Bold/italic carry weight/style only — NO color tint (codex/claude-code
309
- // convention; a colored strong/em clashes per-theme and reads too loud).
310
- assert.ok(strongOut.includes('\x1b[1m'), 'strong is bold');
311
- assert.ok(emOut.includes('\x1b[3m'), 'em is italic');
312
- assert.ok(!strongOut.includes(rgbSgr(theme.mdStrong)), 'strong is not color-tinted');
313
- assert.ok(!emOut.includes(rgbSgr(theme.mdEmph)), 'em is not color-tinted');
314
- });
315
-
316
- test('h1 heading is bold, italic, and underlined', () => {
317
- const token = lexFirst('# Title', 'heading');
318
- const out = formatToken(token);
319
- assert.ok(out.includes('\x1b[1m'), 'h1 is bold');
320
- assert.ok(out.includes('\x1b[3m'), 'h1 is italic');
321
- assert.ok(out.includes('\x1b[4m'), 'h1 is underlined');
322
- });
323
-
324
- test('ordered list markers follow nesting depth (1. / a. / i.)', () => {
325
- const md = [
326
- '1. one',
327
- ' 1. two',
328
- ' 1. three',
329
- ' 1. four',
330
- ].join('\n');
331
- const tokens = marked.lexer(md);
332
- const plain = stripAnsi(tokens.map((t) => formatToken(t)).join(''));
333
- assert.ok(plain.includes('1. one'), 'depth 0 arabic');
334
- assert.ok(plain.includes('1. two'), 'depth 1 arabic');
335
- assert.ok(plain.includes('a. three'), 'depth 2 lowercase letter');
336
- assert.ok(plain.includes('i. four'), 'depth 3 lowercase roman');
337
- });
338
-
339
- test('every palette defines the full extended key set', () => {
340
- const required = [
341
- 'mdCodeBlockBorder', 'mdCodeBlockBg', 'mdCodeSpanBg', 'mdLink', 'mdLinkText', 'mdStrong', 'mdEmph',
342
- 'mdDiffAdded', 'mdDiffRemoved', 'mdDiffHunk', 'mdDiffHeader', 'mdDiffContext',
343
- 'mdDiffAddedBg', 'mdDiffRemovedBg',
344
- 'syntaxComment', 'syntaxKeyword', 'syntaxFunction', 'syntaxVariable',
345
- 'syntaxString', 'syntaxNumber', 'syntaxType', 'syntaxOperator', 'syntaxPunctuation',
346
- ];
347
- for (const id of ['basic', 'indigo', 'warm', 'light', 'teal', 'onedark', 'tokyonight', 'kanagawa', 'catppuccin', 'dracula', 'rosepine', 'nord', 'gruvbox', 'everforest']) {
348
- setThemeSetting(id, { persist: false });
349
- for (const key of required) {
350
- assert.ok(/^rgb\(\d+,\s*\d+,\s*\d+\)$/.test(String(theme[key])), `${id}.${key} is an rgb() string`);
351
- }
352
- }
353
- setThemeSetting('basic', { persist: false });
354
- });
@@ -1,108 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import stripAnsi from 'strip-ansi';
4
- import { assistantBodyWidth } from './table-layout.mjs';
5
- import { renderTokenAnsiSegments, lexMarkdown, hasMarkdownSyntax } from './render-ansi.mjs';
6
- import { setThemeSetting } from '../theme.mjs';
7
-
8
- setThemeSetting('basic', { persist: false });
9
-
10
- // Mirrors the streaming suffix the <Markdown trimPartialFences> path renders.
11
- const STREAMING = '```js\nconst x = 1;\n`';
12
-
13
- function codeBlockVisibleLines(segments) {
14
- const codeSeg = segments.find((s) => s.type === 'ansi' && s.token?.type === 'code');
15
- assert.ok(codeSeg, 'a fenced code segment is rendered');
16
- return stripAnsi(codeSeg.ansi).split('\n');
17
- }
18
-
19
- test('streaming render path drops the partial trailing backtick body line', () => {
20
- const segments = renderTokenAnsiSegments(STREAMING, { trimPartialFences: true });
21
- const lines = codeBlockVisibleLines(segments);
22
- const bodyLines = lines.filter((l) => !/^js$/.test(l.trim()));
23
- for (const l of bodyLines) {
24
- assert.notEqual(l.trim(), '`', 'no body line is a lone partial fence');
25
- }
26
- // The real code body is still present.
27
- assert.ok(lines.some((l) => l.includes('const x = 1;')), 'code body preserved');
28
- });
29
-
30
- test('without trimPartialFences the partial backtick survives as a body line', () => {
31
- // Proves the trim flag is what removes it on the render path (not formatToken).
32
- const segments = renderTokenAnsiSegments(STREAMING, { trimPartialFences: false });
33
- const lines = codeBlockVisibleLines(segments);
34
- const bodyLines = lines.filter((l) => !/^js$/.test(l.trim()));
35
- assert.ok(
36
- bodyLines.some((l) => l.trim() === '`'),
37
- 'untrimmed path keeps the partial fence as a body line',
38
- );
39
- });
40
-
41
- test('trimPartialFences tokens are not cached (stable text unaffected)', () => {
42
- const stable = '```js\nconst y = 2;\n```\n';
43
- const a = lexMarkdown(stable, { trimPartialFences: false });
44
- const b = lexMarkdown(stable, { trimPartialFences: false });
45
- assert.strictEqual(a, b, 'stable content returns the cached token array');
46
- // Streaming lex must be a fresh (non-cached) array.
47
- const c = lexMarkdown(STREAMING, { trimPartialFences: true });
48
- const d = lexMarkdown(STREAMING, { trimPartialFences: true });
49
- assert.notStrictEqual(c, d, 'streaming lex is never cached');
50
- });
51
-
52
- test('closed code block renders lang label and body without fence markers', () => {
53
- const segments = renderTokenAnsiSegments('```js\nconst z = 3;\n```\n', { trimPartialFences: true });
54
- const lines = codeBlockVisibleLines(segments);
55
- assert.ok(lines.some((l) => l.includes('const z = 3;')), 'body present');
56
- assert.ok(lines.some((l) => l.trim() === 'js'), 'language label present');
57
- assert.ok(!lines.some((l) => l.includes('```')), 'no literal fence markers');
58
- });
59
-
60
- test('renderTokenAnsiSegments width matches assistant body width for code pre-wrap', () => {
61
- const long = 'z'.repeat(60);
62
- const md = `\`\`\`js\n${long}\n\`\`\``;
63
- const bodyWidth = assistantBodyWidth(40);
64
- const atBody = renderTokenAnsiSegments(md, { width: bodyWidth });
65
- const atDefault = renderTokenAnsiSegments(md, { width: 80 });
66
- const bodyLines = (seg) => stripAnsi(seg.find((s) => s.token?.type === 'code').ansi)
67
- .split('\n')
68
- .filter((l) => l.startsWith(' '));
69
- assert.ok(bodyLines(atBody).length > bodyLines(atDefault).length,
70
- 'narrow body width produces more wrapped code rows than width 80');
71
- });
72
-
73
- const PLAIN_PREFIX = 'x'.repeat(501);
74
-
75
- test('hasMarkdownSyntax detects strong markers after a 500+ char plain prefix', () => {
76
- assert.ok(hasMarkdownSyntax(`${PLAIN_PREFIX}**late bold**`));
77
- });
78
- test('hasMarkdownSyntax detects inline code after a 500+ char plain prefix', () => {
79
- assert.ok(hasMarkdownSyntax(`${PLAIN_PREFIX}\`late code\``));
80
- });
81
-
82
- test('render path parses fenced code after a 500+ char plain prefix', () => {
83
- const input = `${PLAIN_PREFIX}\n\`\`\`js\nconst late = true;\n\`\`\``;
84
- const tokens = lexMarkdown(input);
85
- assert.ok(
86
- tokens.some((t) => t.type === 'code' && String(t.text).includes('const late = true;')),
87
- 'lexer emits a code token with the fenced body',
88
- );
89
- const segments = renderTokenAnsiSegments(input);
90
- const lines = codeBlockVisibleLines(segments);
91
- assert.ok(lines.some((l) => l.includes('const late = true;')), 'code block body is rendered');
92
- const visible = segments.map((s) => stripAnsi(s.ansi ?? '')).join('\n');
93
- assert.ok(!visible.includes(`${PLAIN_PREFIX}\`\`\`js`), 'prefix is not glued to raw fence markers');
94
- });
95
-
96
- test('render path parses strong after a 500+ char plain prefix (no raw **)', () => {
97
- const segments = renderTokenAnsiSegments(`${PLAIN_PREFIX}**rendered**`);
98
- const visible = segments.map((s) => stripAnsi(s.ansi ?? '')).join('');
99
- assert.ok(visible.includes('rendered'), 'bold text is present');
100
- assert.ok(!visible.includes('**'), 'markdown strong markers are not leaked');
101
- });
102
-
103
- test('render path parses inline code after a 500+ char plain prefix (no raw backticks)', () => {
104
- const segments = renderTokenAnsiSegments(`${PLAIN_PREFIX}\`snippet\``);
105
- const visible = segments.map((s) => stripAnsi(s.ansi ?? '')).join('');
106
- assert.ok(visible.includes('snippet'), 'codespan text is present');
107
- assert.ok(!visible.includes('`'), 'inline code backticks are not leaked');
108
- });
@@ -1,26 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { marked } from 'marked';
4
- import { trimPartialClosingFences } from './stream-fence.mjs';
5
-
6
- test('trims a partial closing fence (single backtick) from open code block', () => {
7
- const tokens = marked.lexer('```js\nconst x = 1;\n`');
8
- trimPartialClosingFences(tokens);
9
- const code = tokens.find((t) => t.type === 'code');
10
- assert.ok(code, 'code token exists');
11
- assert.ok(!code.text.endsWith('`'), 'partial fence trimmed from text');
12
- assert.ok(code.text.includes('const x = 1;'), 'real body preserved');
13
- });
14
-
15
- test('leaves a complete closing fence intact', () => {
16
- const tokens = marked.lexer('```js\nconst x = 1;\n```\n');
17
- const code = tokens.find((t) => t.type === 'code');
18
- const before = code.text;
19
- trimPartialClosingFences(tokens);
20
- assert.equal(code.text, before, 'closed block unchanged');
21
- });
22
-
23
- test('no-op on non-code trailing token', () => {
24
- const tokens = marked.lexer('hello world\n');
25
- assert.doesNotThrow(() => trimPartialClosingFences(tokens));
26
- });
@@ -1,70 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { assistantBodyWidth } from './table-layout.mjs';
4
- import {
5
- measureMarkdownRenderedRows,
6
- measureStreamingMarkdownRenderedRows,
7
- } from './measure-rendered-rows.mjs';
8
- import { resolveStreamingMarkdownParts, streamingLayoutText } from './streaming-markdown.mjs';
9
- import { setThemeSetting } from '../theme.mjs';
10
-
11
- setThemeSetting('basic', { persist: false });
12
-
13
- function measureStreamingLayoutFromParts(text, columns, streamKey) {
14
- const parts = resolveStreamingMarkdownParts(text, streamKey);
15
- if (parts.plain) {
16
- return measureMarkdownRenderedRows(text, columns);
17
- }
18
- let rows = 0;
19
- let childCount = 0;
20
- if (parts.stablePrefix) {
21
- rows += measureMarkdownRenderedRows(parts.stablePrefix, columns);
22
- childCount += 1;
23
- }
24
- if (parts.unstableSuffix) {
25
- rows += measureMarkdownRenderedRows(parts.unstableForRender, columns, { trimPartialFences: true });
26
- childCount += 1;
27
- }
28
- if (childCount === 2) rows += 1;
29
- return Math.max(1, rows);
30
- }
31
-
32
- test('streaming row estimate matches shared layout parts at body width', () => {
33
- const columns = 40;
34
- const bodyWidth = assistantBodyWidth(columns);
35
- const cases = [
36
- { key: 'bold-wrap', text: `**${'m'.repeat(bodyWidth + 12)}` },
37
- { key: 'partial-fence', text: `\`\`\`js\n${'w'.repeat(bodyWidth + 6)}\n\`` },
38
- ];
39
- for (const { key, text } of cases) {
40
- const estimate = measureStreamingMarkdownRenderedRows(text, columns, key);
41
- const layout = measureStreamingLayoutFromParts(text, columns, key);
42
- assert.strictEqual(estimate, layout, `${key}: estimate must match render layout parts`);
43
- }
44
- });
45
-
46
- test('partial code fence streaming measure uses trimPartialFences on unstable suffix', () => {
47
- const columns = 48;
48
- const bodyWidth = assistantBodyWidth(columns);
49
- const streamKey = 'test-stream-fence';
50
- const text = `\`\`\`js\n${'n'.repeat(bodyWidth + 8)}\n\``;
51
- const estimate = measureStreamingMarkdownRenderedRows(text, columns, streamKey);
52
- const parts = resolveStreamingMarkdownParts(text, streamKey);
53
- const manual = measureMarkdownRenderedRows(parts.unstableForRender, columns, { trimPartialFences: true });
54
- assert.strictEqual(estimate, Math.max(1, manual));
55
- });
56
-
57
- test('leading blank lines do not create an empty stable child or extra gap row', () => {
58
- const columns = 40;
59
- const streamKey = 'test-leading-blank';
60
- const raw = '\n\n**streaming body**';
61
- const trimmed = streamingLayoutText(raw);
62
- const parts = resolveStreamingMarkdownParts(raw, streamKey);
63
- assert.strictEqual(parts.stablePrefix, '', 'leading newlines must not promote whitespace-only stable');
64
- assert.ok(parts.unstableSuffix.includes('streaming body'));
65
- const estimate = measureStreamingMarkdownRenderedRows(raw, columns, streamKey);
66
- const trimmedEstimate = measureStreamingMarkdownRenderedRows(trimmed, columns, streamKey);
67
- assert.strictEqual(estimate, trimmedEstimate, 'raw vs trimmed leading newlines must match');
68
- const layout = measureStreamingLayoutFromParts(raw, columns, streamKey);
69
- assert.strictEqual(estimate, layout);
70
- });
@@ -1,52 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, rmSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { tmpdir } from 'node:os';
6
- import {
7
- appendPromptHistory,
8
- buildMergedPromptHistory,
9
- loadPromptHistory,
10
- pushSessionPromptHistory,
11
- promptHistoryKey,
12
- } from './prompt-history-store.mjs';
13
-
14
- test('promptHistoryKey normalizes whitespace', () => {
15
- assert.equal(promptHistoryKey(' hello world '), 'hello world');
16
- assert.equal(promptHistoryKey(' '), '');
17
- });
18
-
19
- test('buildMergedPromptHistory prefers session order and dedupes', () => {
20
- const merged = buildMergedPromptHistory(
21
- ['newest', 'older'],
22
- ['stale', 'newest', 'from-disk'],
23
- 50,
24
- );
25
- assert.deepEqual(merged, ['newest', 'older', 'from-disk', 'stale']);
26
- });
27
-
28
- test('appendPromptHistory persists per cwd across reload', () => {
29
- const dataDir = mkdtempSync(join(tmpdir(), 'mixdog-prompt-history-'));
30
- const previous = process.env.MIXDOG_DATA_DIR;
31
- process.env.MIXDOG_DATA_DIR = dataDir;
32
- try {
33
- const cwd = join(dataDir, 'project-a');
34
- assert.deepEqual(appendPromptHistory(cwd, 'first prompt'), ['first prompt']);
35
- assert.deepEqual(appendPromptHistory(cwd, 'second prompt'), ['first prompt', 'second prompt']);
36
- assert.deepEqual(appendPromptHistory(cwd, 'first prompt'), ['second prompt', 'first prompt']);
37
- const loaded = loadPromptHistory(cwd);
38
- assert.deepEqual(loaded, ['second prompt', 'first prompt']);
39
- } finally {
40
- if (previous === undefined) delete process.env.MIXDOG_DATA_DIR;
41
- else process.env.MIXDOG_DATA_DIR = previous;
42
- rmSync(dataDir, { recursive: true, force: true });
43
- }
44
- });
45
-
46
- test('pushSessionPromptHistory is cwd-session newest-first with dedupe', () => {
47
- let session = pushSessionPromptHistory([], 'alpha');
48
- session = pushSessionPromptHistory(session, 'beta');
49
- session = pushSessionPromptHistory(session, 'alpha');
50
- assert.deepEqual(session, ['alpha', 'beta']);
51
- });
52
-