@pellux/goodvibes-tui 0.18.12 → 0.18.17
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/CHANGELOG.md +172 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- package/src/config/index.ts +1 -138
- package/src/core/conversation-rendering.ts +3 -3
- package/src/core/conversation.ts +176 -423
- package/src/core/history.ts +45 -0
- package/src/core/orchestrator.ts +3 -735
- package/src/core/system-message-router.ts +19 -58
- package/src/daemon/cli.ts +82 -6
- package/src/input/command-registry.ts +2 -0
- package/src/input/commands/control-room-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +20 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed.ts +7 -1
- package/src/input/handler-modal-routes.ts +19 -2
- package/src/input/handler-modal-token-routes.ts +4 -1
- package/src/input/handler-picker-routes.ts +4 -2
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/handler.ts +1 -1
- package/src/input/model-picker.ts +11 -0
- package/src/input/search.ts +1 -1
- package/src/input/selection.ts +2 -2
- package/src/input/settings-modal.ts +31 -3
- package/src/main.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +3 -3
- package/src/panels/agent-logs-panel.ts +26 -27
- package/src/panels/approval-panel.ts +2 -2
- package/src/panels/automation-control-panel.ts +3 -3
- package/src/panels/base-panel.ts +14 -14
- package/src/panels/builtin/operations.ts +1 -1
- package/src/panels/builtin/session.ts +67 -1
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/cockpit-panel.ts +2 -2
- package/src/panels/communication-panel.ts +3 -3
- package/src/panels/context-visualizer-panel.ts +2 -2
- package/src/panels/control-plane-panel.ts +3 -3
- package/src/panels/cost-tracker-panel.ts +3 -3
- package/src/panels/debug-panel.ts +2 -2
- package/src/panels/diff-panel.ts +2 -2
- package/src/panels/docs-panel.ts +1 -1
- package/src/panels/eval-panel.ts +2 -2
- package/src/panels/file-explorer-panel.ts +3 -3
- package/src/panels/file-preview-panel.ts +3 -3
- package/src/panels/forensics-panel.ts +2 -2
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/hooks-panel.ts +3 -3
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/intelligence-panel.ts +2 -2
- package/src/panels/knowledge-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +2 -2
- package/src/panels/marketplace-panel.ts +1 -1
- package/src/panels/mcp-panel.ts +3 -3
- package/src/panels/memory-panel.ts +1 -1
- package/src/panels/ops-control-panel.ts +3 -3
- package/src/panels/ops-strategy-panel.ts +2 -2
- package/src/panels/orchestration-panel.ts +2 -2
- package/src/panels/panel-list-panel.ts +6 -6
- package/src/panels/plan-dashboard-panel.ts +1 -1
- package/src/panels/plugins-panel.ts +2 -2
- package/src/panels/policy-panel.ts +2 -2
- package/src/panels/polish.ts +3 -3
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-accounts-panel.ts +25 -29
- package/src/panels/provider-health-panel.ts +2 -2
- package/src/panels/provider-stats-panel.ts +3 -3
- package/src/panels/qr-panel.ts +182 -0
- package/src/panels/remote-panel.ts +3 -3
- package/src/panels/routes-panel.ts +3 -3
- package/src/panels/sandbox-panel.ts +2 -2
- package/src/panels/schedule-panel.ts +1 -1
- package/src/panels/scrollable-list-panel.ts +407 -0
- package/src/panels/security-panel.ts +2 -2
- package/src/panels/services-panel.ts +3 -3
- package/src/panels/session-browser-panel.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/skills-panel.ts +6 -6
- package/src/panels/subscription-panel.ts +3 -3
- package/src/panels/symbol-outline-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +4 -4
- package/src/panels/tasks-panel.ts +2 -2
- package/src/panels/thinking-panel.ts +3 -3
- package/src/panels/token-budget-panel.ts +1 -1
- package/src/panels/tool-inspector-panel.ts +3 -3
- package/src/panels/types.ts +5 -5
- package/src/panels/watchers-panel.ts +3 -3
- package/src/panels/welcome-panel.ts +1 -1
- package/src/panels/worktree-panel.ts +22 -21
- package/src/panels/wrfc-panel.ts +3 -3
- package/src/permissions/prompt.ts +3 -22
- package/src/plugins/loader.ts +15 -304
- package/src/renderer/agent-detail-modal.ts +1 -1
- package/src/renderer/autocomplete-overlay.ts +2 -2
- package/src/renderer/bookmark-modal.ts +1 -1
- package/src/renderer/bottom-bar.ts +2 -2
- package/src/renderer/buffer.ts +1 -1
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/context-inspector.ts +1 -1
- package/src/renderer/conversation-layout.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/conversation-surface.ts +2 -2
- package/src/renderer/diff-view.ts +2 -2
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +2 -2
- package/src/renderer/file-tree.ts +2 -2
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/history-search-overlay.ts +2 -2
- package/src/renderer/live-tail-modal.ts +1 -1
- package/src/renderer/markdown.ts +2 -2
- package/src/renderer/modal-factory.ts +3 -3
- package/src/renderer/model-picker-overlay.ts +2 -2
- package/src/renderer/overlay-box.ts +2 -2
- package/src/renderer/panel-composite.ts +1 -1
- package/src/renderer/panel-picker-overlay.ts +2 -2
- package/src/renderer/panel-tab-bar.ts +1 -1
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +2 -2
- package/src/renderer/process-modal.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +2 -2
- package/src/renderer/progress.ts +2 -2
- package/src/renderer/qr-renderer.ts +117 -0
- package/src/renderer/search-overlay.ts +2 -2
- package/src/renderer/selection-modal-overlay.ts +2 -2
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +122 -0
- package/src/renderer/settings-modal.ts +149 -113
- package/src/renderer/shell-surface.ts +1 -1
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tab-strip.ts +2 -2
- package/src/renderer/text-layout.ts +1 -1
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/tool-call.ts +2 -2
- package/src/renderer/ui-factory.ts +2 -2
- package/src/runtime/bootstrap-command-context.ts +5 -6
- package/src/runtime/bootstrap-command-parts.ts +32 -18
- package/src/runtime/bootstrap-core.ts +3 -2
- package/src/runtime/bootstrap-hook-bridge.ts +15 -174
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +7 -2
- package/src/runtime/context.ts +4 -20
- package/src/runtime/diagnostics/panels/index.ts +6 -6
- package/src/runtime/diagnostics/panels/ops.ts +1 -1
- package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
- package/src/runtime/perf/panel-contracts.ts +32 -0
- package/src/runtime/perf/panel-health-monitor.ts +18 -0
- package/src/runtime/services.ts +5 -5
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -2
- package/src/runtime/store/selectors/index.ts +11 -6
- package/src/runtime/store/state.ts +12 -4
- package/src/runtime/ui-events.ts +1 -0
- package/src/runtime/ui-read-model-helpers.ts +1 -32
- package/src/runtime/ui-read-models-observability-maintenance.ts +1 -81
- package/src/runtime/ui-read-models-observability-options.ts +1 -5
- package/src/runtime/ui-read-models-observability-remote.ts +1 -73
- package/src/runtime/ui-read-models-observability-security.ts +1 -172
- package/src/runtime/ui-read-models-observability-system.ts +1 -217
- package/src/runtime/ui-read-models-observability.ts +1 -59
- package/src/runtime/ui-service-queries.ts +1 -114
- package/src/runtime/ui-services.ts +1 -1
- package/src/shell/ui-openers.ts +1 -1
- package/src/tools/index.ts +1 -186
- package/src/types/grid.ts +48 -0
- package/src/utils/clipboard.ts +21 -0
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +185 -0
- package/src/version.ts +1 -1
- package/src/config/service-registry.ts +0 -1
- package/src/config/subscription-providers.ts +0 -127
- package/src/daemon/facade-composition.ts +0 -398
- package/src/daemon/facade.ts +0 -638
- package/src/daemon/surface-policy.ts +0 -60
- package/src/daemon/types.ts +0 -191
- package/src/runtime/diagnostics/actions.ts +0 -776
- package/src/runtime/diagnostics/index.ts +0 -99
- package/src/runtime/diagnostics/panels/agents.ts +0 -252
- package/src/runtime/diagnostics/panels/events.ts +0 -188
- package/src/runtime/diagnostics/panels/health.ts +0 -242
- package/src/runtime/diagnostics/panels/tasks.ts +0 -251
- package/src/runtime/diagnostics/panels/tool-calls.ts +0 -267
- package/src/runtime/diagnostics/provider.ts +0 -262
- package/src/runtime/store/domains/conversation.ts +0 -181
- package/src/runtime/store/domains/permissions.ts +0 -143
- package/src/runtime/store/helpers/reducers/conversation.ts +0 -228
- package/src/runtime/store/helpers/reducers/lifecycle.ts +0 -440
- package/src/runtime/store/helpers/reducers/shared.ts +0 -60
- package/src/runtime/store/helpers/reducers/sync.ts +0 -555
- package/src/runtime/store/helpers/reducers.ts +0 -30
- package/src/runtime/ui-read-models-core.ts +0 -95
- package/src/runtime/ui-read-models-operations.ts +0 -203
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createEmptyLine, createStyledCell, type Line } from '
|
|
2
|
-
import { getDisplayWidth } from '
|
|
1
|
+
import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
|
|
2
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
3
3
|
import { getOverlayMaxWidth } from './overlay-viewport.ts';
|
|
4
4
|
import { GLYPHS, UI_TONES } from './ui-primitives.ts';
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Line } from '
|
|
1
|
+
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import type { InputHandler } from '../input/handler.ts';
|
|
3
3
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
4
4
|
import type { PanelCompositeData } from './compositor.ts';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Line } from '
|
|
2
|
-
import { fitDisplay, getDisplayWidth, truncateDisplay } from '
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
|
+
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
3
|
import type { PanelPicker } from '../panels/panel-picker.ts';
|
|
4
4
|
import { CATEGORY_LABELS, CATEGORY_ORDER } from '../panels/panel-picker.ts';
|
|
5
5
|
import type { PanelCategory, PanelRegistration } from '../panels/types.ts';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Line } from '
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
2
|
import { UIFactory } from './ui-factory.ts';
|
|
3
|
-
import { getDisplayWidth } from '
|
|
3
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
4
|
import { GLYPHS } from './ui-primitives.ts';
|
|
5
5
|
|
|
6
6
|
/** Truncate a string to fit within maxWidth display columns. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Line } from '
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
2
|
import { ModalFactory } from './modal-factory.ts';
|
|
3
3
|
import { formatDuration } from './modal-utils.ts';
|
|
4
4
|
import type { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools/shared/process-manager';
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* Footer hints: [Up/Down] Navigate [Enter] Load [d] Arm/Delete [s] Save current [Esc] Close
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { Line } from '
|
|
10
|
+
import type { Line } from '../types/grid.ts';
|
|
11
11
|
import { ModalFactory } from './modal-factory.ts';
|
|
12
12
|
import type { ProfilePickerModal } from '../input/profile-picker-modal.ts';
|
|
13
13
|
import { formatTimestamp } from './modal-utils.ts';
|
|
14
|
-
import { fitDisplay } from '
|
|
14
|
+
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
15
15
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
package/src/renderer/progress.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Line } from '
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
2
|
import { UIFactory } from './ui-factory.ts';
|
|
3
|
-
import { getDisplayWidth } from '
|
|
3
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
4
|
|
|
5
5
|
// Rich spinner frames (used by progress indicators)
|
|
6
6
|
export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Line } from '../types/grid.ts';
|
|
2
|
+
import { createEmptyLine, createStyledCell } from '../types/grid.ts';
|
|
3
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
|
+
import { generateQrMatrix } from '@pellux/goodvibes-sdk/platform/pairing/qr-generator';
|
|
5
|
+
|
|
6
|
+
export { generateQrMatrix };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render a QR boolean matrix to terminal Lines using Unicode half-block characters.
|
|
10
|
+
*
|
|
11
|
+
* Two matrix rows map to one terminal row:
|
|
12
|
+
* top=dark, bottom=dark → '█' (FULL BLOCK)
|
|
13
|
+
* top=dark, bottom=light → '▀' (UPPER HALF BLOCK)
|
|
14
|
+
* top=light, bottom=dark → '▄' (LOWER HALF BLOCK)
|
|
15
|
+
* top=light, bottom=light → ' ' (SPACE)
|
|
16
|
+
*
|
|
17
|
+
* @param modules - 2D boolean matrix where true = dark module
|
|
18
|
+
* @param width - Terminal width available for centering
|
|
19
|
+
* @param options - Optional fg/bg overrides
|
|
20
|
+
*/
|
|
21
|
+
export function renderQrMatrix(
|
|
22
|
+
modules: readonly boolean[][],
|
|
23
|
+
width: number,
|
|
24
|
+
options?: { fg?: string; bg?: string },
|
|
25
|
+
): Line[] {
|
|
26
|
+
const fg = options?.fg ?? '#000000';
|
|
27
|
+
const bg = options?.bg ?? '#ffffff';
|
|
28
|
+
|
|
29
|
+
const rows = modules.length;
|
|
30
|
+
const cols = modules[0]?.length ?? 0;
|
|
31
|
+
if (rows === 0 || cols === 0) return [];
|
|
32
|
+
|
|
33
|
+
// Each terminal row covers two matrix rows
|
|
34
|
+
const terminalRows = Math.ceil(rows / 2);
|
|
35
|
+
// Left-align with a single-cell indent. Visually aligns with the text above
|
|
36
|
+
// the QR when rendered with half-block characters; bumping higher
|
|
37
|
+
// mis-registers the finder patterns by a visible unit.
|
|
38
|
+
const leftPad = 1;
|
|
39
|
+
|
|
40
|
+
const lines: Line[] = [];
|
|
41
|
+
|
|
42
|
+
// Prepend a single-row top quiet band of bg so the QR's first module row
|
|
43
|
+
// does not butt up against whatever chrome precedes it. Combined with the
|
|
44
|
+
// leftPad=1 on the horizontal axis, this keeps the finder-pattern square
|
|
45
|
+
// margin consistent on both axes.
|
|
46
|
+
{
|
|
47
|
+
const topBand = createEmptyLine(width);
|
|
48
|
+
const endCol = Math.min(leftPad + cols + 1, width);
|
|
49
|
+
for (let col = 0; col < endCol; col++) {
|
|
50
|
+
topBand[col] = createStyledCell(' ', { fg, bg });
|
|
51
|
+
}
|
|
52
|
+
lines.push(topBand);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (let termRow = 0; termRow < terminalRows; termRow++) {
|
|
56
|
+
const matrixRowTop = termRow * 2;
|
|
57
|
+
const matrixRowBot = termRow * 2 + 1;
|
|
58
|
+
const topRow = modules[matrixRowTop];
|
|
59
|
+
const botRow = matrixRowBot < rows ? modules[matrixRowBot] : null;
|
|
60
|
+
|
|
61
|
+
const line = createEmptyLine(width);
|
|
62
|
+
|
|
63
|
+
// Fill leading padding with bg
|
|
64
|
+
for (let col = 0; col < leftPad && col < width; col++) {
|
|
65
|
+
line[col] = createStyledCell(' ', { fg, bg });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Render QR columns
|
|
69
|
+
for (let col = 0; col < cols; col++) {
|
|
70
|
+
const termCol = leftPad + col;
|
|
71
|
+
if (termCol >= width) break;
|
|
72
|
+
|
|
73
|
+
const topDark = topRow ? (topRow[col] ?? false) : false;
|
|
74
|
+
const botDark = botRow ? (botRow[col] ?? false) : false;
|
|
75
|
+
|
|
76
|
+
let char: string;
|
|
77
|
+
let cellFg: string;
|
|
78
|
+
let cellBg: string;
|
|
79
|
+
|
|
80
|
+
if (topDark && botDark) {
|
|
81
|
+
char = '█';
|
|
82
|
+
cellFg = fg;
|
|
83
|
+
cellBg = bg;
|
|
84
|
+
} else if (topDark && !botDark) {
|
|
85
|
+
char = '▀';
|
|
86
|
+
cellFg = fg;
|
|
87
|
+
cellBg = bg;
|
|
88
|
+
} else if (!topDark && botDark) {
|
|
89
|
+
char = '▄';
|
|
90
|
+
cellFg = fg;
|
|
91
|
+
cellBg = bg;
|
|
92
|
+
} else {
|
|
93
|
+
char = ' ';
|
|
94
|
+
cellFg = fg;
|
|
95
|
+
cellBg = bg;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Some terminals may not render block chars at full width — guard
|
|
99
|
+
const charWidth = getDisplayWidth(char);
|
|
100
|
+
if (charWidth <= 0) {
|
|
101
|
+
line[termCol] = createStyledCell(' ', { fg: cellFg, bg: cellBg });
|
|
102
|
+
} else {
|
|
103
|
+
line[termCol] = createStyledCell(char, { fg: cellFg, bg: cellBg });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fill trailing with bg up to end of QR block
|
|
108
|
+
for (let col = leftPad + cols; col < leftPad + cols + 1 && col < width; col++) {
|
|
109
|
+
line[col] = createStyledCell(' ', { fg, bg });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
lines.push(line);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return lines;
|
|
116
|
+
}
|
|
117
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Line } from '
|
|
2
|
-
import { fitDisplay, getDisplayWidth, truncateDisplay } from '
|
|
1
|
+
import type { Line } from '../types/grid.ts';
|
|
2
|
+
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
3
|
import type { SearchManager } from '../input/search.ts';
|
|
4
4
|
import { createBottomBarLine, writeBottomBarText } from './bottom-bar.ts';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Line } from '
|
|
2
|
-
import { fitDisplay, getDisplayWidth, truncateDisplay } from '
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
|
+
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
3
|
import type { SelectionModal } from '../input/selection-modal.ts';
|
|
4
4
|
import {
|
|
5
5
|
createOverlayBoxLayout,
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* Footer hints: [Enter] Load [d] Delete [Esc] Close
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { Line } from '
|
|
10
|
+
import type { Line } from '../types/grid.ts';
|
|
11
11
|
import { ModalFactory } from './modal-factory.ts';
|
|
12
12
|
import type { SessionPickerModal } from '../input/session-picker-modal.ts';
|
|
13
13
|
import { formatTimestamp } from './modal-utils.ts';
|
|
14
|
-
import { fitDisplay } from '
|
|
14
|
+
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
15
15
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure formatting, label, and color helpers for renderSettingsModal.
|
|
3
|
+
* Extracted from settings-modal.ts to keep the renderer under the 800-line
|
|
4
|
+
* architecture cap. No layout logic lives here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SettingEntry, McpEntry, SubscriptionEntry } from '../input/settings-modal.ts';
|
|
8
|
+
import { SETTINGS_CATEGORIES } from '../input/settings-modal.ts';
|
|
9
|
+
|
|
10
|
+
export function formatValue(entry: SettingEntry): string {
|
|
11
|
+
const val = entry.currentValue;
|
|
12
|
+
if (val === null || val === undefined) return '(unset)';
|
|
13
|
+
if (typeof val === 'boolean') return val ? 'true' : 'false';
|
|
14
|
+
if (typeof val === 'string' && val === '') return '(empty)';
|
|
15
|
+
return String(val);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function valueColor(entry: SettingEntry): string {
|
|
19
|
+
if (!entry.isDefault) return '#00ffcc'; // cyan-green = modified
|
|
20
|
+
return '244'; // dim = default
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function flagStateColor(state: string, killed: boolean): string {
|
|
24
|
+
if (killed) return '#ef4444'; // red
|
|
25
|
+
if (state === 'enabled') return '#00ffcc'; // cyan-green
|
|
26
|
+
return '244'; // dim
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mcpTrustColor(mode: McpEntry['trustMode']): string {
|
|
30
|
+
switch (mode) {
|
|
31
|
+
case 'allow-all':
|
|
32
|
+
return '#ef4444';
|
|
33
|
+
case 'ask-on-risk':
|
|
34
|
+
return '#eab308';
|
|
35
|
+
case 'constrained':
|
|
36
|
+
return '#00ffcc';
|
|
37
|
+
case 'blocked':
|
|
38
|
+
return '244';
|
|
39
|
+
default:
|
|
40
|
+
return '244';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function subscriptionStateColor(state: SubscriptionEntry['state']): string {
|
|
45
|
+
switch (state) {
|
|
46
|
+
case 'active':
|
|
47
|
+
return '#00ffcc';
|
|
48
|
+
case 'pending':
|
|
49
|
+
return '#eab308';
|
|
50
|
+
case 'available':
|
|
51
|
+
return '#38bdf8';
|
|
52
|
+
default:
|
|
53
|
+
return '244';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function inferSubscriptionRouteReason(entry: SubscriptionEntry): string | undefined {
|
|
58
|
+
if (entry.routeReason?.trim()) return entry.routeReason;
|
|
59
|
+
if (entry.state === 'active' && entry.oauthConfigured) {
|
|
60
|
+
return 'ambient key override enabled for this provider.';
|
|
61
|
+
}
|
|
62
|
+
if (entry.state === 'pending' && entry.oauthConfigured) {
|
|
63
|
+
return 'oauth configuration present; ambient key override will apply after activation.';
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], string> = {
|
|
69
|
+
display: 'Display',
|
|
70
|
+
ui: 'UI',
|
|
71
|
+
provider: 'Provider',
|
|
72
|
+
subscriptions: 'Subscriptions',
|
|
73
|
+
behavior: 'Behavior',
|
|
74
|
+
storage: 'Storage',
|
|
75
|
+
permissions: 'Permissions',
|
|
76
|
+
mcp: 'MCP',
|
|
77
|
+
sandbox: 'Sandbox',
|
|
78
|
+
danger: 'Danger',
|
|
79
|
+
tools: 'Tools',
|
|
80
|
+
flags: 'Flags',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const SETTING_LABELS: Partial<Record<string, string>> = {
|
|
84
|
+
'ui.systemMessages': 'System Message Target',
|
|
85
|
+
'ui.operationalMessages': 'Operational Message Target',
|
|
86
|
+
'ui.wrfcMessages': 'WRFC Message Target',
|
|
87
|
+
'ui.voiceEnabled': 'Voice Surface',
|
|
88
|
+
'behavior.autoCompactThreshold': 'Auto-Compact %',
|
|
89
|
+
'behavior.staleContextWarnings': 'Context Warnings',
|
|
90
|
+
'behavior.returnContextMode': 'Return Context',
|
|
91
|
+
'behavior.guidanceMode': 'Guidance Mode',
|
|
92
|
+
'storage.secretPolicy': 'Secret Policy',
|
|
93
|
+
'sandbox.vmBackend': 'Sandbox Backend',
|
|
94
|
+
'sandbox.qemuBinary': 'QEMU Binary',
|
|
95
|
+
'sandbox.qemuImagePath': 'QEMU Image',
|
|
96
|
+
'sandbox.qemuExecWrapper': 'QEMU Wrapper',
|
|
97
|
+
'tools.llmProvider': 'Tool LLM Provider',
|
|
98
|
+
'tools.llmModel': 'Tool LLM Model',
|
|
99
|
+
'tools.autoHeal': 'Auto-Heal',
|
|
100
|
+
'tools.defaultTokenBudget': 'Default Token Budget',
|
|
101
|
+
'tools.hooksFile': 'Hooks File',
|
|
102
|
+
'helper.enabled': 'Helper Enabled',
|
|
103
|
+
'helper.globalProvider': 'Helper Provider',
|
|
104
|
+
'helper.globalModel': 'Helper Model',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export function getSettingLabel(entry: SettingEntry): string {
|
|
108
|
+
return SETTING_LABELS[entry.setting.key] ?? entry.setting.key.replace(/^[^.]+\./, '');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function describeUiRouting(value: string): string {
|
|
112
|
+
switch (value) {
|
|
113
|
+
case 'panel':
|
|
114
|
+
return 'render in panels only';
|
|
115
|
+
case 'conversation':
|
|
116
|
+
return 'render inline in conversation';
|
|
117
|
+
case 'both':
|
|
118
|
+
return 'render in both conversation and panels';
|
|
119
|
+
default:
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -10,123 +10,24 @@
|
|
|
10
10
|
* - Footer hints: [Tab] Category [↑↓] Navigate [Enter] Edit/Toggle [Esc] Close
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { Line } from '
|
|
13
|
+
import type { Line } from '../types/grid.ts';
|
|
14
14
|
import { ModalFactory } from './modal-factory.ts';
|
|
15
15
|
import type { SettingsModal, SettingEntry, FlagEntry, McpEntry, SubscriptionEntry } from '../input/settings-modal.ts';
|
|
16
16
|
import { SETTINGS_CATEGORIES } from '../input/settings-modal.ts';
|
|
17
|
-
import { fitDisplay, truncateDisplay } from '
|
|
17
|
+
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
18
18
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
19
19
|
import { getVisibleWindow } from './surface-layout.ts';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function valueColor(entry: SettingEntry): string {
|
|
34
|
-
if (!entry.isDefault) return '#00ffcc'; // cyan-green = modified
|
|
35
|
-
return '244'; // dim = default
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function flagStateColor(state: string, killed: boolean): string {
|
|
39
|
-
if (killed) return '#ef4444'; // red
|
|
40
|
-
if (state === 'enabled') return '#00ffcc'; // cyan-green
|
|
41
|
-
return '244'; // dim
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function mcpTrustColor(mode: McpEntry['trustMode']): string {
|
|
45
|
-
switch (mode) {
|
|
46
|
-
case 'allow-all':
|
|
47
|
-
return '#ef4444';
|
|
48
|
-
case 'ask-on-risk':
|
|
49
|
-
return '#eab308';
|
|
50
|
-
case 'constrained':
|
|
51
|
-
return '#00ffcc';
|
|
52
|
-
case 'blocked':
|
|
53
|
-
return '244';
|
|
54
|
-
default:
|
|
55
|
-
return '244';
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function subscriptionStateColor(state: SubscriptionEntry['state']): string {
|
|
60
|
-
switch (state) {
|
|
61
|
-
case 'active':
|
|
62
|
-
return '#00ffcc';
|
|
63
|
-
case 'pending':
|
|
64
|
-
return '#eab308';
|
|
65
|
-
case 'available':
|
|
66
|
-
return '#38bdf8';
|
|
67
|
-
default:
|
|
68
|
-
return '244';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function inferSubscriptionRouteReason(entry: SubscriptionEntry): string | undefined {
|
|
73
|
-
if (entry.routeReason?.trim()) return entry.routeReason;
|
|
74
|
-
if (entry.state === 'active' && entry.oauthConfigured) {
|
|
75
|
-
return 'ambient key override enabled for this provider.';
|
|
76
|
-
}
|
|
77
|
-
if (entry.state === 'pending' && entry.oauthConfigured) {
|
|
78
|
-
return 'oauth configuration present; ambient key override will apply after activation.';
|
|
79
|
-
}
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], string> = {
|
|
84
|
-
display: 'Display',
|
|
85
|
-
ui: 'UI',
|
|
86
|
-
provider: 'Provider',
|
|
87
|
-
subscriptions: 'Subscriptions',
|
|
88
|
-
behavior: 'Behavior',
|
|
89
|
-
storage: 'Storage',
|
|
90
|
-
permissions: 'Permissions',
|
|
91
|
-
mcp: 'MCP',
|
|
92
|
-
sandbox: 'Sandbox',
|
|
93
|
-
danger: 'Danger',
|
|
94
|
-
tools: 'Tools',
|
|
95
|
-
flags: 'Flags',
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const SETTING_LABELS: Partial<Record<string, string>> = {
|
|
99
|
-
'ui.systemMessages': 'System Message Target',
|
|
100
|
-
'ui.operationalMessages': 'Operational Message Target',
|
|
101
|
-
'ui.wrfcMessages': 'WRFC Message Target',
|
|
102
|
-
'ui.voiceEnabled': 'Voice Surface',
|
|
103
|
-
'behavior.autoCompactThreshold': 'Auto-Compact %',
|
|
104
|
-
'behavior.staleContextWarnings': 'Context Warnings',
|
|
105
|
-
'behavior.returnContextMode': 'Return Context',
|
|
106
|
-
'behavior.guidanceMode': 'Guidance Mode',
|
|
107
|
-
'storage.secretPolicy': 'Secret Policy',
|
|
108
|
-
'sandbox.vmBackend': 'Sandbox Backend',
|
|
109
|
-
'sandbox.qemuBinary': 'QEMU Binary',
|
|
110
|
-
'sandbox.qemuImagePath': 'QEMU Image',
|
|
111
|
-
'sandbox.qemuExecWrapper': 'QEMU Wrapper',
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
function getSettingLabel(entry: SettingEntry): string {
|
|
115
|
-
return SETTING_LABELS[entry.setting.key] ?? entry.setting.key.replace(/^[^.]+\./, '');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function describeUiRouting(value: string): string {
|
|
119
|
-
switch (value) {
|
|
120
|
-
case 'panel':
|
|
121
|
-
return 'render in panels only';
|
|
122
|
-
case 'conversation':
|
|
123
|
-
return 'render inline in conversation';
|
|
124
|
-
case 'both':
|
|
125
|
-
return 'render in both conversation and panels';
|
|
126
|
-
default:
|
|
127
|
-
return value;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
20
|
+
import {
|
|
21
|
+
formatValue,
|
|
22
|
+
valueColor,
|
|
23
|
+
flagStateColor,
|
|
24
|
+
mcpTrustColor,
|
|
25
|
+
subscriptionStateColor,
|
|
26
|
+
inferSubscriptionRouteReason,
|
|
27
|
+
CATEGORY_LABELS,
|
|
28
|
+
getSettingLabel,
|
|
29
|
+
describeUiRouting,
|
|
30
|
+
} from './settings-modal-helpers.ts';
|
|
130
31
|
|
|
131
32
|
// ---------------------------------------------------------------------------
|
|
132
33
|
// Renderer
|
|
@@ -161,6 +62,7 @@ export function renderSettingsModal(
|
|
|
161
62
|
const isSubscriptionsTab = SETTINGS_CATEGORIES[modal.categoryIndex] === 'subscriptions';
|
|
162
63
|
const isFlagsTab = SETTINGS_CATEGORIES[modal.categoryIndex] === 'flags';
|
|
163
64
|
const isUiTab = SETTINGS_CATEGORIES[modal.categoryIndex] === 'ui';
|
|
65
|
+
const isToolsTab = SETTINGS_CATEGORIES[modal.categoryIndex] === 'tools';
|
|
164
66
|
let persistentHelpers: import('./modal-factory.ts').ModalHelperRow[] | undefined;
|
|
165
67
|
sections.push({
|
|
166
68
|
type: 'text',
|
|
@@ -174,7 +76,9 @@ export function renderSettingsModal(
|
|
|
174
76
|
? 'Control shell presentation, including where operational and WRFC updates render across conversation and panels.'
|
|
175
77
|
: isFlagsTab
|
|
176
78
|
? 'Feature flags control staged or experimental behavior. Some changes may require restart.'
|
|
177
|
-
:
|
|
79
|
+
: isToolsTab
|
|
80
|
+
? 'Configure tool LLM routing and helper model. Provider and model fields are optional — empty means use the active provider.'
|
|
81
|
+
: 'Browse and adjust operator-facing runtime settings by category.',
|
|
178
82
|
style: { fg: '246', dim: true },
|
|
179
83
|
});
|
|
180
84
|
|
|
@@ -529,6 +433,138 @@ export function renderSettingsModal(
|
|
|
529
433
|
);
|
|
530
434
|
}
|
|
531
435
|
|
|
436
|
+
// ── Tools tab ─────────────────────────────────────────────────
|
|
437
|
+
if (isToolsTab) {
|
|
438
|
+
const toolsItems = modal.currentItems; // includes helper.* entries routed into tools group
|
|
439
|
+
|
|
440
|
+
if (toolsItems.length === 0) {
|
|
441
|
+
sections.push({
|
|
442
|
+
type: 'text',
|
|
443
|
+
content: '(no tool or helper settings available)',
|
|
444
|
+
style: { fg: '240', dim: true },
|
|
445
|
+
});
|
|
446
|
+
} else {
|
|
447
|
+
const labelW = Math.floor(contentW * 0.38);
|
|
448
|
+
const valW = Math.floor(contentW * 0.30);
|
|
449
|
+
const srcW = Math.max(0, contentW - labelW - valW - 4);
|
|
450
|
+
|
|
451
|
+
sections.push({
|
|
452
|
+
type: 'text',
|
|
453
|
+
content: `${fitDisplay('Setting', labelW)} ${fitDisplay('Value', valW)} Source`,
|
|
454
|
+
style: { fg: '240', dim: true },
|
|
455
|
+
});
|
|
456
|
+
sections.push({ type: 'separator' });
|
|
457
|
+
|
|
458
|
+
const window = getVisibleWindow(toolsItems.length, modal.selectedIndex, maxVisibleRows);
|
|
459
|
+
const visibleItems = toolsItems.slice(window.start, window.end);
|
|
460
|
+
|
|
461
|
+
// Render each entry as an individual text row so section headers can interleave.
|
|
462
|
+
// Section headers are emitted when the key prefix changes (tools.* vs helper.*).
|
|
463
|
+
let lastGroupPrefix = '';
|
|
464
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
465
|
+
const entry = visibleItems[i]!;
|
|
466
|
+
const isSelected = window.start + i === modal.selectedIndex;
|
|
467
|
+
const isEditing = isSelected && modal.editingMode;
|
|
468
|
+
const prefix = entry.setting.key.split('.')[0]!;
|
|
469
|
+
|
|
470
|
+
// Emit a section header when the group prefix changes
|
|
471
|
+
if (prefix !== lastGroupPrefix) {
|
|
472
|
+
lastGroupPrefix = prefix;
|
|
473
|
+
const sectionLabel = prefix === 'helper' ? '── Helper Model ──' : '── Tool LLM ──';
|
|
474
|
+
sections.push({
|
|
475
|
+
type: 'text',
|
|
476
|
+
content: fitDisplay(sectionLabel, contentW),
|
|
477
|
+
style: { fg: '243', dim: true },
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const label = getSettingLabel(entry);
|
|
482
|
+
const labelStr = fitDisplay(label, labelW);
|
|
483
|
+
|
|
484
|
+
let valueStr: string;
|
|
485
|
+
if (entry.setting.type === 'boolean') {
|
|
486
|
+
const boolVal = Boolean(entry.currentValue);
|
|
487
|
+
valueStr = isEditing ? `${modal.editBuffer}\u2588` : (boolVal ? '[on]' : '[off]');
|
|
488
|
+
} else if (isEditing) {
|
|
489
|
+
valueStr = `${modal.editBuffer}\u2588`;
|
|
490
|
+
} else {
|
|
491
|
+
valueStr = formatValue(entry);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const valStr = fitDisplay(valueStr, valW);
|
|
495
|
+
const sourceText = entry.effectiveSource ?? 'default';
|
|
496
|
+
const srcStr = fitDisplay(sourceText, srcW);
|
|
497
|
+
const rowLabel = `${labelStr} ${valStr} ${srcStr}`;
|
|
498
|
+
|
|
499
|
+
// Render as a single-item list so the ModalFactory applies selection highlight
|
|
500
|
+
sections.push({
|
|
501
|
+
type: 'list',
|
|
502
|
+
items: [{
|
|
503
|
+
label: rowLabel,
|
|
504
|
+
selected: isSelected,
|
|
505
|
+
style: isSelected ? undefined : { fg: valueColor(entry) },
|
|
506
|
+
}],
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (toolsItems.length > maxVisibleRows) {
|
|
511
|
+
sections.push({
|
|
512
|
+
type: 'text',
|
|
513
|
+
content: `[${window.start + 1}-${window.end} of ${toolsItems.length}]`,
|
|
514
|
+
style: { fg: '244', dim: true },
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Description of selected entry
|
|
519
|
+
const selected = modal.getSelected();
|
|
520
|
+
if (selected) {
|
|
521
|
+
sections.push({ type: 'separator' });
|
|
522
|
+
sections.push({
|
|
523
|
+
type: 'text',
|
|
524
|
+
content: truncateDisplay(selected.setting.description, contentW),
|
|
525
|
+
style: { fg: '246', dim: true },
|
|
526
|
+
});
|
|
527
|
+
if (selected.setting.type === 'boolean') {
|
|
528
|
+
sections.push({
|
|
529
|
+
type: 'text',
|
|
530
|
+
content: truncateDisplay(`Currently ${Boolean(selected.currentValue) ? 'enabled' : 'disabled'}. Press Enter or Space to toggle.`, contentW),
|
|
531
|
+
style: { fg: '#38bdf8', dim: true },
|
|
532
|
+
});
|
|
533
|
+
} else {
|
|
534
|
+
const emptyNote = selected.currentValue === '' || selected.currentValue === null || selected.currentValue === undefined
|
|
535
|
+
? ' (empty = use active provider default)'
|
|
536
|
+
: '';
|
|
537
|
+
sections.push({
|
|
538
|
+
type: 'text',
|
|
539
|
+
content: truncateDisplay(`Current: ${formatValue(selected)}${emptyNote}`, contentW),
|
|
540
|
+
style: { fg: '#38bdf8', dim: true },
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const hints = modal.editingMode
|
|
547
|
+
? ['[Enter] Confirm', '[Esc] Cancel']
|
|
548
|
+
: ['[Tab] Category', '[\u2191\u2193] Navigate', '[Enter] Toggle / Edit', '[Esc] Close'];
|
|
549
|
+
|
|
550
|
+
return ModalFactory.createModal(
|
|
551
|
+
{
|
|
552
|
+
title: 'Settings',
|
|
553
|
+
width: boxW,
|
|
554
|
+
margin: boxMargin,
|
|
555
|
+
targetContentRows,
|
|
556
|
+
tabs: SETTINGS_CATEGORIES.map((category, index) => ({
|
|
557
|
+
label: CATEGORY_LABELS[category],
|
|
558
|
+
active: index === modal.categoryIndex,
|
|
559
|
+
})),
|
|
560
|
+
sections,
|
|
561
|
+
hints,
|
|
562
|
+
helpers: persistentHelpers,
|
|
563
|
+
},
|
|
564
|
+
width,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
532
568
|
// ── Settings list ────────────────────────────────────────────
|
|
533
569
|
const items = modal.currentItems;
|
|
534
570
|
|