@pellux/goodvibes-agent 1.4.4 → 1.5.1
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 +22 -0
- package/README.md +52 -7
- package/dist/package/main.js +7491 -12253
- package/docs/README.md +2 -2
- package/docs/channels-remote-and-api.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/release-and-publishing.md +1 -1
- package/docs/tools-and-commands.md +5 -5
- package/package.json +4 -2
- package/release/live-verification/live-verification.json +13 -13
- package/release/live-verification/live-verification.md +15 -15
- package/release/performance-snapshot.json +2 -2
- package/release/release-notes.md +5 -7
- package/release/release-readiness.json +6 -6
- package/src/agent/behavior-discovery-summary.ts +4 -18
- package/src/agent/calendar-registry.ts +322 -0
- package/src/agent/competitive-feature-inventory.ts +268 -130
- package/src/agent/document-registry.ts +5 -1
- package/src/agent/email/email-service.ts +350 -0
- package/src/agent/email/imap-client.ts +596 -0
- package/src/agent/email/smtp-client.ts +453 -0
- package/src/agent/ics-calendar.ts +662 -0
- package/src/agent/ics-timezone.ts +172 -0
- package/src/agent/markdown-frontmatter.ts +31 -0
- package/src/agent/memory-safety.ts +1 -1
- package/src/agent/note-registry.ts +3 -9
- package/src/agent/persona-discovery.ts +3 -15
- package/src/agent/persona-registry.ts +1 -10
- package/src/agent/research-source-registry.ts +5 -1
- package/src/agent/routine-discovery.ts +3 -15
- package/src/agent/runtime-profile.ts +3 -0
- package/src/agent/skill-discovery.ts +3 -15
- package/src/agent/skill-draft-proposer.ts +203 -0
- package/src/agent/skill-draft-runner.ts +201 -0
- package/src/agent/skill-registry.ts +36 -1
- package/src/agent/skill-standard.ts +99 -0
- package/src/agent/vibe-file.ts +7 -22
- package/src/cli/completion.ts +1 -1
- package/src/cli/config-overrides.ts +10 -1
- package/src/cli/redaction.ts +17 -5
- package/src/config/provider-model.ts +2 -1
- package/src/config/secret-config.ts +13 -6
- package/src/core/activity-feed.ts +97 -0
- package/src/core/away-digest.ts +161 -0
- package/src/core/conversation-rendering.ts +7 -3
- package/src/core/conversation.ts +22 -15
- package/src/core/hardware-profile.ts +362 -0
- package/src/core/last-seen-store.ts +78 -0
- package/src/core/plain-language.ts +52 -0
- package/src/core/setup-incomplete-hint.ts +90 -0
- package/src/core/system-message-router.ts +38 -87
- package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
- package/src/input/agent-workspace-basic-command-editors.ts +39 -141
- package/src/input/agent-workspace-categories.ts +40 -125
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-host-category.ts +0 -5
- package/src/input/agent-workspace-local-editor-submission.ts +3 -1
- package/src/input/agent-workspace-navigation.ts +10 -3
- package/src/input/agent-workspace-onboarding-actions.ts +132 -0
- package/src/input/agent-workspace-onboarding-categories.ts +29 -26
- package/src/input/agent-workspace-onboarding-finish.ts +11 -4
- package/src/input/agent-workspace-onboarding-state.ts +111 -0
- package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
- package/src/input/agent-workspace-profile-editors.ts +155 -0
- package/src/input/agent-workspace-subscription-editor.ts +7 -0
- package/src/input/agent-workspace-types.ts +5 -1
- package/src/input/agent-workspace.ts +65 -39
- package/src/input/command-registry.ts +18 -5
- package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
- package/src/input/commands/agent-skills-runtime.ts +60 -3
- package/src/input/commands/calendar-runtime.ts +209 -0
- package/src/input/commands/channels-runtime.ts +1 -0
- package/src/input/commands/command-error.ts +9 -0
- package/src/input/commands/compat-runtime.ts +1 -0
- package/src/input/commands/config.ts +1 -0
- package/src/input/commands/conversation-runtime.ts +1 -1
- package/src/input/commands/delegation-runtime.ts +5 -6
- package/src/input/commands/email-runtime.ts +387 -0
- package/src/input/commands/experience-runtime.ts +17 -4
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +6 -1
- package/src/input/commands/knowledge-format.ts +73 -0
- package/src/input/commands/knowledge.ts +9 -74
- package/src/input/commands/local-provider-runtime.ts +2 -1
- package/src/input/commands/local-runtime.ts +10 -4
- package/src/input/commands/mcp-runtime.ts +7 -0
- package/src/input/commands/notify-runtime.ts +17 -1
- package/src/input/commands/onboarding-runtime.ts +1 -0
- package/src/input/commands/operator-actions-runtime.ts +1 -0
- package/src/input/commands/operator-runtime.ts +3 -0
- package/src/input/commands/personas-runtime.ts +1 -0
- package/src/input/commands/platform-access-runtime.ts +40 -17
- package/src/input/commands/product-runtime.ts +6 -1
- package/src/input/commands/provider-accounts-runtime.ts +3 -2
- package/src/input/commands/qrcode-runtime.ts +1 -0
- package/src/input/commands/routines-runtime.ts +1 -0
- package/src/input/commands/runtime-services.ts +0 -13
- package/src/input/commands/security-runtime.ts +1 -0
- package/src/input/commands/session-content.ts +1 -0
- package/src/input/commands/shell-core.ts +5 -2
- package/src/input/commands/subscription-runtime.ts +33 -9
- package/src/input/commands/support-bundle-runtime.ts +8 -1
- package/src/input/commands/tasks-runtime.ts +1 -0
- package/src/input/commands/tts-runtime.ts +1 -0
- package/src/input/commands/vibe-runtime.ts +1 -0
- package/src/input/commands.ts +4 -0
- package/src/input/feed-context-factory.ts +3 -12
- package/src/input/handler-command-route.ts +7 -60
- package/src/input/handler-feed-routes.ts +0 -194
- package/src/input/handler-feed.ts +2 -54
- package/src/input/handler-interactions.ts +0 -2
- package/src/input/handler-modal-stack.ts +0 -9
- package/src/input/handler-picker-routes.ts +24 -1
- package/src/input/handler-shortcuts.ts +11 -40
- package/src/input/handler-ui-state.ts +13 -0
- package/src/input/handler.ts +8 -35
- package/src/input/keybindings.ts +40 -17
- package/src/input/model-picker-local-fit.ts +130 -0
- package/src/input/shell-passthrough.ts +58 -0
- package/src/input/submission-router.ts +0 -5
- package/src/main.ts +129 -90
- package/src/renderer/activity-sidebar.ts +186 -0
- package/src/renderer/agent-workspace-context-lines.ts +5 -48
- package/src/renderer/agent-workspace-style.ts +1 -1
- package/src/renderer/agent-workspace.ts +14 -2
- package/src/renderer/compositor.ts +37 -125
- package/src/renderer/conversation-overlays.ts +3 -3
- package/src/renderer/help-overlay.ts +7 -5
- package/src/renderer/model-workspace.ts +101 -86
- package/src/{panels → renderer}/polish.ts +3 -3
- package/src/renderer/shell-surface.ts +4 -5
- package/src/renderer/status-token.ts +31 -11
- package/src/renderer/tab-strip.ts +1 -1
- package/src/renderer/tool-call.ts +7 -8
- package/src/renderer/tool-labels.ts +92 -0
- package/src/renderer/ui-factory.ts +48 -96
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +6 -15
- package/src/runtime/bootstrap-core.ts +34 -13
- package/src/runtime/bootstrap-hook-bridge.ts +3 -1
- package/src/runtime/bootstrap-shell.ts +3 -50
- package/src/runtime/bootstrap.ts +3 -4
- package/src/runtime/context.ts +1 -1
- package/src/runtime/diagnostics/panels/index.ts +0 -1
- package/src/runtime/diagnostics/panels/policy.ts +1 -1
- package/src/runtime/execution-ledger.ts +16 -1
- package/src/runtime/index.ts +6 -1
- package/src/runtime/onboarding/index.ts +1 -0
- package/src/runtime/onboarding/onboarding-state.ts +200 -0
- package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
- package/src/runtime/services.ts +16 -4
- package/src/runtime/terminal-output-guard.ts +7 -0
- package/src/runtime/tool-permission-safety.ts +1 -1
- package/src/runtime/ui/model-picker/data-provider.ts +1 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
- package/src/runtime/ui/model-picker/types.ts +2 -2
- package/src/runtime/ui/provider-health/data-provider.ts +3 -3
- package/src/runtime/ui/provider-health/types.ts +2 -2
- package/src/runtime/ui-services.ts +0 -2
- package/src/shell/agent-workspace-fullscreen.ts +0 -1
- package/src/shell/autonomy-surfacing.ts +272 -0
- package/src/shell/session-continuity-hints.ts +0 -6
- package/src/shell/startup-wiring.ts +221 -0
- package/src/shell/ui-openers.ts +18 -29
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +4 -4
- package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
- package/src/tools/agent-harness-command-runner.ts +6 -1
- package/src/tools/agent-harness-document-ops.ts +26 -53
- package/src/tools/agent-harness-execution-history.ts +1 -13
- package/src/tools/agent-harness-execution-posture.ts +0 -15
- package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
- package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
- package/src/tools/agent-harness-metadata.ts +16 -0
- package/src/tools/agent-harness-mode-catalog.ts +2 -9
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
- package/src/tools/agent-harness-tool-schema.ts +13 -14
- package/src/tools/agent-harness-tool.ts +27 -23
- package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
- package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
- package/src/tools/agent-workspace-tool.ts +2 -33
- package/src/utils/terminal-width.ts +9 -3
- package/src/version.ts +1 -1
- package/src/input/agent-workspace-panel-route.ts +0 -44
- package/src/input/panel-integration-actions.ts +0 -26
- package/src/panels/approval-panel.ts +0 -149
- package/src/panels/automation-control-panel.ts +0 -212
- package/src/panels/base-panel.ts +0 -254
- package/src/panels/builtin/agent.ts +0 -58
- package/src/panels/builtin/knowledge.ts +0 -26
- package/src/panels/builtin/operations.ts +0 -121
- package/src/panels/builtin/session.ts +0 -138
- package/src/panels/builtin/shared.ts +0 -275
- package/src/panels/builtin/usage.ts +0 -21
- package/src/panels/builtin-panels.ts +0 -23
- package/src/panels/confirm-state.ts +0 -61
- package/src/panels/context-visualizer-panel.ts +0 -204
- package/src/panels/cost-tracker-panel.ts +0 -444
- package/src/panels/docs-panel.ts +0 -285
- package/src/panels/index.ts +0 -25
- package/src/panels/knowledge-panel.ts +0 -417
- package/src/panels/memory-panel.ts +0 -226
- package/src/panels/panel-list-panel.ts +0 -464
- package/src/panels/panel-manager.ts +0 -570
- package/src/panels/provider-accounts-panel.ts +0 -233
- package/src/panels/provider-health-domains.ts +0 -208
- package/src/panels/provider-health-panel.ts +0 -720
- package/src/panels/provider-health-tracker.ts +0 -115
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/qr-panel.ts +0 -207
- package/src/panels/schedule-panel.ts +0 -321
- package/src/panels/scrollable-list-panel.ts +0 -491
- package/src/panels/search-focus.ts +0 -32
- package/src/panels/security-panel.ts +0 -295
- package/src/panels/session-browser-panel.ts +0 -395
- package/src/panels/session-maintenance.ts +0 -125
- package/src/panels/subscription-panel.ts +0 -263
- package/src/panels/system-messages-panel.ts +0 -230
- package/src/panels/tasks-panel.ts +0 -344
- package/src/panels/thinking-panel.ts +0 -304
- package/src/panels/token-budget-panel.ts +0 -475
- package/src/panels/tool-inspector-panel.ts +0 -436
- package/src/panels/types.ts +0 -54
- package/src/renderer/panel-composite.ts +0 -158
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/renderer/panel-workspace-bar.ts +0 -42
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/tools/agent-harness-panel-metadata.ts +0 -211
- /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
|
@@ -158,6 +158,8 @@ function editorPurposeLabel(editorKind: AgentWorkspaceEditorKind): string {
|
|
|
158
158
|
case 'skill-search': return 'Search skills';
|
|
159
159
|
case 'skill-show': return 'Show skill';
|
|
160
160
|
case 'skill-discovery-import': return 'Import skill files';
|
|
161
|
+
case 'skill-standard-import': return 'Import shared skill';
|
|
162
|
+
case 'skill-standard-export': return 'Export skill to share';
|
|
161
163
|
case 'routine': return 'Edit routine';
|
|
162
164
|
case 'routine-search': return 'Search routines';
|
|
163
165
|
case 'routine-show': return 'Show routine';
|
|
@@ -669,8 +671,18 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
|
|
|
669
671
|
if (workspace.lastActionResult) {
|
|
670
672
|
rows.push({ text: '' });
|
|
671
673
|
rows.push({ text: `${onboarding ? 'Result' : 'Action Result'}: ${workspace.lastActionResult.title}`, fg: actionResultColor(workspace.lastActionResult), bold: true });
|
|
672
|
-
|
|
673
|
-
|
|
674
|
+
// For recap results, skip the detail body — the checkmarked lines below carry
|
|
675
|
+
// the full content. Rendering detail AND lines would duplicate every line.
|
|
676
|
+
if (workspace.lastActionResult.kind !== 'recap') {
|
|
677
|
+
for (const line of wrapText(workspace.lastActionResult.detail, Math.max(1, width - 2))) {
|
|
678
|
+
rows.push({ text: ` ${line}`, fg: PALETTE.text });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (workspace.lastActionResult.kind === 'recap' && workspace.lastActionResult.lines?.length) {
|
|
682
|
+
rows.push({ text: '' });
|
|
683
|
+
for (const line of workspace.lastActionResult.lines) {
|
|
684
|
+
rows.push({ text: ` ✔ ${line}`, fg: PALETTE.good });
|
|
685
|
+
}
|
|
674
686
|
}
|
|
675
687
|
if (!onboarding && workspace.lastActionResult.command) {
|
|
676
688
|
rows.push({ text: ` Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
|
|
@@ -17,25 +17,9 @@ export interface SearchInfo {
|
|
|
17
17
|
viewportStartY: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/** Top pane: tab bar */
|
|
24
|
-
topTabBar?: Line;
|
|
25
|
-
/** Top pane: panel content lines */
|
|
26
|
-
topContent: Line[];
|
|
27
|
-
/** Whether the top pane is focused (affects separator color) */
|
|
28
|
-
topFocused: boolean;
|
|
29
|
-
/** Bottom pane tab bar. Undefined = no bottom pane. */
|
|
30
|
-
bottomTabBar?: Line;
|
|
31
|
-
/** Bottom pane content lines. Undefined = no bottom pane. */
|
|
32
|
-
bottomContent?: Line[];
|
|
33
|
-
/** Whether the bottom pane is focused */
|
|
34
|
-
bottomFocused?: boolean;
|
|
35
|
-
/** Separator between left and right panel area */
|
|
36
|
-
separator: boolean;
|
|
37
|
-
/** Ratio of panel height for the top pane (0–1). Only used when bottom pane is present. */
|
|
38
|
-
verticalSplitRatio: number;
|
|
20
|
+
export interface SidebarCompositeData {
|
|
21
|
+
/** Pre-rendered sidebar lines, one per viewport row. */
|
|
22
|
+
lines: Line[];
|
|
39
23
|
}
|
|
40
24
|
|
|
41
25
|
export interface CompositeRequest {
|
|
@@ -47,8 +31,8 @@ export interface CompositeRequest {
|
|
|
47
31
|
forceFullRedraw?: boolean;
|
|
48
32
|
selection?: SelectionInfo;
|
|
49
33
|
search?: SearchInfo;
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
sidebar?: SidebarCompositeData;
|
|
35
|
+
sidebarWidth?: number; // width of the right sidebar area (0 = no sidebar)
|
|
52
36
|
}
|
|
53
37
|
|
|
54
38
|
/**
|
|
@@ -75,7 +59,7 @@ export class Compositor {
|
|
|
75
59
|
}
|
|
76
60
|
|
|
77
61
|
public composite(params: CompositeRequest): void {
|
|
78
|
-
const { width, height, header, viewport, footer, forceFullRedraw, selection, search,
|
|
62
|
+
const { width, height, header, viewport, footer, forceFullRedraw, selection, search, sidebar, sidebarWidth } = params;
|
|
79
63
|
const previousFrontBuffer = forceFullRedraw ? null : this.frontBuffer;
|
|
80
64
|
if (forceFullRedraw) this.diffEngine.reset();
|
|
81
65
|
|
|
@@ -87,9 +71,9 @@ export class Compositor {
|
|
|
87
71
|
}
|
|
88
72
|
const newBuffer = this.backBuffer;
|
|
89
73
|
|
|
90
|
-
const
|
|
91
|
-
const leftWidth =
|
|
92
|
-
const sepX =
|
|
74
|
+
const hasSidebar = sidebar !== undefined && sidebarWidth !== undefined && sidebarWidth > 0;
|
|
75
|
+
const leftWidth = hasSidebar ? Math.max(1, width - sidebarWidth - 1) : width;
|
|
76
|
+
const sepX = hasSidebar ? leftWidth : -1;
|
|
93
77
|
|
|
94
78
|
// 1. Draw Header — always full width
|
|
95
79
|
header.forEach((line, i) => newBuffer.blitLine(i, line));
|
|
@@ -102,41 +86,17 @@ export class Compositor {
|
|
|
102
86
|
const lineCount = selection?.lineCount ?? 0;
|
|
103
87
|
const offset = Math.max(0, vHeight - lineCount);
|
|
104
88
|
|
|
105
|
-
|
|
106
|
-
// When both top and bottom panes are visible, the panel area is split:
|
|
107
|
-
// row 0: workspace tab bar
|
|
108
|
-
// row 1: top tab bar
|
|
109
|
-
// rows 2..topH+1: top content
|
|
110
|
-
// row topH+2: horizontal separator (───)
|
|
111
|
-
// row topH+3: bottom tab bar
|
|
112
|
-
// rows topH+4..end: bottom content
|
|
113
|
-
const hasBottomPane = hasPanel && panel!.bottomTabBar !== undefined;
|
|
114
|
-
let topPaneHeight = 0; // number of content rows in top pane
|
|
115
|
-
let bottomPaneHeight = 0;
|
|
116
|
-
let hSepRow = -1; // viewport row of the horizontal separator
|
|
117
|
-
if (hasPanel && hasBottomPane) {
|
|
118
|
-
const panelAreaRows = Math.max(0, vHeight - 1); // subtract workspace tab bar
|
|
119
|
-
// top: 1 (tabbar) + topContent rows; bottom: 1 (sep) + 1 (tabbar) + bottomContent
|
|
120
|
-
const contentRows = Math.max(0, panelAreaRows - 3); // subtract top-tabbar + h-sep + bottom-tabbar
|
|
121
|
-
topPaneHeight = Math.max(1, Math.floor(contentRows * panel!.verticalSplitRatio));
|
|
122
|
-
bottomPaneHeight = Math.max(1, contentRows - topPaneHeight);
|
|
123
|
-
hSepRow = 2 + topPaneHeight; // workspace bar + top tab bar + top content rows
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const sepFg = hasPanel && panel!.separator
|
|
127
|
-
? (panel!.topFocused || panel!.bottomFocused ? '244' : '238')
|
|
128
|
-
: '238';
|
|
89
|
+
const sepFg = '238';
|
|
129
90
|
|
|
130
91
|
viewport.forEach((line, i) => {
|
|
131
92
|
const screenY = viewportStartY + i;
|
|
132
93
|
if (screenY >= height) return;
|
|
133
94
|
|
|
134
|
-
if (!
|
|
135
|
-
// No
|
|
95
|
+
if (!hasSidebar) {
|
|
96
|
+
// No sidebar: existing fast path
|
|
136
97
|
newBuffer.blitLine(screenY, line);
|
|
137
98
|
} else {
|
|
138
|
-
//
|
|
139
|
-
// Left side: viewport cells 0..leftWidth-1
|
|
99
|
+
// Sidebar active: left side gets viewport cells 0..leftWidth-1
|
|
140
100
|
for (let x = 0; x < leftWidth; x++) {
|
|
141
101
|
const cell = line[x];
|
|
142
102
|
if (cell !== undefined) {
|
|
@@ -151,79 +111,21 @@ export class Compositor {
|
|
|
151
111
|
}
|
|
152
112
|
}
|
|
153
113
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// Separator column (vertical bar between left and panel area)
|
|
157
|
-
if (p.separator) {
|
|
158
|
-
newBuffer.setCell(sepX, screenY, createStyledCell('│', { fg: sepFg }));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const panelStartX = sepX + 1;
|
|
162
|
-
const clearPanelRemainder = (fromX = 0) => {
|
|
163
|
-
for (let x = Math.max(0, fromX); x < panelWidth; x++) {
|
|
164
|
-
newBuffer.setCell(panelStartX + x, screenY, createEmptyCell());
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
const drawPanelLine = (panelLine: Line | undefined) => {
|
|
168
|
-
if (panelLine === undefined) {
|
|
169
|
-
clearPanelRemainder();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const limit = Math.min(panelLine.length, panelWidth);
|
|
173
|
-
for (let x = 0; x < limit; x++) {
|
|
174
|
-
const cell = panelLine[x];
|
|
175
|
-
if (cell !== undefined) {
|
|
176
|
-
newBuffer.setCell(panelStartX + x, screenY, cell);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
clearPanelRemainder(limit);
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
if (!hasBottomPane) {
|
|
183
|
-
// --- Single pane mode ---
|
|
184
|
-
// viewport row 0 → workspace bar, viewport rows 1+ → panel content
|
|
185
|
-
const panelLine = i === 0 ? p.workspaceBar : p.topContent[i - 1];
|
|
186
|
-
drawPanelLine(panelLine);
|
|
187
|
-
} else {
|
|
188
|
-
// --- Two pane mode ---
|
|
189
|
-
// Row layout (by viewport row i):
|
|
190
|
-
// i = 0: workspace tab bar
|
|
191
|
-
// i = 1: top tab bar
|
|
192
|
-
// 2 <= i <= topPaneHeight+1: top content[i-2]
|
|
193
|
-
// i = hSepRow: horizontal separator
|
|
194
|
-
// i = hSepRow+1: bottom tab bar
|
|
195
|
-
// i >= hSepRow+2: bottom content[i - (hSepRow+2)]
|
|
196
|
-
let panelLine: Line | undefined;
|
|
197
|
-
|
|
198
|
-
if (i === 0) {
|
|
199
|
-
panelLine = p.workspaceBar;
|
|
200
|
-
} else if (i === 1) {
|
|
201
|
-
panelLine = p.topTabBar;
|
|
202
|
-
} else if (i <= topPaneHeight + 1) {
|
|
203
|
-
panelLine = p.topContent[i - 2];
|
|
204
|
-
} else if (i === hSepRow) {
|
|
205
|
-
// Horizontal separator between the two panes
|
|
206
|
-
// Render ─ chars across the panel width
|
|
207
|
-
const focusFg = p.bottomFocused ? '36' : '238'; // cyan if bottom pane focused
|
|
208
|
-
for (let x = 0; x < panelWidth; x++) {
|
|
209
|
-
newBuffer.setCell(panelStartX + x, screenY, createStyledCell('─', { fg: focusFg }));
|
|
210
|
-
}
|
|
211
|
-
// Also update the separator column char to T-junction (├):
|
|
212
|
-
// ├ connects the vertical left-separator with the horizontal pane divider,
|
|
213
|
-
// forming a clean T-shaped joint at the split point.
|
|
214
|
-
if (p.separator) {
|
|
215
|
-
newBuffer.setCell(sepX, screenY, createStyledCell('├', { fg: focusFg }));
|
|
216
|
-
}
|
|
217
|
-
} else if (i === hSepRow + 1) {
|
|
218
|
-
panelLine = p.bottomTabBar;
|
|
219
|
-
} else {
|
|
220
|
-
panelLine = p.bottomContent?.[i - (hSepRow + 2)];
|
|
221
|
-
}
|
|
114
|
+
// Separator column (vertical bar between conversation and sidebar)
|
|
115
|
+
newBuffer.setCell(sepX, screenY, createStyledCell('│', { fg: sepFg }));
|
|
222
116
|
|
|
223
|
-
|
|
224
|
-
|
|
117
|
+
const sidebarStartX = sepX + 1;
|
|
118
|
+
const sidebarLine = sidebar!.lines[i];
|
|
119
|
+
const limit = sidebarLine === undefined ? 0 : Math.min(sidebarLine.length, sidebarWidth);
|
|
120
|
+
for (let x = 0; x < limit; x++) {
|
|
121
|
+
const cell = sidebarLine![x];
|
|
122
|
+
if (cell !== undefined) {
|
|
123
|
+
newBuffer.setCell(sidebarStartX + x, screenY, cell);
|
|
225
124
|
}
|
|
226
125
|
}
|
|
126
|
+
for (let x = limit; x < sidebarWidth; x++) {
|
|
127
|
+
newBuffer.setCell(sidebarStartX + x, screenY, createEmptyCell());
|
|
128
|
+
}
|
|
227
129
|
}
|
|
228
130
|
|
|
229
131
|
// Apply Selection Highlighting Overlay (left side only)
|
|
@@ -260,12 +162,22 @@ export class Compositor {
|
|
|
260
162
|
newBuffer.blitLine(screenY, createEmptyLine(width));
|
|
261
163
|
}
|
|
262
164
|
|
|
263
|
-
// Draw separator on
|
|
264
|
-
if (
|
|
165
|
+
// Draw the separator and sidebar on viewport rows past the conversation content
|
|
166
|
+
if (hasSidebar) {
|
|
265
167
|
for (let i = viewport.length; i < vHeight; i++) {
|
|
266
168
|
const screenY = viewportStartY + i;
|
|
267
169
|
if (screenY >= height) break;
|
|
268
170
|
newBuffer.setCell(sepX, screenY, createStyledCell('│', { fg: sepFg }));
|
|
171
|
+
const sidebarStartX = sepX + 1;
|
|
172
|
+
const sidebarLine = sidebar!.lines[i];
|
|
173
|
+
const limit = sidebarLine === undefined ? 0 : Math.min(sidebarLine.length, sidebarWidth!);
|
|
174
|
+
for (let x = 0; x < limit; x++) {
|
|
175
|
+
const cell = sidebarLine![x];
|
|
176
|
+
if (cell !== undefined) newBuffer.setCell(sidebarStartX + x, screenY, cell);
|
|
177
|
+
}
|
|
178
|
+
for (let x = limit; x < sidebarWidth!; x++) {
|
|
179
|
+
newBuffer.setCell(sidebarStartX + x, screenY, createEmptyCell());
|
|
180
|
+
}
|
|
269
181
|
}
|
|
270
182
|
}
|
|
271
183
|
|
|
@@ -54,11 +54,11 @@ export function applyConversationOverlays(
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (input.searchManager.active) {
|
|
57
|
-
next
|
|
57
|
+
next = [...next, ...renderSearchOverlay(input.searchManager, conversationWidth)];
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (input.historySearch.active) {
|
|
61
|
-
next
|
|
61
|
+
next = [...next, ...renderHistorySearchOverlay(input.historySearch, conversationWidth)];
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
if (input.processModal.active) {
|
|
@@ -102,7 +102,7 @@ export function applyConversationOverlays(
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
if (input.helpOverlayActive) {
|
|
105
|
-
const lines = renderHelpOverlay(conversationWidth, keybindingsManager, commandRegistry.
|
|
105
|
+
const lines = renderHelpOverlay(conversationWidth, keybindingsManager, commandRegistry.getVisible(), input.helpScrollOffset, viewportHeight);
|
|
106
106
|
next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -15,7 +15,7 @@ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
15
15
|
const FEATURED_HELP_COMMANDS: Array<[name: string, argHint: string, desc: string]> = [
|
|
16
16
|
['agent', '', 'Open workspace; press / there to search every action'],
|
|
17
17
|
['agent', 'knowledge', 'Open isolated Agent Knowledge workspace actions'],
|
|
18
|
-
['agent', 'voice
|
|
18
|
+
['agent', 'voice', 'Open voice, image, and media workspace actions'],
|
|
19
19
|
['setup', '', 'Open the Agent workspace'],
|
|
20
20
|
['knowledge', 'status', 'Inspect isolated Agent Knowledge readiness'],
|
|
21
21
|
['memory', '', 'Manage Agent-local memory records'],
|
|
@@ -55,6 +55,7 @@ const HELP_OVERLAY_STATIC_TEXT = [
|
|
|
55
55
|
'Quick Start',
|
|
56
56
|
'Available Slash Commands',
|
|
57
57
|
'More Commands',
|
|
58
|
+
'Hidden power commands still work — run /commands for the full catalog.',
|
|
58
59
|
'Essentials',
|
|
59
60
|
'Show this help overlay',
|
|
60
61
|
'Keyboard shortcut reference',
|
|
@@ -158,7 +159,7 @@ export function renderHelpOverlay(
|
|
|
158
159
|
' ' + '\u2500'.repeat(40),
|
|
159
160
|
` ${'?'.padEnd(20)} Toggle help`,
|
|
160
161
|
` ${'/shortcuts'.padEnd(20)} Full keyboard shortcuts`,
|
|
161
|
-
` ${kb('
|
|
162
|
+
` ${kb('workspace-picker').padEnd(20)} Open the Agent operator workspace`,
|
|
162
163
|
` ${'Workspace /'.padEnd(20)} Search all Agent workspace actions`,
|
|
163
164
|
` ${'Workspace Enter'.padEnd(20)} Open selected action or form`,
|
|
164
165
|
'',
|
|
@@ -234,6 +235,7 @@ export function renderHelpOverlay(
|
|
|
234
235
|
commandRows.push(` ${nameCol} ${cmd.description}`);
|
|
235
236
|
}
|
|
236
237
|
}
|
|
238
|
+
commandRows.push('', ' Hidden power commands still work — run /commands for the full catalog.');
|
|
237
239
|
} else if (!hasCommand('help')) {
|
|
238
240
|
commandRows.push('', ' Essentials', ' ' + '\u2500'.repeat(40));
|
|
239
241
|
commandRows.push(' /help Show this help overlay');
|
|
@@ -330,9 +332,9 @@ export function renderShortcutsOverlay(
|
|
|
330
332
|
' Workspace',
|
|
331
333
|
' ' + '\u2500'.repeat(40),
|
|
332
334
|
row('Tab', 'Swap focus between input and active Agent workspace'),
|
|
333
|
-
row(kb('
|
|
334
|
-
row(kb('
|
|
335
|
-
row(kb('
|
|
335
|
+
row(kb('workspace-picker'), 'Open the Agent operator workspace'),
|
|
336
|
+
row(kb('workspace-tab-next'), 'Cycle Agent workspace category forward'),
|
|
337
|
+
row(kb('workspace-tab-prev'), 'Cycle Agent workspace category backward'),
|
|
336
338
|
'',
|
|
337
339
|
` Config: /keybindings to list and customize`,
|
|
338
340
|
];
|
|
@@ -8,10 +8,24 @@
|
|
|
8
8
|
import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
9
9
|
import type { ModelPickerModal } from '../input/model-picker.ts';
|
|
10
10
|
import type { ModelPickerTargetInfo } from '../input/model-picker.ts';
|
|
11
|
+
import { isLocalFitRecommendation, isProviderSignInRow, LOCAL_REC_PROVIDER } from '../input/model-picker-local-fit.ts';
|
|
12
|
+
import { estimateModelBytes, fitAssessment, fitVerdictLabel, paramCountFromModel, readHardwareProfileSync, REPRESENTATIVE_7B_PARAMS } from '../core/hardware-profile.ts';
|
|
11
13
|
import type { Line } from '../types/grid.ts';
|
|
12
|
-
import {
|
|
14
|
+
import { createStyledCell } from '../types/grid.ts';
|
|
13
15
|
import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
|
|
14
16
|
import { GLYPHS, UI_TONES } from './ui-primitives.ts';
|
|
17
|
+
import {
|
|
18
|
+
clamp,
|
|
19
|
+
clipDisplay,
|
|
20
|
+
contentLine,
|
|
21
|
+
fillRange,
|
|
22
|
+
makeLine,
|
|
23
|
+
padDisplay,
|
|
24
|
+
stableWindow,
|
|
25
|
+
writeText,
|
|
26
|
+
borderLine as primsBorderLine,
|
|
27
|
+
drawVerticalRule,
|
|
28
|
+
} from './fullscreen-primitives.ts';
|
|
15
29
|
|
|
16
30
|
const PALETTE = {
|
|
17
31
|
border: '#64748b',
|
|
@@ -58,98 +72,30 @@ const MODEL_WORKSPACE_CONTEXT_CAP_INPUT_HELP = 'Type digits to set a cap. Enter
|
|
|
58
72
|
const MODEL_WORKSPACE_FOOTER_SEARCH_ACTIVE = 'Typing filters search; Esc clears search';
|
|
59
73
|
const MODEL_WORKSPACE_FOOTER_SEARCH_INACTIVE = '/ search';
|
|
60
74
|
const MODEL_WORKSPACE_FOOTER_CONTROLS = 'Up/Down navigate • Left/Right pane • Enter select • <search> • Tab price • C caps • A available • B benchmark • G group • Esc close';
|
|
75
|
+
const MODEL_WORKSPACE_LOCAL_ONLY_HEADER = 'No provider signed in — these run on your machine';
|
|
76
|
+
const MODEL_WORKSPACE_LOCAL_ONLY_SIGN_IN = 'Sign in instead: select "Sign in to a provider" in the list below to connect a cloud or local provider.';
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
fg: cell.fg,
|
|
71
|
-
bg,
|
|
72
|
-
bold: cell.bold,
|
|
73
|
-
dim: cell.dim,
|
|
74
|
-
underline: cell.underline,
|
|
75
|
-
italic: cell.italic,
|
|
76
|
-
strikethrough: cell.strikethrough,
|
|
77
|
-
link: cell.link,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function makeLine(width: number, bg = ''): Line {
|
|
83
|
-
const line = createEmptyLine(width);
|
|
84
|
-
if (bg) fillRange(line, 0, width - 1, bg);
|
|
85
|
-
return line;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function writeText(line: Line, startX: number, maxWidth: number, text: string, style: Partial<Omit<Line[number], 'char'>> = {}): void {
|
|
89
|
-
let x = startX;
|
|
90
|
-
let used = 0;
|
|
91
|
-
for (const ch of text) {
|
|
92
|
-
const width = getDisplayWidth(ch);
|
|
93
|
-
if (width <= 0) continue;
|
|
94
|
-
if (used + width > maxWidth || x >= line.length) break;
|
|
95
|
-
line[x] = createStyledCell(ch, style);
|
|
96
|
-
if (width > 1 && x + 1 < line.length) {
|
|
97
|
-
line[x + 1] = createStyledCell(' ', style);
|
|
98
|
-
}
|
|
99
|
-
x += width;
|
|
100
|
-
used += width;
|
|
101
|
-
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns true when every model in the picker's list is a synthetic local
|
|
80
|
+
* fit recommendation (no real credentials configured).
|
|
81
|
+
*/
|
|
82
|
+
function isLocalOnlyList(picker: ModelPickerModal): boolean {
|
|
83
|
+
if (picker.mode !== 'model') return false;
|
|
84
|
+
const models = picker.models;
|
|
85
|
+
return models.length > 0 && models.every((m) => m.provider === LOCAL_REC_PROVIDER || isLocalFitRecommendation(m));
|
|
102
86
|
}
|
|
103
87
|
|
|
88
|
+
// Local wrappers that forward PALETTE.border as the fg/borderFg default, keeping
|
|
89
|
+
// the same visual output as the deleted local copies.
|
|
104
90
|
function borderLine(width: number, left: string, fill: string, right: string): Line {
|
|
105
|
-
|
|
106
|
-
if (width <= 0) return line;
|
|
107
|
-
line[0] = createStyledCell(left, { fg: PALETTE.border });
|
|
108
|
-
for (let x = 1; x < width - 1; x += 1) {
|
|
109
|
-
line[x] = createStyledCell(fill, { fg: PALETTE.border });
|
|
110
|
-
}
|
|
111
|
-
if (width > 1) line[width - 1] = createStyledCell(right, { fg: PALETTE.border });
|
|
112
|
-
return line;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function contentLine(width: number, bg: string): Line {
|
|
116
|
-
const line = makeLine(width, bg);
|
|
117
|
-
if (width > 0) line[0] = createStyledCell(GLYPHS.frame.vertical, { fg: PALETTE.border });
|
|
118
|
-
if (width > 1) line[width - 1] = createStyledCell(GLYPHS.frame.vertical, { fg: PALETTE.border });
|
|
119
|
-
return line;
|
|
91
|
+
return primsBorderLine(width, left, fill, right, PALETTE.border);
|
|
120
92
|
}
|
|
121
93
|
|
|
94
|
+
// drawVertical intentionally keeps the original guard (x<=0) which differs from
|
|
95
|
+
// drawVerticalRule in fullscreen-primitives (x<0). Callers rely on the <=0 skip.
|
|
122
96
|
function drawVertical(line: Line, x: number, bg = ''): void {
|
|
123
97
|
if (x <= 0 || x >= line.length - 1) return;
|
|
124
|
-
line
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function clipDisplay(text: string, width: number): string {
|
|
128
|
-
if (width <= 0) return '';
|
|
129
|
-
let used = 0;
|
|
130
|
-
let output = '';
|
|
131
|
-
for (const ch of text) {
|
|
132
|
-
const chWidth = getDisplayWidth(ch);
|
|
133
|
-
if (chWidth <= 0) continue;
|
|
134
|
-
if (used + chWidth > width) break;
|
|
135
|
-
output += ch;
|
|
136
|
-
used += chWidth;
|
|
137
|
-
}
|
|
138
|
-
return output;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function padDisplay(text: string, width: number): string {
|
|
142
|
-
const clipped = clipDisplay(text, width);
|
|
143
|
-
return clipped + ' '.repeat(Math.max(0, width - getDisplayWidth(clipped)));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function stableWindow(total: number, selected: number, visible: number): { start: number; end: number } {
|
|
147
|
-
if (total <= 0 || visible <= 0) return { start: 0, end: 0 };
|
|
148
|
-
const clamped = clamp(selected, 0, total - 1);
|
|
149
|
-
const half = Math.floor(visible / 2);
|
|
150
|
-
const maxStart = Math.max(0, total - visible);
|
|
151
|
-
const start = clamp(clamped - half, 0, maxStart);
|
|
152
|
-
return { start, end: Math.min(total, start + visible) };
|
|
98
|
+
drawVerticalRule(line, x, PALETTE.border, bg);
|
|
153
99
|
}
|
|
154
100
|
|
|
155
101
|
function formatContext(value: number | undefined): string {
|
|
@@ -241,6 +187,8 @@ export function renderModelWorkspacePackageText(): string {
|
|
|
241
187
|
'Detected context: <context>',
|
|
242
188
|
'Override: <cap>',
|
|
243
189
|
MODEL_WORKSPACE_CONTEXT_CAP_INPUT_HELP,
|
|
190
|
+
MODEL_WORKSPACE_LOCAL_ONLY_HEADER,
|
|
191
|
+
MODEL_WORKSPACE_LOCAL_ONLY_SIGN_IN,
|
|
244
192
|
'Focus targets',
|
|
245
193
|
'Focus list',
|
|
246
194
|
modelWorkspaceFooterControls(MODEL_WORKSPACE_FOOTER_SEARCH_ACTIVE),
|
|
@@ -248,12 +196,66 @@ export function renderModelWorkspacePackageText(): string {
|
|
|
248
196
|
].join('\n');
|
|
249
197
|
}
|
|
250
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Returns a plain-language hardware fit line for a local model, or an empty
|
|
201
|
+
* string when the model is not local or when the verdict is unknown.
|
|
202
|
+
* Uses the process-level cached hardware probe — never probes per render.
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Format a param count (e.g. 70e9) as a human-readable size token (e.g. '70B', '1.5B').
|
|
206
|
+
* Used to build the sizeDescriptor passed to fitVerdictLabel.
|
|
207
|
+
*/
|
|
208
|
+
function formatParamDescriptor(params: number): string {
|
|
209
|
+
const b = params / 1e9;
|
|
210
|
+
// Avoid floating-point noise: round to 1 decimal, strip trailing .0
|
|
211
|
+
const rounded = Math.round(b * 10) / 10;
|
|
212
|
+
return rounded === Math.floor(rounded) ? `${Math.floor(rounded)}B` : `${rounded}B`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function modelHardwareFitLine(model: ModelDefinition): string {
|
|
216
|
+
const provider = (model.provider ?? '').toLowerCase();
|
|
217
|
+
// Anchor on known local-provider identities only; cloud providers that happen
|
|
218
|
+
// to serve Llama/local-named models (Groq 'llama-3.1-70b', Together
|
|
219
|
+
// 'meta-llama/...') are NOT local and must not be annotated.
|
|
220
|
+
const isLocal =
|
|
221
|
+
provider === 'ollama' ||
|
|
222
|
+
provider === 'vllm' ||
|
|
223
|
+
provider === 'llama-cpp' ||
|
|
224
|
+
provider === 'llama.cpp' ||
|
|
225
|
+
provider === 'lm-studio' ||
|
|
226
|
+
provider === 'lmstudio' ||
|
|
227
|
+
provider === 'localai' ||
|
|
228
|
+
provider === 'tgi' ||
|
|
229
|
+
provider === 'local-openai' ||
|
|
230
|
+
provider === 'openai-compatible-local';
|
|
231
|
+
if (!isLocal) return '';
|
|
232
|
+
// Use the actual param count from the model id/displayName when parseable;
|
|
233
|
+
// fall back to the shared 7B representative constant otherwise.
|
|
234
|
+
const parsedParams = paramCountFromModel(model);
|
|
235
|
+
const params = parsedParams ?? REPRESENTATIVE_7B_PARAMS;
|
|
236
|
+
const sizeBytes = estimateModelBytes(params);
|
|
237
|
+
// Build a human-readable size descriptor only when we could parse the real size.
|
|
238
|
+
const sizeDescriptor = parsedParams !== null
|
|
239
|
+
? formatParamDescriptor(parsedParams)
|
|
240
|
+
: undefined;
|
|
241
|
+
const label = fitVerdictLabel(fitAssessment(sizeBytes, readHardwareProfileSync()), sizeDescriptor);
|
|
242
|
+
return label ? `Hardware: ${label}` : '';
|
|
243
|
+
}
|
|
244
|
+
|
|
251
245
|
function detailLines(picker: ModelPickerModal, width: number): string[] {
|
|
252
246
|
const target = picker.getSelectedTargetInfo();
|
|
253
247
|
const targetLabel = target?.label ?? targetLabelFor(picker.target);
|
|
254
248
|
const targetState = target ? (target.enabled ? 'enabled' : 'disabled') : 'active';
|
|
255
249
|
const selected = selectedModel(picker);
|
|
256
250
|
const lines: string[] = [];
|
|
251
|
+
|
|
252
|
+
// Local-only header: shown when the list contains only synthetic fit recommendations.
|
|
253
|
+
// Never show when any real provider is configured.
|
|
254
|
+
if (isLocalOnlyList(picker)) {
|
|
255
|
+
lines.push(MODEL_WORKSPACE_LOCAL_ONLY_HEADER);
|
|
256
|
+
lines.push(MODEL_WORKSPACE_LOCAL_ONLY_SIGN_IN);
|
|
257
|
+
}
|
|
258
|
+
|
|
257
259
|
lines.push(`Target: ${targetLabel} (${targetState})`);
|
|
258
260
|
if (target) lines.push(`Current: ${targetSummary(target)}`);
|
|
259
261
|
if (picker.mode === 'provider') {
|
|
@@ -269,7 +271,20 @@ function detailLines(picker: ModelPickerModal, width: number): string[] {
|
|
|
269
271
|
caps.multimodal ? 'vision' : '',
|
|
270
272
|
caps.toolCalling ? 'tools' : '',
|
|
271
273
|
].filter(Boolean).join(', ') || 'standard';
|
|
272
|
-
|
|
274
|
+
// Suppress the key:id line for synthetic local recommendations — they have
|
|
275
|
+
// no real registry key and displaying the synthetic id would confuse users.
|
|
276
|
+
if (isProviderSignInRow(selected)) {
|
|
277
|
+
lines.push('Sign in to a provider to connect a cloud or local model.');
|
|
278
|
+
if (selected.description) lines.push(selected.description);
|
|
279
|
+
} else if (!isLocalFitRecommendation(selected)) {
|
|
280
|
+
lines.push(`Selected: ${modelKey(selected)} | ${selected.displayName} | context ${formatContext(selected.contextWindow)} | ${capText}`);
|
|
281
|
+
} else {
|
|
282
|
+
lines.push(`Selected: ${selected.displayName}`);
|
|
283
|
+
// Show the fit hint from the description (e.g. "not yet installed (fits in GPU memory)")
|
|
284
|
+
if (selected.description) lines.push(selected.description);
|
|
285
|
+
}
|
|
286
|
+
const fitLine = modelHardwareFitLine(selected);
|
|
287
|
+
if (fitLine) lines.push(fitLine);
|
|
273
288
|
}
|
|
274
289
|
} else if (picker.mode === 'effort') {
|
|
275
290
|
lines.push(MODEL_WORKSPACE_REASONING_EFFORT_DETAIL);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine, createStyledCell } from '../types/grid.ts';
|
|
3
3
|
import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
|
|
4
|
-
import { getSurfaceContentRows, getTrackedVisibleWindow, getVisibleWindow, type VisibleWindow } from '
|
|
5
|
-
import { GLYPHS, UI_TONES } from '
|
|
6
|
-
import { type StatusState, STATE_GLYPHS } from '
|
|
4
|
+
import { getSurfaceContentRows, getTrackedVisibleWindow, getVisibleWindow, type VisibleWindow } from './surface-layout.ts';
|
|
5
|
+
import { GLYPHS, UI_TONES } from './ui-primitives.ts';
|
|
6
|
+
import { type StatusState, STATE_GLYPHS } from './status-glyphs.ts';
|
|
7
7
|
|
|
8
8
|
export interface PanelPalette {
|
|
9
9
|
readonly label: string;
|
|
@@ -36,17 +36,16 @@ export interface ShellFooterBuildResult {
|
|
|
36
36
|
readonly height: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
const
|
|
39
|
+
// Footer chrome: box top + box bottom + status line + hints line.
|
|
40
|
+
const FOOTER_BASE_ROWS = 4;
|
|
41
41
|
const PROCESS_INDICATOR_ROWS = 1;
|
|
42
42
|
|
|
43
43
|
export function estimateShellFooterHeight(
|
|
44
44
|
promptLineCount: number,
|
|
45
|
-
|
|
45
|
+
_contextWindow?: number,
|
|
46
46
|
): number {
|
|
47
47
|
const safePromptLines = Math.max(1, promptLineCount);
|
|
48
|
-
|
|
49
|
-
return FOOTER_BASE_ROWS + safePromptLines + progressRows + PROCESS_INDICATOR_ROWS;
|
|
48
|
+
return FOOTER_BASE_ROWS + safePromptLines + PROCESS_INDICATOR_ROWS;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
export function buildShellFooter(
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
14
|
import type { Cell } from '../types/grid.ts';
|
|
15
|
-
import { DEFAULT_PANEL_PALETTE } from '
|
|
15
|
+
import { DEFAULT_PANEL_PALETTE } from './polish.ts';
|
|
16
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
16
17
|
import { type StatusState, STATE_GLYPHS } from './status-glyphs.ts';
|
|
17
18
|
|
|
18
19
|
// Re-export for downstream consumers that import from this module.
|
|
@@ -54,14 +55,33 @@ export function buildStatusToken(
|
|
|
54
55
|
const suffix = opts?.count !== undefined ? ` (${opts.count})` : '';
|
|
55
56
|
const text = `${glyph} ${label}${suffix}`;
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
const cells: Cell[] = [];
|
|
59
|
+
for (const char of text) {
|
|
60
|
+
const displayW = getDisplayWidth(char);
|
|
61
|
+
cells.push({
|
|
62
|
+
char,
|
|
63
|
+
fg: color,
|
|
64
|
+
bg: '',
|
|
65
|
+
bold: false,
|
|
66
|
+
dim: false,
|
|
67
|
+
underline: false,
|
|
68
|
+
italic: false,
|
|
69
|
+
strikethrough: false,
|
|
70
|
+
});
|
|
71
|
+
if (displayW > 1) {
|
|
72
|
+
// Continuation cell for wide characters (CJK, emoji) to occupy the
|
|
73
|
+
// second terminal column without introducing a broken surrogate half.
|
|
74
|
+
cells.push({
|
|
75
|
+
char: '',
|
|
76
|
+
fg: color,
|
|
77
|
+
bg: '',
|
|
78
|
+
bold: false,
|
|
79
|
+
dim: false,
|
|
80
|
+
underline: false,
|
|
81
|
+
italic: false,
|
|
82
|
+
strikethrough: false,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return cells;
|
|
67
87
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
3
|
-
import { buildStyledPanelLine, type StyledPanelSegment } from '
|
|
3
|
+
import { buildStyledPanelLine, type StyledPanelSegment } from './polish.ts';
|
|
4
4
|
|
|
5
5
|
export interface TabStripItem {
|
|
6
6
|
readonly label: string;
|