mixdog 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,119 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import stringWidth from 'string-width';
4
- import {
5
- shouldFoldPastedText,
6
- formatPastedTextRef,
7
- pastedTextReferenceIds,
8
- expandPastedTextTokens,
9
- } from './paste-attachments.mjs';
10
- import { caretPosition } from './input-editing.mjs';
11
-
12
- // --- Part 1: pasted-text token folding ----------------------------------
13
-
14
- test('shouldFoldPastedText fires on >=3 lines or >200 chars', () => {
15
- assert.equal(shouldFoldPastedText('a\nb\nc'), true); // 3 lines
16
- assert.equal(shouldFoldPastedText('a\nb'), false); // 2 lines, short
17
- assert.equal(shouldFoldPastedText('x'.repeat(201)), true); // > 200 chars
18
- assert.equal(shouldFoldPastedText('x'.repeat(200)), false); // == 200, not over
19
- assert.equal(shouldFoldPastedText(''), false);
20
- });
21
-
22
- test('token round-trips through format + expand', () => {
23
- const original = 'line1\nline2\nline3';
24
- const token = formatPastedTextRef(7, original);
25
- assert.equal(token, '[Pasted text #7 +3 lines]');
26
- const refs = pastedTextReferenceIds(`before ${token} after`);
27
- assert.deepEqual([...refs], [7]);
28
- const map = { 7: { id: 7, text: original } };
29
- assert.equal(
30
- expandPastedTextTokens(`before ${token} after`, map),
31
- `before ${original} after`,
32
- );
33
- });
34
-
35
- test('broken / partially-deleted tokens do not expand', () => {
36
- const map = { 3: { id: 3, text: 'HIDDEN' } };
37
- // Missing the "+N lines" tail — not a valid token, left verbatim.
38
- assert.equal(expandPastedTextTokens('[Pasted text #3]', map), '[Pasted text #3]');
39
- // Truncated bracket.
40
- assert.equal(expandPastedTextTokens('[Pasted text #3 +2 lines', map), '[Pasted text #3 +2 lines');
41
- // Unknown id stays as-is.
42
- assert.equal(expandPastedTextTokens('[Pasted text #9 +2 lines]', map), '[Pasted text #9 +2 lines]');
43
- });
44
-
45
- // --- Part 2: caret row math vs ink wrap-ansi (hard, wordWrap:false) -------
46
-
47
- // Faithful re-implementation of wrap-ansi's wrapWord loop for a single
48
- // space-free line (the prompt caret math walks graphemes the same way). ink
49
- // renders <Text wrap="hard"> which calls wrapAnsi(text, w, {hard,wordWrap:false}).
50
- // For a run with no spaces the whole line goes through wrapWord, so the visual
51
- // row count is what this produces.
52
- function inkWrapWordRows(line, columns) {
53
- const chars = Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(line), (s) => s.segment);
54
- let rows = 1;
55
- let visible = 0;
56
- for (let i = 0; i < chars.length; i += 1) {
57
- const cw = stringWidth(chars[i]);
58
- if (visible + cw <= columns) {
59
- // stays on current row
60
- } else {
61
- rows += 1;
62
- visible = 0;
63
- }
64
- visible += cw;
65
- if (visible === columns && i < chars.length - 1) {
66
- rows += 1;
67
- visible = 0;
68
- }
69
- }
70
- return rows;
71
- }
72
-
73
- // caretPosition row for the END of the string == (visual row count - 1).
74
- function caretRowsForWholeString(text, w) {
75
- return caretPosition(text, text.length, w).row + 1;
76
- }
77
-
78
- test('caret rows agree with ink wrap for wide-char lines at odd widths', () => {
79
- // A width where a width-2 glyph cannot sit flush on the last column exposes
80
- // the mismatch: caretPosition must NOT advance an extra row when the char is
81
- // simply pushed to the next line (ink does the same — it never leaves a
82
- // half-column then over-counts).
83
- const cases = [
84
- { text: '가'.repeat(30), w: 41 },
85
- { text: '가'.repeat(30), w: 40 },
86
- { text: '가나다'.repeat(10) + 'ascii', w: 13 },
87
- { text: '한글'.repeat(8), w: 7 },
88
- { text: '가'.repeat(5), w: 2 },
89
- ];
90
- for (const { text, w } of cases) {
91
- assert.equal(
92
- caretRowsForWholeString(text, w),
93
- inkWrapWordRows(text, w),
94
- `width ${w} on ${JSON.stringify(text.slice(0, 6))}…`,
95
- );
96
- }
97
- });
98
-
99
- // Cursor exactly at a soft-wrap boundary MID-text: ink renders the following
100
- // glyph on the next row, so the caret must report {row N+1, col 0}, never
101
- // {row N, col == w} (which would sit outside the box).
102
- test('caret at mid-text wrap boundary rolls to next row (col 0)', () => {
103
- const value = '가나다라마'; // 5 wide glyphs, width 2 each
104
- const w = 4; // 2 glyphs per row
105
- // offset 2 = after '나' (each glyph is ONE UTF-16 unit) = end of visual
106
- // row 0; content ('다라마') follows, so the caret rolls to the next row.
107
- const at = caretPosition(value, 2, w);
108
- assert.deepEqual(at, { row: 1, col: 0 }, 'boundary caret must sit on next row');
109
- // offset 4 = after '라' = end of visual row 1 (rows 0-1 are full).
110
- assert.deepEqual(caretPosition(value, 4, w), { row: 2, col: 0 }, 'second boundary rolls too');
111
- // Cursor at the very end of a FLUSH-ending text ('가나다라' fills rows 0-1
112
- // exactly) with a trailing cell (PromptInput appends one) also rolls.
113
- const flush = '가나다라';
114
- const end = caretPosition(flush, flush.length, w, true);
115
- assert.deepEqual(end, { row: 2, col: 0 }, 'flush end with trailing cell rolls to next row');
116
- // Non-flush end ('마' leaves col 2) must NOT roll even with a trailing cell.
117
- const nonFlush = caretPosition(value, value.length, w, true);
118
- assert.deepEqual(nonFlush, { row: 2, col: 2 }, 'non-flush end stays on its row');
119
- });
@@ -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
-
@@ -1,159 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- remapCanonicalStatuslineTruecolor,
5
- normalizeStatuslineAnsi,
6
- statuslineFooterCacheKey,
7
- statuslineFooterIdentityChanged,
8
- isResetStatsState,
9
- STATUSLINE_CANONICAL_TRUECOLOR,
10
- } from './statusline-ansi-bridge.mjs';
11
- import { theme, setThemeSetting } from './theme.mjs';
12
-
13
- function sgr38(r, g, b) {
14
- return `\x1b[38;2;${r};${g};${b}m`;
15
- }
16
-
17
- function statusColorsFromTheme() {
18
- const ansiRgb = (value, fallback) => {
19
- const match = /^rgb\((\d+),(\d+),(\d+)\)$/.exec(String(value || '').replace(/\s+/g, ''));
20
- if (!match) return fallback;
21
- return sgr38(match[1], match[2], match[3]);
22
- };
23
- return {
24
- STATUS: ansiRgb(theme.statusText, sgr38(198, 198, 198)),
25
- SUBTLE: ansiRgb(theme.statusSubtle, sgr38(136, 136, 136)),
26
- SUCCESS: ansiRgb(theme.success, sgr38(0, 170, 75)),
27
- WARNING: ansiRgb(theme.warning, sgr38(255, 193, 7)),
28
- ERROR: ansiRgb(theme.error, sgr38(220, 70, 88)),
29
- };
30
- }
31
-
32
- test('remaps canonical truecolor sequences from ui/statusline.mjs', () => {
33
- const colors = statusColorsFromTheme();
34
- const subtle = sgr38(...STATUSLINE_CANONICAL_TRUECOLOR.subtle);
35
- const success = sgr38(...STATUSLINE_CANONICAL_TRUECOLOR.success);
36
- const raw = `${subtle}│\x1b[0m ${success}42%`;
37
- const out = remapCanonicalStatuslineTruecolor(raw, colors);
38
- assert.ok(!out.includes('38;2;136;136;136'), 'subtle canonical RGB should be replaced');
39
- assert.ok(!out.includes('38;2;0;170;75'), 'success canonical RGB should be replaced');
40
- assert.ok(out.includes(colors.SUBTLE));
41
- assert.ok(out.includes(colors.SUCCESS));
42
- });
43
-
44
- test('normalizeStatuslineAnsi maps bold and legacy SGR plus truecolor', () => {
45
- const colors = statusColorsFromTheme();
46
- const raw = `\x1b[1mmodel\x1b[0m \x1b[33m5H\x1b[0m ${sgr38(...STATUSLINE_CANONICAL_TRUECOLOR.error)}quota`;
47
- const out = normalizeStatuslineAnsi(raw, colors);
48
- assert.ok(out.includes(colors.STATUS));
49
- assert.ok(out.includes(colors.WARNING));
50
- assert.ok(out.includes(colors.ERROR));
51
- assert.ok(!out.includes('\x1b[33m'));
52
- assert.ok(!out.includes('38;2;220;70;88'));
53
- });
54
-
55
- test('normalizeStatuslineAnsi preserves leading diamond glyph', () => {
56
- const colors = statusColorsFromTheme();
57
- const subtle = sgr38(...STATUSLINE_CANONICAL_TRUECOLOR.subtle);
58
- const raw = `${subtle}◆\x1b[0m \x1b[1mgpt-4o\x1b[0m`;
59
- const out = normalizeStatuslineAnsi(raw, colors);
60
- assert.ok(out.includes('◆'), 'visible ◆ must remain');
61
- assert.ok(out.includes('gpt-4o'));
62
- assert.ok(out.indexOf('◆') < out.indexOf('gpt-4o'));
63
- });
64
-
65
- test('retimes bare values after reset without clobbering explicit segment colors', () => {
66
- const colors = statusColorsFromTheme();
67
- const subtle = colors.SUBTLE;
68
- const success = colors.SUCCESS;
69
- const warning = colors.WARNING;
70
- const reset = '\x1b[0m';
71
- const creditSeg = `${subtle}Credit${reset} $12.34`;
72
- const outCredit = normalizeStatuslineAnsi(creditSeg, colors, { reset });
73
- assert.ok(outCredit.includes('$12.34'));
74
- const moneyIdx = outCredit.indexOf('$12.34');
75
- assert.ok(moneyIdx > 0);
76
- assert.ok(outCredit.slice(0, moneyIdx).includes(colors.STATUS), 'bare credit amount uses status text');
77
-
78
- const usedLimit = `${subtle}5H${reset} $3.00/$10.00`;
79
- const outUsed = normalizeStatuslineAnsi(usedLimit, colors, { reset });
80
- assert.ok(outUsed.includes('$3.00/$10.00'));
81
- assert.ok(outUsed.slice(0, outUsed.indexOf('$3.00')).includes(colors.STATUS));
82
-
83
- const colored = `${subtle}5H${reset} ${warning}est $5.00${reset}`;
84
- const outColored = normalizeStatuslineAnsi(colored, colors, { reset });
85
- assert.ok(outColored.includes(warning), 'warning color on value must remain');
86
- const estIdx = outColored.indexOf('est');
87
- assert.ok(estIdx > 0);
88
- assert.ok(outColored.slice(0, estIdx).includes(warning));
89
- });
90
-
91
- test('statuslineFooterCacheKey changes when session or route identity changes', () => {
92
- const a = statuslineFooterCacheKey({ sessionId: 's1', provider: 'p', model: 'm1' });
93
- const b = statuslineFooterCacheKey({ sessionId: 's2', provider: 'p', model: 'm1' });
94
- const c = statuslineFooterCacheKey({ sessionId: 's1', provider: 'p', model: 'm2' });
95
- assert.notEqual(a, b);
96
- assert.notEqual(a, c);
97
- });
98
-
99
- test('statusline footer identity tracks context display boundary fields', () => {
100
- const base = {
101
- sessionId: 's1',
102
- provider: 'p',
103
- model: 'm',
104
- contextWindow: 1_000_000,
105
- displayContextWindow: 900_000,
106
- rawContextWindow: 1_000_000,
107
- compactBoundaryTokens: 900_000,
108
- autoCompactTokenLimit: 810_000,
109
- stats: { currentEstimatedContextTokens: 10_000 },
110
- };
111
- const changedDisplay = { ...base, displayContextWindow: 800_000 };
112
- const changedBoundary = { ...base, compactBoundaryTokens: 800_000 };
113
- const changedTrigger = { ...base, autoCompactTokenLimit: 720_000 };
114
-
115
- assert.notEqual(statuslineFooterCacheKey(base), statuslineFooterCacheKey(changedDisplay));
116
- assert.notEqual(statuslineFooterCacheKey(base), statuslineFooterCacheKey(changedBoundary));
117
- assert.notEqual(statuslineFooterCacheKey(base), statuslineFooterCacheKey(changedTrigger));
118
- assert.equal(statuslineFooterIdentityChanged(changedDisplay, base), true);
119
- assert.equal(statuslineFooterIdentityChanged(changedBoundary, base), true);
120
- assert.equal(statuslineFooterIdentityChanged(changedTrigger, base), true);
121
- });
122
-
123
- test('stats reset transition is footer identity change (cache must not reuse on /theme)', () => {
124
- const activeStats = {
125
- currentContextTokens: 1200,
126
- inputTokens: 800,
127
- turns: 2,
128
- };
129
- const resetStats = {
130
- currentContextTokens: 0,
131
- currentEstimatedContextTokens: 0,
132
- inputTokens: 0,
133
- latestInputTokens: 0,
134
- promptTokens: 0,
135
- turns: 0,
136
- };
137
- assert.equal(isResetStatsState(resetStats), true);
138
- assert.equal(isResetStatsState(activeStats), false);
139
- const sameRoute = { sessionId: 's1', provider: 'p', model: 'm', stats: resetStats };
140
- const lastRoute = { sessionId: 's1', provider: 'p', model: 'm', stats: activeStats };
141
- assert.equal(statuslineFooterCacheKey(sameRoute), statuslineFooterCacheKey(lastRoute));
142
- assert.equal(statuslineFooterIdentityChanged(sameRoute, lastRoute), true);
143
- });
144
-
145
- test('theme switch changes normalized output when raw line is canonical', () => {
146
- setThemeSetting('basic', { persist: false });
147
- const colorsA = statusColorsFromTheme();
148
- const raw = `${sgr38(...STATUSLINE_CANONICAL_TRUECOLOR.success)}▓▓\x1b[0m ${sgr38(...STATUSLINE_CANONICAL_TRUECOLOR.subtle)}░░`;
149
- const tonedA = normalizeStatuslineAnsi(raw, colorsA);
150
-
151
- setThemeSetting('teal', { persist: false });
152
- const colorsB = statusColorsFromTheme();
153
- const tonedB = normalizeStatuslineAnsi(raw, colorsB);
154
-
155
- assert.notEqual(tonedA, tonedB);
156
- assert.ok(tonedB.includes(colorsB.SUCCESS));
157
- assert.ok(tonedB.includes(colorsB.SUBTLE));
158
- setThemeSetting('basic', { persist: false });
159
- });
@@ -1,111 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- formatHookDenialDetail,
5
- hasUsefulFailedToolResultBody,
6
- isFullyFailedToolBatch,
7
- isHookApprovalDenialToolItem,
8
- isHookApprovalDenialToolResult,
9
- shouldSuppressFullyFailedToolItem,
10
- } from './transcript-tool-failures.mjs';
11
-
12
- const hookDeniedItem = {
13
- kind: 'tool',
14
- name: 'shell',
15
- args: { command: 'rm -rf /' },
16
- result: 'Error: tool "shell" denied by hook: approval required but no approval UI is available (policy)',
17
- isError: true,
18
- count: 1,
19
- completedCount: 1,
20
- };
21
-
22
- const noisyFailedItem = {
23
- kind: 'tool',
24
- name: 'grep',
25
- args: { pattern: 'foo' },
26
- result: 'no matches',
27
- isError: true,
28
- count: 1,
29
- completedCount: 1,
30
- };
31
-
32
- const emptyFailedItem = {
33
- kind: 'tool',
34
- name: 'grep',
35
- args: { pattern: 'foo' },
36
- result: null,
37
- rawResult: null,
38
- isError: true,
39
- count: 1,
40
- completedCount: 1,
41
- };
42
-
43
- const failedTaskJsonStringArgs = {
44
- kind: 'tool',
45
- name: 'shell',
46
- args: '{"task_id":"t1","status":"failed","error":"boom"}',
47
- result: null,
48
- rawResult: null,
49
- isError: true,
50
- count: 1,
51
- completedCount: 1,
52
- };
53
-
54
- const failedTaskInputWrapperArgs = {
55
- kind: 'tool',
56
- name: 'shell',
57
- args: { input: { task_id: 't2', status: 'timeout' } },
58
- result: null,
59
- rawResult: null,
60
- isError: true,
61
- count: 1,
62
- completedCount: 1,
63
- };
64
-
65
- test('detects hook denial result text', () => {
66
- assert.equal(
67
- isHookApprovalDenialToolResult('Error: tool "read" denied by hook: blocked'),
68
- true,
69
- );
70
- assert.equal(
71
- isHookApprovalDenialToolResult('Error: tool "shell" denied by hook: approval required but no approval UI is available'),
72
- true,
73
- );
74
- assert.equal(isHookApprovalDenialToolResult('grep: no matches'), false);
75
- });
76
-
77
- test('fully-failed hook denials are not suppressed', () => {
78
- assert.equal(isFullyFailedToolBatch(hookDeniedItem), true);
79
- assert.equal(isHookApprovalDenialToolItem(hookDeniedItem), true);
80
- assert.equal(shouldSuppressFullyFailedToolItem(hookDeniedItem), false);
81
- });
82
-
83
- test('fully-failed non-hook errors with visible body stay visible', () => {
84
- assert.equal(isFullyFailedToolBatch(noisyFailedItem), true);
85
- assert.equal(hasUsefulFailedToolResultBody(noisyFailedItem), true);
86
- assert.equal(shouldSuppressFullyFailedToolItem(noisyFailedItem), false);
87
- });
88
-
89
- test('fully-failed non-hook errors without useful body stay suppressed', () => {
90
- assert.equal(isFullyFailedToolBatch(emptyFailedItem), true);
91
- assert.equal(hasUsefulFailedToolResultBody(emptyFailedItem), false);
92
- assert.equal(shouldSuppressFullyFailedToolItem(emptyFailedItem), true);
93
- });
94
-
95
- test('failed background task with JSON-string args is not suppressed', () => {
96
- assert.equal(isFullyFailedToolBatch(failedTaskJsonStringArgs), false);
97
- assert.equal(shouldSuppressFullyFailedToolItem(failedTaskJsonStringArgs), false);
98
- });
99
-
100
- test('failed background task with input-wrapped args is not suppressed', () => {
101
- assert.equal(isFullyFailedToolBatch(failedTaskInputWrapperArgs), false);
102
- assert.equal(shouldSuppressFullyFailedToolItem(failedTaskInputWrapperArgs), false);
103
- });
104
-
105
- test('formatHookDenialDetail strips tool prefix', () => {
106
- assert.equal(
107
- formatHookDenialDetail(hookDeniedItem.result),
108
- 'denied by hook: approval required but no approval UI is available (policy)',
109
- );
110
- });
111
-
@@ -1,70 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { env } from 'node:process';
4
- import { renderMarkdown } from './markdown.mjs';
5
- import { refreshColorSupport, stripAnsi } from './ansi.mjs';
6
-
7
- test.before(() => {
8
- env.FORCE_COLOR = '1';
9
- refreshColorSupport();
10
- });
11
-
12
- test('renderMarkdown never throws on garbage input', () => {
13
- assert.doesNotThrow(() => renderMarkdown('**unclosed `fence\n> quote\n'));
14
- assert.equal(renderMarkdown(null), '');
15
- });
16
-
17
- test('diff fenced blocks color hunks and +/- lines', () => {
18
- const src = [
19
- '```diff',
20
- '--- a/file.mjs',
21
- '+++ b/file.mjs',
22
- '@@ -1,2 +1,2 @@',
23
- '-old',
24
- '+new',
25
- '```',
26
- ].join('\n');
27
- const out = renderMarkdown(src);
28
- assert.match(out, /\x1b\[38;2;204;157;44/);
29
- assert.match(out, /\x1b\[38;2;0;170;75/);
30
- assert.match(out, /\x1b\[38;2;220;70;88/);
31
- assert.ok(stripAnsi(out).includes('diff'));
32
- });
33
-
34
- test('inline code and headings use Mixdog accent colors', () => {
35
- const out = renderMarkdown('# Title\nUse `code` here.');
36
- assert.match(out, /\x1b\[38;2;215;119;87/);
37
- assert.match(out, /\x1b\[38;2;138;190;183/);
38
- });
39
-
40
- test('longer fence opener does not close on shorter backtick line inside block', () => {
41
- const src = ['````', '```', 'still inside', '````'].join('\n');
42
- const plain = stripAnsi(renderMarkdown(src));
43
- assert.ok(plain.includes('still inside'));
44
- assert.ok(!plain.includes('```\nstill'));
45
- });
46
-
47
- test('mixed backtick-tilde closer does not end backtick fence', () => {
48
- const src = ['```', '``~', 'still inside', '```'].join('\n');
49
- const plain = stripAnsi(renderMarkdown(src));
50
- assert.ok(plain.includes('still inside'));
51
- assert.ok(plain.includes('``~'));
52
- });
53
-
54
- test('fenced code block body rows include right border', () => {
55
- const out = stripAnsi(renderMarkdown('```js\nx\n```'));
56
- const rows = out.split('\n').filter((line) => line.startsWith('│ '));
57
- assert.ok(rows.length >= 1);
58
- for (const row of rows) {
59
- assert.match(row, /│\s*$/);
60
- }
61
- });
62
-
63
- test('inline code inside link labels is preserved', () => {
64
- const out = renderMarkdown('[see `foo`](https://example.com)');
65
- const plain = stripAnsi(out);
66
- assert.ok(plain.includes('foo'));
67
- assert.ok(plain.includes('https://example.com'));
68
- assert.match(out, /\x1b\[38;2;138;190;183/);
69
- });
70
-
@@ -1,15 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { contextPctDisplayLabel } from './statusline.mjs';
4
-
5
- test('contextPctDisplayLabel clamps displayed percent at 100', () => {
6
- assert.equal(contextPctDisplayLabel(105), '100');
7
- assert.equal(contextPctDisplayLabel(100.9), '100');
8
- assert.equal(contextPctDisplayLabel(99.2), '99');
9
- });
10
-
11
- test('contextPctDisplayLabel keeps sub-1% decimal display', () => {
12
- assert.equal(contextPctDisplayLabel(0.45), '0.5');
13
- assert.equal(contextPctDisplayLabel(0.04), '0');
14
- });
15
-