mixdog 0.8.0 → 0.9.0

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 (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -1,34 +1,39 @@
1
1
  /**
2
2
  * components/AnsiText.jsx — render ANSI SGR text as ink <Text> spans.
3
3
  *
4
- * Claude Code parses ANSI into styled Text nodes instead of relying on raw
4
+ * Parses ANSI into styled Text nodes instead of relying on raw
5
5
  * escape passthrough. This keeps markdown emphasis/code colors stable when an
6
6
  * inner span resets color back to the default.
7
7
  */
8
8
  import React from 'react';
9
9
  import { Text } from 'ink';
10
- import { theme } from '../theme.mjs';
10
+ import { theme, getThemeVersion } from '../theme.mjs';
11
11
 
12
12
  const ANSI_RE = /\x1b\[([0-9;]*)m/g;
13
13
 
14
- const ANSI_COLORS = {
15
- 30: theme.subtle,
16
- 31: theme.error,
17
- 32: theme.success,
18
- 33: theme.warning,
19
- 34: theme.code,
20
- 35: 'ansi:magenta',
21
- 36: 'ansi:cyan',
22
- 37: theme.text,
23
- 90: theme.subtle,
24
- 91: theme.error,
25
- 92: theme.success,
26
- 93: theme.warning,
27
- 94: theme.code,
28
- 95: 'ansi:magentaBright',
29
- 96: 'ansi:cyanBright',
30
- 97: theme.statusText,
31
- };
14
+ // Resolve the named SGR (30-37/90-97) → theme color map at call time so a
15
+ // live `/theme` switch is honored. `theme` is mutated in-place on switch, so
16
+ // reading the keys per parse keeps colors in sync without a module reload.
17
+ function ansiColorMap() {
18
+ return {
19
+ 30: theme.subtle,
20
+ 31: theme.error,
21
+ 32: theme.success,
22
+ 33: theme.warning,
23
+ 34: theme.code,
24
+ 35: 'ansi:magenta',
25
+ 36: 'ansi:cyan',
26
+ 37: theme.text,
27
+ 90: theme.subtle,
28
+ 91: theme.error,
29
+ 92: theme.success,
30
+ 93: theme.warning,
31
+ 94: theme.code,
32
+ 95: 'ansi:magentaBright',
33
+ 96: 'ansi:cyanBright',
34
+ 97: theme.statusText,
35
+ };
36
+ }
32
37
 
33
38
  const ANSI_BG_COLORS = {
34
39
  40: 'ansi:black',
@@ -74,7 +79,7 @@ function rgbCode(codes, index) {
74
79
  return `rgb(${r},${g},${b})`;
75
80
  }
76
81
 
77
- function applySgr(state, codes, defaultColor) {
82
+ function applySgr(state, codes, defaultColor, ansiColors) {
78
83
  if (!codes.length) codes = [0];
79
84
  for (let i = 0; i < codes.length; i++) {
80
85
  const code = codes[i];
@@ -133,8 +138,8 @@ function applySgr(state, codes, defaultColor) {
133
138
  state.backgroundColor = undefined;
134
139
  break;
135
140
  default:
136
- if (ANSI_COLORS[code]) {
137
- state.color = ANSI_COLORS[code];
141
+ if (ansiColors[code]) {
142
+ state.color = ansiColors[code];
138
143
  } else if (ANSI_BG_COLORS[code]) {
139
144
  state.backgroundColor = ANSI_BG_COLORS[code];
140
145
  }
@@ -147,6 +152,7 @@ function parseAnsi(text, defaultColor) {
147
152
  const source = String(text ?? '');
148
153
  const spans = [];
149
154
  const state = defaultState(defaultColor);
155
+ const ansiColors = ansiColorMap();
150
156
  let lastIndex = 0;
151
157
  let match;
152
158
 
@@ -159,7 +165,7 @@ function parseAnsi(text, defaultColor) {
159
165
  .split(';')
160
166
  .filter(Boolean)
161
167
  .map((n) => Number(n));
162
- applySgr(state, codes, defaultColor);
168
+ applySgr(state, codes, defaultColor, ansiColors);
163
169
  lastIndex = ANSI_RE.lastIndex;
164
170
  }
165
171
 
@@ -171,9 +177,13 @@ function parseAnsi(text, defaultColor) {
171
177
  }
172
178
 
173
179
  export function AnsiText({ children, defaultColor, wrap }) {
180
+ // ansiColorMap() reads live theme.* keys, so the parsed spans depend on the
181
+ // active theme. Include the theme version in the memo deps so a /theme switch
182
+ // re-parses with the new palette instead of reusing stale span colors.
183
+ const themeVersion = getThemeVersion();
174
184
  const spans = React.useMemo(
175
185
  () => parseAnsi(children, defaultColor),
176
- [children, defaultColor],
186
+ [children, defaultColor, themeVersion],
177
187
  );
178
188
 
179
189
  return (
@@ -183,9 +193,10 @@ export function AnsiText({ children, defaultColor, wrap }) {
183
193
  key={index}
184
194
  color={span.style.color}
185
195
  backgroundColor={span.style.backgroundColor}
186
- // Windows Terminal's synthetic bold makes Korean markdown text look
187
- // slightly fuzzy. Keep the color emphasis, but avoid the font weight.
188
- bold={false}
196
+ // Honor SGR bold only on spans that chalk/markdown set (e.g. **strong**,
197
+ // headings). Do not force bold globally avoids fuzzy Korean body text
198
+ // when models emit no bold codes.
199
+ bold={span.style.bold}
189
200
  dimColor={span.style.dimColor}
190
201
  italic={span.style.italic}
191
202
  underline={span.style.underline}
@@ -75,25 +75,57 @@ function metricValue(parts) {
75
75
  return parts.filter(Boolean).join(' · ');
76
76
  }
77
77
 
78
- function MetricCell({ label, value, width, tone }) {
79
- const labelWidth = Math.min(12, Math.max(8, Math.floor(width * 0.34)));
80
- const valueWidth = Math.max(0, width - labelWidth - 1);
78
+ function DetailLine({ label, value, columns }) {
79
+ const innerWidth = Math.max(24, Math.floor(columns || 80) - 4);
80
+ const labelWidth = 10;
81
+ const valueWidth = Math.max(0, innerWidth - labelWidth - 2);
82
+ return (
83
+ <Box flexDirection="row" width="100%">
84
+ <Text color={theme.subtle}>{padCells(truncateText(label, labelWidth), labelWidth)}</Text>
85
+ <Text color={theme.inactive}> </Text>
86
+ <Text color={theme.text}>{truncateText(value, valueWidth)}</Text>
87
+ </Box>
88
+ );
89
+ }
90
+
91
+ function bucketTokens(map, names) {
92
+ return names.reduce((sum, name) => sum + finiteNumber(map?.[name]?.tokens), 0);
93
+ }
94
+
95
+ function bucketCount(map, names) {
96
+ return names.reduce((sum, name) => sum + finiteNumber(map?.[name]?.count), 0);
97
+ }
98
+
99
+ function semanticTokens(semantic, names) {
100
+ return names.reduce((sum, name) => sum + finiteNumber(semantic?.[name]?.tokens), 0);
101
+ }
102
+
103
+ function CategoryItem({ label, tokens, total, width }) {
104
+ const labelWidth = Math.min(11, Math.max(7, Math.floor(width * 0.22)));
105
+ const pctWidth = 6;
106
+ const tokenWidth = 7;
107
+ const barWidth = Math.max(6, Math.min(20, width - labelWidth - pctWidth - tokenWidth - 4));
108
+ const pct = percent(tokens, total);
81
109
  return (
82
110
  <Box flexDirection="row" width={width}>
83
- <Text color={theme.subtle}>{padCells(truncateText(label, labelWidth), labelWidth)} </Text>
84
- <Text color={tone || theme.text}>{truncateText(value, valueWidth)}</Text>
111
+ <Text color={theme.subtle}>{padCells(truncateText(label, labelWidth), labelWidth)}</Text>
112
+ <Text color={theme.inactive}> </Text>
113
+ <Text color={usageColor(pct)}>{padCells(percentLabel(tokens, total), pctWidth)}</Text>
114
+ <ProgressBar value={tokens} total={total} width={barWidth} />
115
+ <Text color={theme.inactive}> </Text>
116
+ <Text color={theme.text}>{padCells(formatTokens(tokens), tokenWidth)}</Text>
85
117
  </Box>
86
118
  );
87
119
  }
88
120
 
89
- function MetricGrid({ cells, columns }) {
121
+ function CategoryGrid({ categories, columns, total }) {
90
122
  const innerWidth = Math.max(24, Math.floor(columns || 80) - 4);
91
- const twoColumns = innerWidth >= 76;
123
+ const twoColumns = innerWidth >= 84;
92
124
  if (!twoColumns) {
93
125
  return (
94
126
  <Box flexDirection="column" width="100%">
95
- {cells.map((cell) => (
96
- <MetricCell key={cell.label} {...cell} width={innerWidth} />
127
+ {categories.map((category) => (
128
+ <CategoryItem key={category.label} {...category} total={total} width={innerWidth} />
97
129
  ))}
98
130
  </Box>
99
131
  );
@@ -102,14 +134,14 @@ function MetricGrid({ cells, columns }) {
102
134
  const leftWidth = Math.floor((innerWidth - gap) / 2);
103
135
  const rightWidth = innerWidth - gap - leftWidth;
104
136
  const pairs = [];
105
- for (let i = 0; i < cells.length; i += 2) pairs.push(cells.slice(i, i + 2));
137
+ for (let i = 0; i < categories.length; i += 2) pairs.push(categories.slice(i, i + 2));
106
138
  return (
107
139
  <Box flexDirection="column" width="100%">
108
140
  {pairs.map((pair, index) => (
109
141
  <Box key={index} flexDirection="row" width="100%">
110
- <MetricCell {...pair[0]} width={leftWidth} />
142
+ <CategoryItem {...pair[0]} total={total} width={leftWidth} />
111
143
  <Text>{' '.repeat(gap)}</Text>
112
- {pair[1] ? <MetricCell {...pair[1]} width={rightWidth} /> : null}
144
+ {pair[1] ? <CategoryItem {...pair[1]} total={total} width={rightWidth} /> : null}
113
145
  </Box>
114
146
  ))}
115
147
  </Box>
@@ -127,6 +159,9 @@ function ContextUsageView({ detail, columns }) {
127
159
  const lastApi = detail?.lastApi || {};
128
160
  const cache = detail?.cache || {};
129
161
  const extensions = detail?.extensions || {};
162
+ const mcp = detail?.mcp || {};
163
+ const semantic = messages.semantic || {};
164
+ const schema = request.toolSchemaBreakdown || {};
130
165
  const usedTokens = finiteNumber(usage.usedTokens);
131
166
  const windowTokens = finiteNumber(usage.windowTokens);
132
167
  const freeTokens = windowTokens ? Math.max(0, windowTokens - usedTokens) : finiteNumber(usage.freeTokens);
@@ -134,25 +169,36 @@ function ContextUsageView({ detail, columns }) {
134
169
  const summaryText = `${formatTokens(usedTokens)} / ${formatTokens(windowTokens)} · ${formatTokens(freeTokens)} free`;
135
170
  const pctText = `${percentLabel(usedTokens, windowTokens)} used`;
136
171
  const barWidth = Math.max(12, Math.min(34, innerWidth - stringWidth(summaryText) - stringWidth(pctText) - 5));
172
+ const builtInToolTokens = bucketTokens(schema, ['code', 'web', 'mutation', 'channels', 'setup', 'other']);
173
+ const builtInToolCount = bucketCount(schema, ['code', 'web', 'mutation', 'channels', 'setup', 'other']);
174
+ const sessionTokens = semanticTokens(semantic, ['workspace', 'environment', 'other']);
137
175
  const compactionLine = metricValue([
138
- compaction.stage || 'pending',
176
+ compaction.stage && compaction.stage !== 'pending' ? compaction.stage : '',
139
177
  compaction.state,
178
+ compaction.type ? `type ${compaction.type}` : '',
140
179
  compaction.triggerTokens ? `trigger ${formatTokens(compaction.triggerTokens)}` : '',
141
180
  compaction.boundaryTokens ? `boundary ${formatTokens(compaction.boundaryTokens)}` : '',
142
181
  ]);
143
- const metaLine = metricValue([
144
- usage.source || 'estimated',
145
- usage.effective ? 'effective window' : '',
182
+ const sourceLine = metricValue([
183
+ usage.effective ? `effective ${formatTokens(windowTokens)}` : `window ${formatTokens(windowTokens)}`,
146
184
  usage.rawWindowTokens && usage.rawWindowTokens !== usage.windowTokens ? `raw ${formatTokens(usage.rawWindowTokens)}` : '',
147
185
  ]);
148
- const cells = [
149
- { label: 'Messages', value: metricValue([`${formatTokens(messages.tokens)} tokens`, `${messages.count || 0} msgs`]) },
150
- { label: 'Tools', value: metricValue([`${formatTokens(tools.schemaTokens)} schema`, `${tools.active || 0}/${tools.count || 0} active`]) },
151
- { label: 'Tool I/O', value: metricValue([`${toolIo.calls || 0} calls`, `${toolIo.results || 0} results`]) },
152
- { label: 'Overhead', value: metricValue([`${formatTokens(request.overheadTokens)} frame`, `${formatTokens(request.reserveTokens)} reserve`]) },
153
- { label: 'Last API', value: metricValue([`${formatTokens(lastApi.contextTokens)} ctx`, `${formatTokens(lastApi.inputTokens)} in`, `${formatTokens(lastApi.outputTokens)} out`]) },
154
- { label: 'Cache', value: metricValue([`${cache.hitRate || 'n/a'} hit`, `${formatTokens(cache.readTokens)} read`]) },
155
- { label: 'Extensions', value: metricValue([`${extensions.skills || 0} skills`, `${extensions.plugins || 0} plugins`]) },
186
+ const apiLine = metricValue([
187
+ `last ctx ${formatTokens(lastApi.contextTokens)}`,
188
+ `in/out ${formatTokens(lastApi.inputTokens)}/${formatTokens(lastApi.outputTokens)}`,
189
+ `cache ${cache.hitRate || 'n/a'}`,
190
+ ]);
191
+ const categories = [
192
+ { label: 'Messages', tokens: semanticTokens(semantic, ['chat', 'assistant']), meta: '' },
193
+ { label: 'Tools', tokens: builtInToolTokens, meta: `${tools.active || 0}/${tools.count || 0} active${builtInToolCount ? ` · ${builtInToolCount} defs` : ''}` },
194
+ { label: 'MCP', tokens: bucketTokens(schema, ['mcp']), meta: `${mcp.connected || 0}/${mcp.configured || 0} servers` },
195
+ { label: 'Skills', tokens: bucketTokens(schema, ['skills']), meta: `${extensions.skills || 0} skills` },
196
+ { label: 'Memory', tokens: semanticTokens(semantic, ['memory']) + bucketTokens(schema, ['memory']), meta: 'core + recall tools' },
197
+ { label: 'Session', tokens: sessionTokens, meta: 'workspace · environment' },
198
+ { label: 'Workflow', tokens: semanticTokens(semantic, ['workflow']) + bucketTokens(schema, ['agents']), meta: 'workflow · agents' },
199
+ { label: 'System', tokens: semanticTokens(semantic, ['system']), meta: 'rules · role catalog' },
200
+ { label: 'Overhead', tokens: finiteNumber(request.overheadTokens), meta: 'request frame' },
201
+ { label: 'Tool I/O', tokens: semanticTokens(semantic, ['toolResults']), meta: `${toolIo.calls || 0} calls · ${toolIo.results || 0} results` },
156
202
  ];
157
203
 
158
204
  return (
@@ -165,17 +211,21 @@ function ContextUsageView({ detail, columns }) {
165
211
  <Text color={theme.text}>{truncateText(summaryText, Math.max(0, innerWidth - Math.min(10, innerWidth) - barWidth - 3))}</Text>
166
212
  </Box>
167
213
  <Box marginTop={1} flexDirection="column" width="100%">
168
- <Text color={theme.subtle}>{truncateText(metaLine, innerWidth)}</Text>
169
- <Text color={theme.text}>{truncateText(`Compaction ${compactionLine}`, innerWidth)}</Text>
214
+ <DetailLine label="Source" value={sourceLine} columns={columns} />
215
+ <DetailLine label="Compaction" value={compactionLine} columns={columns} />
216
+ <DetailLine label="API/cache" value={apiLine} columns={columns} />
170
217
  </Box>
171
218
  <Box marginTop={1} flexDirection="column" width="100%">
172
- <MetricGrid cells={cells} columns={columns} />
219
+ <Text color={theme.subtle}>Context mix</Text>
220
+ <Box marginTop={1} flexDirection="column" width="100%">
221
+ <CategoryGrid categories={categories} columns={columns} total={windowTokens} />
222
+ </Box>
173
223
  </Box>
174
224
  </Box>
175
225
  );
176
226
  }
177
227
 
178
- export function ContextPanel({ rows, title = 'Context Usage', columns = 80, fillHeight = false, detail = null }) {
228
+ export function ContextPanel({ rows, title = 'Context Usage', columns = 80, fillHeight = false, detail = null, description = '' }) {
179
229
  const safeRows = Array.isArray(rows) ? rows : [];
180
230
  const labelWidth = Math.min(
181
231
  safeRows.reduce((w, row) => Math.max(w, String(row.label || '').length), 0),
@@ -183,6 +233,11 @@ export function ContextPanel({ rows, title = 'Context Usage', columns = 80, fill
183
233
  );
184
234
  const valueWidth = Math.max(0, columns - labelWidth - 8);
185
235
  const isContextUsage = detail?.type === 'context';
236
+ // Standard panel rhythm: title row, blank, description/hint row, blank, content.
237
+ const panelDescription = truncateText(
238
+ String(description || (isContextUsage ? 'Live context window usage by category.' : '')).replace(/\s+/g, ' ').trim(),
239
+ Math.max(0, columns - 4),
240
+ );
186
241
 
187
242
  return (
188
243
  <Box flexDirection="column" flexShrink={0} width="100%" height={fillHeight ? '100%' : undefined}>
@@ -194,10 +249,13 @@ export function ContextPanel({ rows, title = 'Context Usage', columns = 80, fill
194
249
  width="100%"
195
250
  height={fillHeight ? '100%' : undefined}
196
251
  >
197
- <Box flexDirection="row" justifyContent="space-between" marginBottom={1}>
252
+ <Box flexDirection="row" justifyContent="space-between">
198
253
  <Text color={theme.panelTitle}>{title}</Text>
199
254
  <Text color={theme.subtle}>Esc back</Text>
200
255
  </Box>
256
+ <Text> </Text>
257
+ <Text color={theme.subtle}>{panelDescription || ' '}</Text>
258
+ <Text> </Text>
201
259
  {isContextUsage ? (
202
260
  <ContextUsageView detail={detail} columns={columns} />
203
261
  ) : (
@@ -1,93 +1,52 @@
1
1
  /**
2
2
  * components/Markdown.jsx — markdown → ink, hybrid renderer.
3
3
  *
4
- * Ported from Claude Code (refs/claude-code/src/components/Markdown.tsx):
5
- * - marked.lexer() produces the token stream (same lib + config as CC).
4
+ * Markdown ink hybrid renderer:
5
+ * - marked.lexer() produces the token stream.
6
6
  * - Non-table tokens are rendered to ANSI strings via formatToken and emitted
7
- * through <AnsiText>, matching CC's span-based <Ansi> behavior.
7
+ * through <AnsiText> as styled spans.
8
8
  * - Tables are rendered by the MarkdownTable component (proper Box layout).
9
- * - Adjacent non-table tokens are coalesced into one <Text> (CC's
10
- * nonTableContent buffer) so block spacing matches.
9
+ * - Block tokens are emitted as separate Ink children so lists, code fences,
10
+ * and tables keep their markdown block boundaries.
11
11
  *
12
- * Syntax highlighting from CC is dropped, but token cache + streaming-split are
12
+ * Syntax highlighting is omitted, but token cache + streaming-split are
13
13
  * kept so partial markdown does not repaint stable text on every delta.
14
14
  */
15
15
  import React, { useRef } from 'react';
16
16
  import { Box, Text } from 'ink';
17
17
  import { marked } from 'marked';
18
- import { formatToken } from '../markdown/format-token.mjs';
18
+ import {
19
+ configureMarked,
20
+ hasMarkdownSyntax,
21
+ renderTokenAnsiSegments,
22
+ } from '../markdown/render-ansi.mjs';
23
+ import { trimPartialClosingFences } from '../markdown/stream-fence.mjs';
19
24
  import { AnsiText } from './AnsiText.jsx';
20
25
  import { MarkdownTable } from './MarkdownTable.jsx';
21
26
  import { theme } from '../theme.mjs';
22
27
 
23
- let _configured = false;
24
- function configureMarked() {
25
- if (_configured) return;
26
- _configured = true;
27
- // Disable strikethrough: models use ~ for "approximate" (~100), not <del>.
28
- marked.use({ tokenizer: { del() { return undefined; } } });
29
- }
30
-
31
- const TOKEN_CACHE_MAX = 500;
32
- const tokenCache = new Map();
33
- const MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
34
-
35
- function hasMarkdownSyntax(text) {
36
- const sample = text.length > 500 ? text.slice(0, 500) : text;
37
- return MD_SYNTAX_RE.test(sample);
38
- }
39
-
40
- function cachedLexer(content) {
41
- const text = String(content ?? '');
42
- if (!hasMarkdownSyntax(text)) {
43
- return [{
44
- type: 'paragraph',
45
- raw: text,
46
- text,
47
- tokens: [{ type: 'text', raw: text, text }],
48
- }];
49
- }
50
- const hit = tokenCache.get(text);
51
- if (hit) {
52
- tokenCache.delete(text);
53
- tokenCache.set(text, hit);
54
- return hit;
55
- }
56
- const tokens = marked.lexer(text);
57
- if (tokenCache.size >= TOKEN_CACHE_MAX) {
58
- const first = tokenCache.keys().next().value;
59
- if (first !== undefined) tokenCache.delete(first);
60
- }
61
- tokenCache.set(text, tokens);
62
- return tokens;
63
- }
64
-
65
- function renderMarkdownElements(content) {
66
- configureMarked();
67
- const tokens = cachedLexer(String(content ?? ''));
28
+ function renderMarkdownElements(content, trimPartialFences = false, tableWidth) {
29
+ // `tableWidth` is the App's body/content width; it doubles as the hr fill
30
+ // width. Fall back to 80 when not provided so an hr still spans a sane rule.
31
+ const segments = renderTokenAnsiSegments(content, { trimPartialFences, width: tableWidth || 80 });
68
32
  const result = [];
69
- let buffer = '';
70
33
  let idx = 0;
71
- const flush = () => {
72
- if (buffer) {
73
- // CC trims the coalesced non-table block (MarkdownBody: nonTableContent
74
- // .trim()) so leading/trailing blank lines from token EOLs don't bleed
75
- // into the surrounding gap={1} spacing. defaultColor={theme.text}
76
- // keeps ANSI resets on the same dark-theme foreground instead of the
77
- // terminal profile's default foreground.
78
- result.push(<AnsiText key={`md_${idx++}`} defaultColor={theme.text}>{buffer.trim()}</AnsiText>);
79
- buffer = '';
80
- }
81
- };
82
- for (const token of tokens) {
83
- if (token.type === 'table') {
84
- flush();
85
- result.push(<MarkdownTable key={`md_${idx++}`} token={token} />);
34
+ for (const segment of segments) {
35
+ if (segment.type === 'table') {
36
+ // Pass the App's body width as forceWidth so the table is laid out at the
37
+ // SAME width the row-height estimator measures (measureMarkdownTableRows).
38
+ // Without it MarkdownTable falls back to useStdout().columns, which on
39
+ // win32 is frameColumns+1 a 1-col gap that can flip the table between
40
+ // horizontal and vertical layout and top-clip streaming output.
41
+ result.push(<MarkdownTable key={`md_${idx++}`} token={segment.token} forceWidth={tableWidth} />);
86
42
  } else {
87
- buffer += formatToken(token);
43
+ // defaultColor={theme.text} keeps ANSI resets on the same dark-theme
44
+ // foreground instead of the terminal profile's default foreground.
45
+ result.push(
46
+ <AnsiText key={`md_${idx++}`} defaultColor={theme.text}>{segment.ansi}</AnsiText>,
47
+ );
88
48
  }
89
49
  }
90
- flush();
91
50
  return result;
92
51
  }
93
52
 
@@ -145,15 +104,18 @@ function balanceStreamingMarkdown(text) {
145
104
  return rendered;
146
105
  }
147
106
 
148
- export function Markdown({ children }) {
107
+ export function Markdown({ children, themeEpoch = 0, trimPartialFences = false, columns }) {
149
108
  const elements = React.useMemo(() => {
150
109
  try {
151
- return renderMarkdownElements(children);
110
+ return renderMarkdownElements(children, trimPartialFences, columns);
152
111
  } catch {
153
112
  // Never throw into the render tree — fall back to raw text.
154
113
  return [<Text key="md_0" color={theme.text}>{String(children ?? '')}</Text>];
155
114
  }
156
- }, [children]);
115
+ // themeEpoch is a memo dep so a /theme switch re-renders to ANSI with the new
116
+ // md* colors (formatToken re-resolves its colorizers on the active theme).
117
+ // columns is a dep so a resize re-lays-out tables at the new forceWidth.
118
+ }, [children, themeEpoch, trimPartialFences, columns]);
157
119
 
158
120
  return (
159
121
  <Box flexDirection="column" gap={1}>
@@ -162,13 +124,13 @@ export function Markdown({ children }) {
162
124
  );
163
125
  }
164
126
 
165
- export function StreamingMarkdown({ children }) {
127
+ export function StreamingMarkdown({ children, themeEpoch = 0, columns }) {
166
128
  const stablePrefixRef = useRef('');
167
129
  const text = String(children ?? '');
168
130
 
169
131
  if (!hasMarkdownSyntax(text)) {
170
132
  stablePrefixRef.current = '';
171
- return <Markdown>{text}</Markdown>;
133
+ return <Markdown themeEpoch={themeEpoch} columns={columns}>{text}</Markdown>;
172
134
  }
173
135
 
174
136
  if (!text.startsWith(stablePrefixRef.current)) {
@@ -179,7 +141,11 @@ export function StreamingMarkdown({ children }) {
179
141
  try {
180
142
  configureMarked();
181
143
  const boundary = stablePrefix.length;
144
+ // Lex the still-streaming suffix and trim any partial closing fence so an
145
+ // open code block does not grow-then-shrink as the final backtick(s)
146
+ // stream in. Operates on a fresh lex so the shared tokenCache is untouched.
182
147
  const tokens = marked.lexer(text.substring(boundary));
148
+ trimPartialClosingFences(tokens);
183
149
  let lastContentIdx = tokens.length - 1;
184
150
  while (lastContentIdx >= 0 && tokens[lastContentIdx]?.type === 'space') lastContentIdx--;
185
151
  let advance = 0;
@@ -198,8 +164,8 @@ export function StreamingMarkdown({ children }) {
198
164
  const unstableSuffix = text.substring(stablePrefix.length);
199
165
  return (
200
166
  <Box flexDirection="column" gap={1}>
201
- {stablePrefix ? <Markdown>{stablePrefix}</Markdown> : null}
202
- {unstableSuffix ? <Markdown>{balanceStreamingMarkdown(unstableSuffix)}</Markdown> : null}
167
+ {stablePrefix ? <Markdown themeEpoch={themeEpoch} columns={columns}>{stablePrefix}</Markdown> : null}
168
+ {unstableSuffix ? <Markdown themeEpoch={themeEpoch} columns={columns} trimPartialFences>{balanceStreamingMarkdown(unstableSuffix)}</Markdown> : null}
203
169
  </Box>
204
170
  );
205
171
  }