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,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* components/MarkdownTable.jsx — GFM table → ink Box layout.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* adapted for this CLI:
|
|
4
|
+
* GFM table layout for ink:
|
|
6
5
|
* - <Ansi> → <Text> (ink 7 has no <Ansi>; <Text> passes ANSI escapes through).
|
|
7
6
|
* - useTerminalSize() → ink useStdout().stdout.columns.
|
|
8
|
-
* - stringWidth / wrapAnsi from
|
|
7
|
+
* - stringWidth / wrapAnsi from npm packages.
|
|
9
8
|
* - formatCell uses our format-token.formatToken (no highlight arg).
|
|
10
9
|
*
|
|
11
10
|
* The width-fitting algorithm (ideal vs min widths, proportional shrink, hard
|
|
@@ -13,29 +12,7 @@
|
|
|
13
12
|
*/
|
|
14
13
|
import React from 'react';
|
|
15
14
|
import { Text, useStdout } from 'ink';
|
|
16
|
-
import
|
|
17
|
-
import stringWidth from 'string-width';
|
|
18
|
-
import wrapAnsi from 'wrap-ansi';
|
|
19
|
-
import { formatToken, padAligned } from '../markdown/format-token.mjs';
|
|
20
|
-
|
|
21
|
-
const SAFETY_MARGIN = 4;
|
|
22
|
-
const MIN_COLUMN_WIDTH = 3;
|
|
23
|
-
const MAX_ROW_LINES = 4;
|
|
24
|
-
const ANSI_BOLD_START = '\x1b[1m';
|
|
25
|
-
const ANSI_BOLD_END = '\x1b[22m';
|
|
26
|
-
|
|
27
|
-
/** Wrap text to width, ANSI-aware, returning lines (CC wrapText). */
|
|
28
|
-
function wrapText(text, width, options) {
|
|
29
|
-
if (width <= 0) return [text];
|
|
30
|
-
const trimmedText = String(text).trimEnd();
|
|
31
|
-
const wrapped = wrapAnsi(trimmedText, width, {
|
|
32
|
-
hard: options?.hard ?? false,
|
|
33
|
-
trim: false,
|
|
34
|
-
wordWrap: true,
|
|
35
|
-
});
|
|
36
|
-
const lines = wrapped.split('\n').filter((line) => line.length > 0);
|
|
37
|
-
return lines.length > 0 ? lines : [''];
|
|
38
|
-
}
|
|
15
|
+
import { buildTableRender } from '../markdown/table-layout.mjs';
|
|
39
16
|
|
|
40
17
|
export function MarkdownTable({ token, forceWidth }) {
|
|
41
18
|
// App owns resize reflow and there is no <Static> transcript anymore, so table
|
|
@@ -43,162 +20,10 @@ export function MarkdownTable({ token, forceWidth }) {
|
|
|
43
20
|
const actualTerminalWidth = useStdout()?.stdout?.columns ?? 80;
|
|
44
21
|
const terminalWidth = forceWidth ?? actualTerminalWidth;
|
|
45
22
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (words.length === 0) return MIN_COLUMN_WIDTH;
|
|
53
|
-
return Math.max(...words.map((w) => stringWidth(w)), MIN_COLUMN_WIDTH);
|
|
54
|
-
};
|
|
55
|
-
const getIdealWidth = (tokens) =>
|
|
56
|
-
Math.max(stringWidth(getPlainText(tokens)), MIN_COLUMN_WIDTH);
|
|
57
|
-
|
|
58
|
-
// Step 1: min (longest word) and ideal (full content) widths per column.
|
|
59
|
-
const minWidths = token.header.map((header, colIndex) => {
|
|
60
|
-
let maxMinWidth = getMinWidth(header.tokens);
|
|
61
|
-
for (const row of token.rows) maxMinWidth = Math.max(maxMinWidth, getMinWidth(row[colIndex]?.tokens));
|
|
62
|
-
return maxMinWidth;
|
|
63
|
-
});
|
|
64
|
-
const idealWidths = token.header.map((header, colIndex) => {
|
|
65
|
-
let maxIdeal = getIdealWidth(header.tokens);
|
|
66
|
-
for (const row of token.rows) maxIdeal = Math.max(maxIdeal, getIdealWidth(row[colIndex]?.tokens));
|
|
67
|
-
return maxIdeal;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Step 2: available space.
|
|
71
|
-
const numCols = token.header.length;
|
|
72
|
-
const borderOverhead = 1 + numCols * 3;
|
|
73
|
-
const availableWidth = Math.max(terminalWidth - borderOverhead - SAFETY_MARGIN, numCols * MIN_COLUMN_WIDTH);
|
|
74
|
-
|
|
75
|
-
// Step 3: fit column widths into available space.
|
|
76
|
-
const totalMin = minWidths.reduce((s, w) => s + w, 0);
|
|
77
|
-
const totalIdeal = idealWidths.reduce((s, w) => s + w, 0);
|
|
78
|
-
let needsHardWrap = false;
|
|
79
|
-
let columnWidths;
|
|
80
|
-
if (totalIdeal <= availableWidth) {
|
|
81
|
-
columnWidths = idealWidths;
|
|
82
|
-
} else if (totalMin <= availableWidth) {
|
|
83
|
-
const extraSpace = availableWidth - totalMin;
|
|
84
|
-
const overflows = idealWidths.map((ideal, i) => ideal - minWidths[i]);
|
|
85
|
-
const totalOverflow = overflows.reduce((s, o) => s + o, 0);
|
|
86
|
-
columnWidths = minWidths.map((min, i) => {
|
|
87
|
-
if (totalOverflow === 0) return min;
|
|
88
|
-
return min + Math.floor((overflows[i] / totalOverflow) * extraSpace);
|
|
89
|
-
});
|
|
90
|
-
} else {
|
|
91
|
-
needsHardWrap = true;
|
|
92
|
-
const scaleFactor = availableWidth / totalMin;
|
|
93
|
-
columnWidths = minWidths.map((w) => Math.max(Math.floor(w * scaleFactor), MIN_COLUMN_WIDTH));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Step 4: max row lines → decide vertical fallback.
|
|
97
|
-
const calculateMaxRowLines = () => {
|
|
98
|
-
let maxLines = 1;
|
|
99
|
-
for (let i = 0; i < token.header.length; i++) {
|
|
100
|
-
const wrapped = wrapText(formatCell(token.header[i].tokens), columnWidths[i], { hard: needsHardWrap });
|
|
101
|
-
maxLines = Math.max(maxLines, wrapped.length);
|
|
102
|
-
}
|
|
103
|
-
for (const row of token.rows) {
|
|
104
|
-
for (let i = 0; i < row.length; i++) {
|
|
105
|
-
const wrapped = wrapText(formatCell(row[i]?.tokens), columnWidths[i], { hard: needsHardWrap });
|
|
106
|
-
maxLines = Math.max(maxLines, wrapped.length);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return maxLines;
|
|
110
|
-
};
|
|
111
|
-
const useVerticalFormat = calculateMaxRowLines() > MAX_ROW_LINES;
|
|
112
|
-
|
|
113
|
-
const renderRowLines = (cells, isHeader) => {
|
|
114
|
-
const cellLines = cells.map((cell, colIndex) =>
|
|
115
|
-
wrapText(formatCell(cell.tokens), columnWidths[colIndex], { hard: needsHardWrap }),
|
|
116
|
-
);
|
|
117
|
-
const maxLines = Math.max(...cellLines.map((l) => l.length), 1);
|
|
118
|
-
const verticalOffsets = cellLines.map((l) => Math.floor((maxLines - l.length) / 2));
|
|
119
|
-
const result = [];
|
|
120
|
-
for (let lineIdx = 0; lineIdx < maxLines; lineIdx++) {
|
|
121
|
-
let line = '│';
|
|
122
|
-
for (let colIndex = 0; colIndex < cells.length; colIndex++) {
|
|
123
|
-
const lines = cellLines[colIndex];
|
|
124
|
-
const offset = verticalOffsets[colIndex];
|
|
125
|
-
const contentLineIdx = lineIdx - offset;
|
|
126
|
-
const lineText = contentLineIdx >= 0 && contentLineIdx < lines.length ? lines[contentLineIdx] : '';
|
|
127
|
-
const width = columnWidths[colIndex];
|
|
128
|
-
const align = isHeader ? 'center' : token.align?.[colIndex] ?? 'left';
|
|
129
|
-
line += ' ' + padAligned(lineText, stringWidth(lineText), width, align) + ' │';
|
|
130
|
-
}
|
|
131
|
-
result.push(line);
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const renderBorderLine = (type) => {
|
|
137
|
-
const [left, mid, cross, right] = {
|
|
138
|
-
top: ['┌', '─', '┬', '┐'],
|
|
139
|
-
middle: ['├', '─', '┼', '┤'],
|
|
140
|
-
bottom: ['└', '─', '┴', '┘'],
|
|
141
|
-
}[type];
|
|
142
|
-
let line = left;
|
|
143
|
-
columnWidths.forEach((width, colIndex) => {
|
|
144
|
-
line += mid.repeat(width + 2);
|
|
145
|
-
line += colIndex < columnWidths.length - 1 ? cross : right;
|
|
146
|
-
});
|
|
147
|
-
return line;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const renderVerticalFormat = () => {
|
|
151
|
-
const lines = [];
|
|
152
|
-
const headers = token.header.map((h) => getPlainText(h.tokens));
|
|
153
|
-
const separatorWidth = Math.min(terminalWidth - 1, 40);
|
|
154
|
-
const separator = '─'.repeat(separatorWidth);
|
|
155
|
-
const wrapIndent = ' ';
|
|
156
|
-
token.rows.forEach((row, rowIndex) => {
|
|
157
|
-
if (rowIndex > 0) lines.push(separator);
|
|
158
|
-
row.forEach((cell, colIndex) => {
|
|
159
|
-
const label = headers[colIndex] || `Column ${colIndex + 1}`;
|
|
160
|
-
const rawValue = formatCell(cell.tokens).trimEnd();
|
|
161
|
-
const value = rawValue.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
162
|
-
const firstLineWidth = terminalWidth - stringWidth(label) - 3;
|
|
163
|
-
const subsequentLineWidth = terminalWidth - wrapIndent.length - 1;
|
|
164
|
-
const firstPassLines = wrapText(value, Math.max(firstLineWidth, 10));
|
|
165
|
-
const firstLine = firstPassLines[0] || '';
|
|
166
|
-
let wrappedValue;
|
|
167
|
-
if (firstPassLines.length <= 1 || subsequentLineWidth <= firstLineWidth) {
|
|
168
|
-
wrappedValue = firstPassLines;
|
|
169
|
-
} else {
|
|
170
|
-
const remainingText = firstPassLines.slice(1).map((l) => l.trim()).join(' ');
|
|
171
|
-
wrappedValue = [firstLine, ...wrapText(remainingText, subsequentLineWidth)];
|
|
172
|
-
}
|
|
173
|
-
lines.push(`${ANSI_BOLD_START}${label}:${ANSI_BOLD_END} ${wrappedValue[0] || ''}`);
|
|
174
|
-
for (let i = 1; i < wrappedValue.length; i++) {
|
|
175
|
-
if (!wrappedValue[i].trim()) continue;
|
|
176
|
-
lines.push(`${wrapIndent}${wrappedValue[i]}`);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
return lines.join('\n');
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
if (useVerticalFormat) {
|
|
184
|
-
return <Text>{renderVerticalFormat()}</Text>;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const tableLines = [];
|
|
188
|
-
tableLines.push(renderBorderLine('top'));
|
|
189
|
-
tableLines.push(...renderRowLines(token.header, true));
|
|
190
|
-
tableLines.push(renderBorderLine('middle'));
|
|
191
|
-
token.rows.forEach((row, rowIndex) => {
|
|
192
|
-
tableLines.push(...renderRowLines(row, false));
|
|
193
|
-
if (rowIndex < token.rows.length - 1) tableLines.push(renderBorderLine('middle'));
|
|
194
|
-
});
|
|
195
|
-
tableLines.push(renderBorderLine('bottom'));
|
|
196
|
-
|
|
197
|
-
// Safety: if any line would overflow (resize race), fall back to vertical.
|
|
198
|
-
const maxLineWidth = Math.max(...tableLines.map((l) => stringWidth(stripAnsi(l))));
|
|
199
|
-
if (maxLineWidth > terminalWidth - SAFETY_MARGIN) {
|
|
200
|
-
return <Text>{renderVerticalFormat()}</Text>;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return <Text>{tableLines.join('\n')}</Text>;
|
|
23
|
+
// The entire layout (column fit, vertical fallback, bordered box, overflow
|
|
24
|
+
// re-fallback) lives in the pure markdown/table-layout.mjs module so the
|
|
25
|
+
// renderer and the App.jsx row-height estimator share one source of truth and
|
|
26
|
+
// can never drift. The component just draws the lines it returns verbatim.
|
|
27
|
+
const { lines } = buildTableRender(token, terminalWidth);
|
|
28
|
+
return <Text>{lines.join('\n')}</Text>;
|
|
204
29
|
}
|
|
@@ -1,42 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* components/Message.jsx — transcript message rows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* in the `text` color (
|
|
4
|
+
* Transcript message rows:
|
|
5
|
+
* - Assistant: `● <markdown>` where the dot is `BLACK_CIRCLE`
|
|
6
|
+
* in the `text` color (not the orange title accent), in a 2-wide gutter, and
|
|
7
7
|
* the markdown sits in a column box beside it (flexDirection row).
|
|
8
|
-
* -
|
|
9
|
-
* `paddingRight={1}` (no prompt glyph —
|
|
8
|
+
* - User: text on a `userMessageBackground` background with
|
|
9
|
+
* `paddingRight={1}` (no prompt glyph — the bg distinguishes input
|
|
10
10
|
* from assistant/tool rows).
|
|
11
11
|
*
|
|
12
12
|
* The dot uses `minWidth={2}` so wrapped markdown lines align under the text,
|
|
13
|
-
* not under the dot (
|
|
13
|
+
* not under the dot (2-wide gutter alignment).
|
|
14
14
|
*/
|
|
15
15
|
import React from 'react';
|
|
16
16
|
import { Box, Text, useAnimation } from 'ink';
|
|
17
17
|
import { theme, TURN_MARKER } from '../theme.mjs';
|
|
18
18
|
import { Markdown, StreamingMarkdown } from './Markdown.jsx';
|
|
19
|
+
import { assistantBodyWidth } from '../markdown/table-layout.mjs';
|
|
19
20
|
import { THEREFORE } from '../figures.mjs';
|
|
20
21
|
import { formatDuration } from '../time-format.mjs';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// `themeEpoch` is a memo-busting prop (threaded from App): the active theme
|
|
24
|
+
// mutates `theme` in-place, so a /theme switch must re-render this memoized row
|
|
25
|
+
// and recompute its markdown colors. It is forwarded into Markdown so the
|
|
26
|
+
// token/AnsiText caches include it as a dep.
|
|
27
|
+
export const AssistantMessage = React.memo(function AssistantMessage({ text, streaming = false, columns = 80, themeEpoch = 0 }) {
|
|
28
|
+
// The body column needs an EXPLICIT numeric width. Without it, ink/Yoga
|
|
29
|
+
// measures the wrapped markdown body before the row's available width is
|
|
30
|
+
// resolved and caches its height as a single row — so a multi-line assistant
|
|
31
|
+
// message renders only its LAST wrapped line (the head lines are clipped) and
|
|
32
|
+
// streaming height jitters as the measure flips. Reserve the 2-col gutter
|
|
33
|
+
// (BLACK CIRCLE in minWidth={2}) plus one right-edge safety cell. Letting an
|
|
34
|
+
// assistant line reach the terminal's last column can auto-wrap/scroll on
|
|
35
|
+
// Windows Terminal/conhost and make the next redraw appear to lose leading
|
|
36
|
+
// CJK characters even though the backing transcript is intact.
|
|
37
|
+
const bodyWidth = assistantBodyWidth(columns);
|
|
23
38
|
return (
|
|
24
39
|
<Box flexDirection="row" marginTop={1}>
|
|
25
40
|
<Box flexShrink={0} minWidth={2}>
|
|
26
41
|
<Text color={theme.text}>{TURN_MARKER}</Text>
|
|
27
42
|
</Box>
|
|
28
|
-
<Box flexDirection="column"
|
|
29
|
-
{streaming
|
|
43
|
+
<Box flexDirection="column" flexShrink={0} width={bodyWidth}>
|
|
44
|
+
{streaming
|
|
45
|
+
? <StreamingMarkdown themeEpoch={themeEpoch} columns={bodyWidth}>{text}</StreamingMarkdown>
|
|
46
|
+
: <Markdown themeEpoch={themeEpoch} columns={bodyWidth}>{text}</Markdown>}
|
|
30
47
|
</Box>
|
|
31
48
|
</Box>
|
|
32
49
|
);
|
|
33
50
|
});
|
|
34
51
|
|
|
35
|
-
export const UserMessage = React.memo(function UserMessage({ text, attached = false, columns }) {
|
|
52
|
+
export const UserMessage = React.memo(function UserMessage({ text, attached = false, columns, themeEpoch = 0 }) {
|
|
36
53
|
// `attached` = the previous transcript row is also a user message (consecutive
|
|
37
54
|
// steering prompts). Those stack flush together; a user message that follows
|
|
38
55
|
// an assistant/tool row gets a one-row gap above it.
|
|
39
|
-
// The background band fills the row edge-to-edge (
|
|
56
|
+
// The background band fills the row edge-to-edge (user bubble reads as a
|
|
40
57
|
// full-width band, not a text-hugging tag). An explicit numeric width is more
|
|
41
58
|
// robust than "100%" here — it guarantees the band even if a parent's width
|
|
42
59
|
// context is ambiguous. paddingLeft aligns the body under the 2-col gutter.
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* components/Picker.jsx — selectable list picker for slash commands.
|
|
3
|
-
*
|
|
4
|
-
* Renders a bordered, scrollable list of items with up/down navigation,
|
|
1
|
+
/**
|
|
2
|
+
* components/Picker.jsx — selectable list picker for slash commands.
|
|
3
|
+
*
|
|
4
|
+
* Renders a bordered, scrollable list of items with up/down navigation,
|
|
5
5
|
* Enter confirms and Escape backs out. Used by /model and /resume to let the
|
|
6
|
-
* user pick from available presets or saved sessions.
|
|
7
|
-
*
|
|
8
|
-
* Keyboard:
|
|
6
|
+
* user pick from available presets or saved sessions.
|
|
7
|
+
*
|
|
8
|
+
* Keyboard:
|
|
9
9
|
* ↑ / ↓ — move selection (wraps at ends)
|
|
10
10
|
* ← / → — optional picker-specific adjustment
|
|
11
11
|
* Tab — optional picker-specific toggle
|
|
12
12
|
* Enter — choose/apply the selected row
|
|
13
13
|
* Escape — back/cancel
|
|
14
14
|
* Ctrl+C — handled globally for selection copy
|
|
15
|
-
*/
|
|
15
|
+
*/
|
|
16
16
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
17
17
|
import { Box, Text, useInput } from 'ink';
|
|
18
18
|
import stringWidth from 'string-width';
|
|
19
19
|
import { theme } from '../theme.mjs';
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
/** Max items visible at once before scrolling kicks in. */
|
|
22
22
|
const MAX_VISIBLE = 8;
|
|
23
23
|
const DEFAULT_LABEL_WIDTH = 28;
|
|
@@ -78,6 +78,8 @@ export function Picker({
|
|
|
78
78
|
onLeft,
|
|
79
79
|
onRight,
|
|
80
80
|
onTab,
|
|
81
|
+
onKey,
|
|
82
|
+
onHighlight,
|
|
81
83
|
title,
|
|
82
84
|
description = '',
|
|
83
85
|
footer = '',
|
|
@@ -85,10 +87,15 @@ export function Picker({
|
|
|
85
87
|
columns = 80,
|
|
86
88
|
labelWidth: labelWidthOverride = null,
|
|
87
89
|
metaWidth: metaWidthOverride = null,
|
|
90
|
+
footerGapRows = 1,
|
|
88
91
|
initialIndex = 0,
|
|
89
92
|
indexMode = 'auto',
|
|
90
93
|
fillHeight = false,
|
|
91
94
|
visibleCount = MAX_VISIBLE,
|
|
95
|
+
// Memo-busting epoch: ItemRow is React.memo and reads theme.* directly, so a
|
|
96
|
+
// live /theme switch (or picker preview) must re-render every row. Threading
|
|
97
|
+
// the epoch into each ItemRow breaks its shallow-equality on a theme change.
|
|
98
|
+
themeEpoch = 0,
|
|
92
99
|
}) {
|
|
93
100
|
const visibleLimit = Math.max(1, Math.floor(Number(visibleCount) || MAX_VISIBLE));
|
|
94
101
|
const [selectedIndex, setSelectedIndex] = useState(() => Math.max(0, Math.min(Number(initialIndex) || 0, Math.max(0, items.length - 1))));
|
|
@@ -101,9 +108,20 @@ export function Picker({
|
|
|
101
108
|
setSelectedIndex(Math.max(0, Math.min(Number(initialIndex) || 0, Math.max(0, items.length - 1))));
|
|
102
109
|
}, [initialIndex, items.length]);
|
|
103
110
|
|
|
111
|
+
// Live-preview hook: notify the owner whenever the highlighted row changes
|
|
112
|
+
// (arrow keys, paging, initial mount). The /theme picker uses this to apply a
|
|
113
|
+
// non-persisted palette preview as the selection moves. Kept side-effect-free
|
|
114
|
+
// for pickers that do not pass onHighlight.
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (typeof onHighlight !== 'function') return;
|
|
117
|
+
const item = items[selectedIndex];
|
|
118
|
+
if (item) onHighlight(item.value, item, selectedIndex);
|
|
119
|
+
}, [onHighlight, items, selectedIndex]);
|
|
120
|
+
|
|
104
121
|
const activeFooter = typeof footer === 'function' ? footer(items[selectedIndex], selectedIndex) : footer;
|
|
105
122
|
const footerLines = normalizeFooterLines(activeFooter, columns);
|
|
106
|
-
const
|
|
123
|
+
const footerGap = footerLines.length > 0 ? Math.max(0, Math.floor(Number(footerGapRows) || 0)) : 0;
|
|
124
|
+
const footerReserveRows = footerLines.length > 0 ? footerLines.length + footerGap : 0;
|
|
107
125
|
const effectiveVisibleLimit = Math.max(1, visibleLimit - footerReserveRows);
|
|
108
126
|
const helpText = help || (onLeft || onRight || onTab ? ADJUST_HELP : SELECT_HELP);
|
|
109
127
|
|
|
@@ -157,18 +175,25 @@ export function Picker({
|
|
|
157
175
|
if (selected && onSelect) onSelect(selected.value, selected);
|
|
158
176
|
return;
|
|
159
177
|
}
|
|
160
|
-
if (key.escape) {
|
|
161
|
-
onCancel();
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
if (key.escape) {
|
|
179
|
+
onCancel();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (key.ctrl && (input === 'c' || input === 'C')) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (onKey) {
|
|
186
|
+
onKey(input, key, items[selectedIndex], selectedIndex);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[items, selectedIndex, onSelect, onCancel, onLeft, onRight, onTab, onKey, effectiveVisibleLimit],
|
|
166
191
|
),
|
|
167
192
|
);
|
|
168
|
-
|
|
169
|
-
// Clamp selected index when items change length.
|
|
193
|
+
|
|
194
|
+
// Clamp selected index when items change length.
|
|
170
195
|
if (items.length === 0) {
|
|
171
|
-
const
|
|
196
|
+
const emptyLine = truncateText(String(description || '').replace(/\s+/g, ' ').trim(), Math.max(0, columns - 4));
|
|
172
197
|
return (
|
|
173
198
|
<Box flexDirection="column" flexShrink={0} height={fillHeight ? '100%' : undefined}>
|
|
174
199
|
<Box
|
|
@@ -179,27 +204,21 @@ export function Picker({
|
|
|
179
204
|
height={fillHeight ? '100%' : undefined}
|
|
180
205
|
width="100%"
|
|
181
206
|
>
|
|
182
|
-
<Box flexDirection="row" justifyContent="space-between"
|
|
207
|
+
<Box flexDirection="row" justifyContent="space-between">
|
|
183
208
|
<Text color={theme.panelTitle}>{title || 'Picker'}</Text>
|
|
184
209
|
<Text color={theme.subtle}>{helpText}</Text>
|
|
185
210
|
</Box>
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
) : (
|
|
193
|
-
<>
|
|
194
|
-
<Text> </Text>
|
|
195
|
-
<Text color={theme.inactive}>(empty)</Text>
|
|
196
|
-
</>
|
|
197
|
-
)}
|
|
211
|
+
{/* Standard rhythm: title, blank, description/hint (blank if none),
|
|
212
|
+
blank, then the (empty) content row. */}
|
|
213
|
+
<Text> </Text>
|
|
214
|
+
<Text color={theme.text}>{emptyLine || ' '}</Text>
|
|
215
|
+
<Text> </Text>
|
|
216
|
+
<Text color={theme.inactive}>(empty)</Text>
|
|
198
217
|
</Box>
|
|
199
218
|
</Box>
|
|
200
219
|
);
|
|
201
220
|
}
|
|
202
|
-
|
|
221
|
+
|
|
203
222
|
// Scroll window centered on the selected item.
|
|
204
223
|
const total = items.length;
|
|
205
224
|
const half = Math.floor(effectiveVisibleLimit / 2);
|
|
@@ -220,10 +239,17 @@ export function Picker({
|
|
|
220
239
|
// Keep the label column fixed across menus. Per-picker overrides are still
|
|
221
240
|
// allowed for intentionally compact surfaces such as providers/resume.
|
|
222
241
|
const labelWidth = clampLabelWidth(labelWidthOverride ?? DEFAULT_LABEL_WIDTH, columns);
|
|
223
|
-
const
|
|
242
|
+
const hasMarker = items.some((item) => item.marker || item.checked === true || item.checked === false);
|
|
243
|
+
const markerWidth = hasMarker ? 2 : 0;
|
|
244
|
+
const hasMeta = metaWidthOverride != null || items.some((item) => item.meta || item.modelProfile || item.metaParts);
|
|
224
245
|
const metaWidth = hasMeta ? clampMetaWidth(metaWidthOverride, columns, labelWidth) : 0;
|
|
225
|
-
const descriptionWidth = Math.max(0, columns - indexOffset - labelWidth - (hasMeta ? metaWidth + 14 : 12));
|
|
226
|
-
|
|
246
|
+
const descriptionWidth = Math.max(0, columns - indexOffset - markerWidth - labelWidth - (hasMeta ? metaWidth + 14 : 12));
|
|
247
|
+
// Standard panel rhythm: title row, blank, description/hint row, blank,
|
|
248
|
+
// content. Description newlines are collapsed and width-truncated to a single
|
|
249
|
+
// line so a multi-line description (e.g. ToolApproval) cannot push the title
|
|
250
|
+
// off the top. The slot is always reserved so panel chrome is a constant 6
|
|
251
|
+
// rows (title + blank + desc + blank + 2 border), matching PICKER_CHROME_ROWS.
|
|
252
|
+
const panelDescription = truncateText(String(description || '').replace(/\s+/g, ' ').trim(), Math.max(0, columns - 4));
|
|
227
253
|
|
|
228
254
|
return (
|
|
229
255
|
<Box flexDirection="column" flexShrink={0} width="100%" height={fillHeight ? '100%' : undefined}>
|
|
@@ -235,26 +261,27 @@ export function Picker({
|
|
|
235
261
|
width="100%"
|
|
236
262
|
height={fillHeight ? '100%' : undefined}
|
|
237
263
|
>
|
|
238
|
-
<Box flexDirection="row" justifyContent="space-between"
|
|
264
|
+
<Box flexDirection="row" justifyContent="space-between">
|
|
239
265
|
<Text color={theme.panelTitle}>{title}</Text>
|
|
240
266
|
<Text color={theme.subtle}>{helpText}</Text>
|
|
241
267
|
</Box>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<Text color={theme.text}>{panelDescription}</Text>
|
|
246
|
-
<Text> </Text>
|
|
247
|
-
</>
|
|
248
|
-
) : null}
|
|
268
|
+
<Text> </Text>
|
|
269
|
+
<Text color={theme.text}>{panelDescription || ' '}</Text>
|
|
270
|
+
<Text> </Text>
|
|
249
271
|
{visible.map((item, i) => {
|
|
250
|
-
const idx = start + i;
|
|
251
|
-
const isSelected = idx === selectedIndex;
|
|
252
|
-
return (
|
|
253
|
-
<ItemRow
|
|
272
|
+
const idx = start + i;
|
|
273
|
+
const isSelected = idx === selectedIndex;
|
|
274
|
+
return (
|
|
275
|
+
<ItemRow
|
|
254
276
|
key={item.value}
|
|
255
277
|
indexText={showIndex ? `${idx + 1}.` : ''}
|
|
256
278
|
indexWidth={indexWidth}
|
|
279
|
+
marker={item.marker || (item.checked === true ? '✓' : item.checked === false ? ' ' : '')}
|
|
280
|
+
markerColor={item.markerColor}
|
|
281
|
+
markerWidth={markerWidth}
|
|
257
282
|
label={item.label}
|
|
283
|
+
labelSuffix={item.labelSuffix}
|
|
284
|
+
labelSuffixColor={item.labelSuffixColor}
|
|
258
285
|
meta={item.meta || item.modelProfile || ''}
|
|
259
286
|
metaParts={item.metaParts}
|
|
260
287
|
description={item.description}
|
|
@@ -263,6 +290,7 @@ export function Picker({
|
|
|
263
290
|
descriptionWidth={descriptionWidth}
|
|
264
291
|
showMeta={hasMeta}
|
|
265
292
|
isSelected={isSelected}
|
|
293
|
+
themeEpoch={themeEpoch}
|
|
266
294
|
/>
|
|
267
295
|
);
|
|
268
296
|
})}
|
|
@@ -282,8 +310,14 @@ export function Picker({
|
|
|
282
310
|
);
|
|
283
311
|
}
|
|
284
312
|
|
|
285
|
-
const ItemRow = React.memo(function ItemRow({ indexText, indexWidth, label, meta, metaParts, description, labelWidth, metaWidth, descriptionWidth, showMeta, isSelected }) {
|
|
286
|
-
const
|
|
313
|
+
const ItemRow = React.memo(function ItemRow({ indexText, indexWidth, marker, markerColor, markerWidth, label, labelSuffix, labelSuffixColor, meta, metaParts, description, labelWidth, metaWidth, descriptionWidth, showMeta, isSelected, themeEpoch = 0 }) {
|
|
314
|
+
const rawSuffix = String(labelSuffix || '');
|
|
315
|
+
const suffix = rawSuffix ? truncateText(rawSuffix, labelWidth) : '';
|
|
316
|
+
const suffixGap = suffix && stringWidth(suffix) < labelWidth ? ' ' : '';
|
|
317
|
+
const suffixWidth = suffix ? stringWidth(suffixGap) + stringWidth(suffix) : 0;
|
|
318
|
+
const displayMarker = truncateText(marker, markerWidth);
|
|
319
|
+
const displayLabel = truncateText(label, Math.max(0, labelWidth - suffixWidth));
|
|
320
|
+
const labelPadding = ' '.repeat(Math.max(0, labelWidth - stringWidth(displayLabel) - suffixWidth));
|
|
287
321
|
const displayMeta = truncateText(meta, metaWidth);
|
|
288
322
|
const displayDescription = truncateText(description, descriptionWidth);
|
|
289
323
|
const parts = Array.isArray(metaParts) ? metaParts : null;
|
|
@@ -295,9 +329,14 @@ const ItemRow = React.memo(function ItemRow({ indexText, indexWidth, label, meta
|
|
|
295
329
|
{padCells(indexText, indexWidth)}{' '}
|
|
296
330
|
</Text>
|
|
297
331
|
) : null}
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
|
|
332
|
+
{markerWidth > 0 ? (
|
|
333
|
+
<Text color={marker ? (markerColor || theme.success) : theme.text}>
|
|
334
|
+
{padCells(displayMarker, markerWidth)}
|
|
335
|
+
</Text>
|
|
336
|
+
) : null}
|
|
337
|
+
<Text color={theme.text}>{displayLabel}</Text>
|
|
338
|
+
{suffix ? <Text color={labelSuffixColor || theme.success}>{suffixGap}{suffix}</Text> : null}
|
|
339
|
+
<Text color={theme.text}>{labelPadding}</Text>
|
|
301
340
|
{showMeta ? (
|
|
302
341
|
<Text color={theme.text}>
|
|
303
342
|
{' '}
|
|
@@ -312,6 +351,6 @@ const ItemRow = React.memo(function ItemRow({ indexText, indexWidth, label, meta
|
|
|
312
351
|
{displayDescription}
|
|
313
352
|
</Text>
|
|
314
353
|
) : null}
|
|
315
|
-
</Box>
|
|
316
|
-
);
|
|
317
|
-
});
|
|
354
|
+
</Box>
|
|
355
|
+
);
|
|
356
|
+
});
|