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.
- package/README.md +47 -23
- package/package.json +33 -27
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +344 -0
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1223 -95
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +118 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +145 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -98
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +367 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +746 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- 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
|
-
*
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 (
|
|
137
|
-
state.color =
|
|
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
|
-
//
|
|
187
|
-
//
|
|
188
|
-
bold
|
|
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
|
|
79
|
-
const
|
|
80
|
-
const
|
|
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)}
|
|
84
|
-
<Text color={
|
|
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
|
|
121
|
+
function CategoryGrid({ categories, columns, total }) {
|
|
90
122
|
const innerWidth = Math.max(24, Math.floor(columns || 80) - 4);
|
|
91
|
-
const twoColumns = innerWidth >=
|
|
123
|
+
const twoColumns = innerWidth >= 84;
|
|
92
124
|
if (!twoColumns) {
|
|
93
125
|
return (
|
|
94
126
|
<Box flexDirection="column" width="100%">
|
|
95
|
-
{
|
|
96
|
-
<
|
|
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 <
|
|
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
|
-
<
|
|
142
|
+
<CategoryItem {...pair[0]} total={total} width={leftWidth} />
|
|
111
143
|
<Text>{' '.repeat(gap)}</Text>
|
|
112
|
-
{pair[1] ? <
|
|
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
|
|
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
|
|
144
|
-
usage.
|
|
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
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
{ label: '
|
|
155
|
-
{ label: '
|
|
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
|
-
<
|
|
169
|
-
<
|
|
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
|
-
<
|
|
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"
|
|
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
|
-
*
|
|
5
|
-
* - marked.lexer() produces the token stream
|
|
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
|
|
7
|
+
* through <AnsiText> as styled spans.
|
|
8
8
|
* - Tables are rendered by the MarkdownTable component (proper Box layout).
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
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
|
|
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 {
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
72
|
-
if (
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
result.push(<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|