ubume 0.1.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 +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import React, { memo } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../../config/settings.js";
|
|
4
|
+
import { formatUbumeBrandLabel } from "../../core/version/channel.js";
|
|
5
|
+
import { UBUME_UPDATE_COMMAND, formatVersionLabel } from "../../core/version/updateCheck.js";
|
|
6
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
7
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
8
|
+
import { getAuthStateLabel } from "../../core/auth/codexAuth.js";
|
|
9
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
10
|
+
import { useTheme } from "../theme.js";
|
|
11
|
+
import { clampVisualText, isDecorativeLayoutMode, type Layout, useAppLayoutBudget } from "../layout.js";
|
|
12
|
+
import { getTextWidth } from "../render/textLayout.js";
|
|
13
|
+
import { UPDATE_CARD_ROWS, UpdateAvailableCard } from "./UpdateAvailableCard.js";
|
|
14
|
+
import {
|
|
15
|
+
LOGO_LARGE,
|
|
16
|
+
LOGO_COMPACT,
|
|
17
|
+
LOGO_COMPACT_MIN_COLS,
|
|
18
|
+
LOGO_LARGE_MIN_COLS,
|
|
19
|
+
LOGO_LARGE_MIN_ROWS,
|
|
20
|
+
getLogoWidth,
|
|
21
|
+
} from "../render/logoVariants.js";
|
|
22
|
+
|
|
23
|
+
// Re-exported for backward compatibility with existing tests.
|
|
24
|
+
export const HEADER_WORDMARK_LINES = LOGO_LARGE;
|
|
25
|
+
|
|
26
|
+
const HEADER_PADDING_COLUMNS = 2;
|
|
27
|
+
const SHELL_GUTTER_COLUMNS = 1;
|
|
28
|
+
// Require 130+ cols for wide side-by-side so the UpdateAvailableCard has room.
|
|
29
|
+
const WIDE_HEADER_MIN_COLUMNS = 130;
|
|
30
|
+
// Require 72+ cols for medium side-by-side canonical-logo layout.
|
|
31
|
+
const MEDIUM_HEADER_MIN_COLUMNS = LOGO_LARGE_MIN_COLS;
|
|
32
|
+
const MIN_SIDE_BY_SIDE_METADATA_WIDTH = 18;
|
|
33
|
+
const STACKED_METADATA_GAP_ROWS = 1;
|
|
34
|
+
// Gap row between the UpdateAvailableCard and the metadata lines.
|
|
35
|
+
const UPDATE_CARD_GAP_ROWS = 1;
|
|
36
|
+
// Recommended terminal size shown in the compact-mode hint when the viewport is
|
|
37
|
+
// too small to render any logo art.
|
|
38
|
+
const RECOMMENDED_FULL_HEADER_HINT = `Resize to ≥${LOGO_LARGE_MIN_COLS}×${LOGO_LARGE_MIN_ROWS} for the full Ubume header`;
|
|
39
|
+
|
|
40
|
+
export type HeaderHeroMode = "wide" | "medium" | "narrow" | "compact";
|
|
41
|
+
|
|
42
|
+
export interface HeaderHeroLayout {
|
|
43
|
+
mode: HeaderHeroMode;
|
|
44
|
+
topMarginRows: number;
|
|
45
|
+
bottomMarginRows: number;
|
|
46
|
+
metadataGapColumns: number;
|
|
47
|
+
metadataGapRows: number;
|
|
48
|
+
logoRows: number;
|
|
49
|
+
metadataRows: number;
|
|
50
|
+
// Extra rows used by the compact-mode recommended-size hint (0 in non-compact
|
|
51
|
+
// modes). Threaded through so measureTopHeaderRows matches what renders.
|
|
52
|
+
compactHintRows: number;
|
|
53
|
+
totalRows: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type HeaderMetadataLine = { key: string; text: string; color: string; bold: boolean };
|
|
57
|
+
|
|
58
|
+
export interface UpdateAvailableInfo {
|
|
59
|
+
latestVersion: string;
|
|
60
|
+
currentVersion: string;
|
|
61
|
+
updateCommand?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface TopHeaderProps {
|
|
65
|
+
authState: CodexAuthState;
|
|
66
|
+
workspaceLabel: string;
|
|
67
|
+
layout: Layout;
|
|
68
|
+
runtimeSummary?: RuntimeSummary | null;
|
|
69
|
+
headerConfig?: HeaderConfig;
|
|
70
|
+
updateAvailable?: UpdateAvailableInfo | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getHeaderContentWidth(cols: number): number {
|
|
74
|
+
return Math.max(1, cols - SHELL_GUTTER_COLUMNS - HEADER_PADDING_COLUMNS);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getMetadataRowCount(headerConfig: HeaderConfig): number {
|
|
78
|
+
return [
|
|
79
|
+
headerConfig.showBrand,
|
|
80
|
+
headerConfig.showAuthStatus,
|
|
81
|
+
headerConfig.showWorkspace,
|
|
82
|
+
headerConfig.showProvider,
|
|
83
|
+
].filter(Boolean).length;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getHeaderVerticalMargins(layout: Layout): { topMarginRows: number; bottomMarginRows: number } {
|
|
87
|
+
if (!isDecorativeLayoutMode(layout.mode)) {
|
|
88
|
+
return {
|
|
89
|
+
topMarginRows: 0,
|
|
90
|
+
bottomMarginRows: 0,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (layout.rows <= 24) {
|
|
95
|
+
return { topMarginRows: 0, bottomMarginRows: 0 };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
topMarginRows: 1,
|
|
100
|
+
bottomMarginRows: 0,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function selectHeaderLogo(layout: Layout): readonly string[] {
|
|
105
|
+
if (process.env["UBUME_NO_ASCII_LOGO"] === "1") return [];
|
|
106
|
+
const showNormalLogo =
|
|
107
|
+
process.env["UBUME_NO_ASCII_LOGO"] !== "1" && (
|
|
108
|
+
layout.mode === "regular" ||
|
|
109
|
+
layout.mode === "expanded" ||
|
|
110
|
+
(layout.mode === "compact" && layout.cols >= 72)
|
|
111
|
+
);
|
|
112
|
+
if (!showNormalLogo) return [];
|
|
113
|
+
if (process.env["UBUME_COMPACT_LOGO"] === "1") return LOGO_COMPACT;
|
|
114
|
+
return LOGO_LARGE;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getHeaderHeroLayout(
|
|
118
|
+
layout: Layout,
|
|
119
|
+
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
120
|
+
hasUpdate = false,
|
|
121
|
+
): HeaderHeroLayout {
|
|
122
|
+
const { topMarginRows, bottomMarginRows } = getHeaderVerticalMargins(layout);
|
|
123
|
+
const metadataRows = getMetadataRowCount(headerConfig);
|
|
124
|
+
const contentWidth = getHeaderContentWidth(layout.cols);
|
|
125
|
+
|
|
126
|
+
const showNormalLogo =
|
|
127
|
+
process.env["UBUME_NO_ASCII_LOGO"] !== "1" && (
|
|
128
|
+
layout.mode === "regular" ||
|
|
129
|
+
layout.mode === "expanded" ||
|
|
130
|
+
(layout.mode === "compact" && layout.cols >= 72)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const placeMetadataBesideLogo = showNormalLogo && layout.cols >= 95;
|
|
134
|
+
|
|
135
|
+
let mode: HeaderHeroMode;
|
|
136
|
+
if (!showNormalLogo) {
|
|
137
|
+
mode = "compact";
|
|
138
|
+
} else if (placeMetadataBesideLogo) {
|
|
139
|
+
mode = layout.cols >= 130 ? "wide" : "medium";
|
|
140
|
+
} else {
|
|
141
|
+
mode = "narrow";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const logo = selectHeaderLogo(layout);
|
|
145
|
+
const logoWidth = logo.length > 0 ? getLogoWidth(logo) : 0;
|
|
146
|
+
|
|
147
|
+
if (mode === "compact") {
|
|
148
|
+
// Compact / micro text-only header.
|
|
149
|
+
return {
|
|
150
|
+
mode: "compact",
|
|
151
|
+
topMarginRows,
|
|
152
|
+
bottomMarginRows,
|
|
153
|
+
metadataGapColumns: 0,
|
|
154
|
+
metadataGapRows: 0,
|
|
155
|
+
logoRows: 1,
|
|
156
|
+
metadataRows: 0,
|
|
157
|
+
compactHintRows: 0,
|
|
158
|
+
totalRows: topMarginRows + 1 + bottomMarginRows,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const logoRowCount = logo.length;
|
|
163
|
+
const metadataGapColumns = layout.cols >= WIDE_HEADER_MIN_COLUMNS ? 4 : 2;
|
|
164
|
+
const metadataGapRows = mode === "narrow" && metadataRows > 0 ? STACKED_METADATA_GAP_ROWS : 0;
|
|
165
|
+
|
|
166
|
+
const isSideBySide = mode === "wide" || mode === "medium";
|
|
167
|
+
|
|
168
|
+
// In side-by-side mode the right column grows to: update card + gap + metadata.
|
|
169
|
+
// In narrow/stacked mode, the update card is a single extra line below metadata.
|
|
170
|
+
const rightColRows = isSideBySide
|
|
171
|
+
? (hasUpdate ? UPDATE_CARD_ROWS + UPDATE_CARD_GAP_ROWS : 0) + metadataRows
|
|
172
|
+
: 0;
|
|
173
|
+
|
|
174
|
+
const contentRows = isSideBySide
|
|
175
|
+
? Math.max(logoRowCount, rightColRows)
|
|
176
|
+
: logoRowCount + metadataGapRows + metadataRows + (hasUpdate ? STACKED_METADATA_GAP_ROWS + 1 : 0);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
mode,
|
|
180
|
+
topMarginRows,
|
|
181
|
+
bottomMarginRows,
|
|
182
|
+
metadataGapColumns,
|
|
183
|
+
metadataGapRows,
|
|
184
|
+
logoRows: logoRowCount,
|
|
185
|
+
metadataRows,
|
|
186
|
+
compactHintRows: 0,
|
|
187
|
+
totalRows: topMarginRows + contentRows + bottomMarginRows,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function measureTopHeaderRows(
|
|
192
|
+
layout: Layout,
|
|
193
|
+
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
194
|
+
hasUpdate = false,
|
|
195
|
+
): number {
|
|
196
|
+
return getHeaderHeroLayout(layout, headerConfig, hasUpdate).totalRows;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function takeVisualSuffix(text: string, maxWidth: number): string {
|
|
200
|
+
if (maxWidth <= 0) return "";
|
|
201
|
+
let output = "";
|
|
202
|
+
|
|
203
|
+
for (const char of Array.from(text).reverse()) {
|
|
204
|
+
if (getTextWidth(char + output) > maxWidth) break;
|
|
205
|
+
output = char + output;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return output;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function shortenHeaderWorkspaceLabel(workspaceLabel: string, maxWidth: number): string {
|
|
212
|
+
const trimmed = workspaceLabel.trim();
|
|
213
|
+
if (!trimmed || maxWidth <= 0) return "";
|
|
214
|
+
if (getTextWidth(trimmed) <= maxWidth) return trimmed;
|
|
215
|
+
if (maxWidth <= 1) return "…";
|
|
216
|
+
|
|
217
|
+
const normalized = trimmed.replace(/[\\/]+$/, "");
|
|
218
|
+
const separatorMatch = normalized.match(/[\\/]/g);
|
|
219
|
+
const separator = normalized.includes("\\") && (!separatorMatch || normalized.lastIndexOf("\\") >= normalized.lastIndexOf("/"))
|
|
220
|
+
? "\\"
|
|
221
|
+
: "/";
|
|
222
|
+
const lastSlash = Math.max(normalized.lastIndexOf("\\"), normalized.lastIndexOf("/"));
|
|
223
|
+
const leaf = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
|
|
224
|
+
const prefix = lastSlash >= 0 ? `…${separator}` : "…";
|
|
225
|
+
const leafWidth = Math.max(1, maxWidth - getTextWidth(prefix));
|
|
226
|
+
|
|
227
|
+
return prefix + takeVisualSuffix(leaf, leafWidth);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function clampMetadataText(text: string, maxWidth: number): string {
|
|
231
|
+
return clampVisualText(text, Math.max(1, maxWidth));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function TopHeader({
|
|
235
|
+
authState,
|
|
236
|
+
workspaceLabel,
|
|
237
|
+
layout,
|
|
238
|
+
runtimeSummary = null,
|
|
239
|
+
headerConfig = HEADER_CONFIG_DEFAULTS,
|
|
240
|
+
updateAvailable = null,
|
|
241
|
+
}: TopHeaderProps) {
|
|
242
|
+
renderDebug.useRenderDebug("Header", {
|
|
243
|
+
authState,
|
|
244
|
+
workspaceLabel,
|
|
245
|
+
cols: layout.cols,
|
|
246
|
+
rows: layout.rows,
|
|
247
|
+
mode: layout.mode,
|
|
248
|
+
});
|
|
249
|
+
renderDebug.useLifecycleDebug("Header", {
|
|
250
|
+
authState,
|
|
251
|
+
cols: layout.cols,
|
|
252
|
+
rows: layout.rows,
|
|
253
|
+
mode: layout.mode,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const theme = useTheme();
|
|
257
|
+
|
|
258
|
+
const authLabelRaw = getAuthStateLabel(authState);
|
|
259
|
+
const authLabel = authLabelRaw.length > 0
|
|
260
|
+
? authLabelRaw[0]!.toUpperCase() + authLabelRaw.slice(1)
|
|
261
|
+
: authLabelRaw;
|
|
262
|
+
|
|
263
|
+
const heroLayout = getHeaderHeroLayout(layout, headerConfig, !!updateAvailable);
|
|
264
|
+
const selectedLogo = selectHeaderLogo(layout);
|
|
265
|
+
const selectedLogoWidth = selectedLogo.length > 0 ? getLogoWidth(selectedLogo) : 0;
|
|
266
|
+
|
|
267
|
+
const contentWidth = getHeaderContentWidth(layout.cols);
|
|
268
|
+
const sideBySideMetadataWidth = Math.max(1, contentWidth - selectedLogoWidth - heroLayout.metadataGapColumns);
|
|
269
|
+
const metadataWidth = heroLayout.mode === "wide" || heroLayout.mode === "medium"
|
|
270
|
+
? sideBySideMetadataWidth
|
|
271
|
+
: contentWidth;
|
|
272
|
+
const workspaceValueWidth = Math.max(1, metadataWidth - getTextWidth("Workspace: "));
|
|
273
|
+
const wsDisplay = shortenHeaderWorkspaceLabel(workspaceLabel, workspaceValueWidth);
|
|
274
|
+
const brandLabel = formatUbumeBrandLabel();
|
|
275
|
+
const metadataLinesRaw = [
|
|
276
|
+
headerConfig.showBrand ? { key: "brand", text: brandLabel, color: theme.text, bold: true } : null,
|
|
277
|
+
headerConfig.showAuthStatus ? { key: "auth", text: `Auth: ${authLabel}`, color: theme.text, bold: false } : null,
|
|
278
|
+
headerConfig.showWorkspace ? { key: "workspace", text: `Workspace: ${wsDisplay}`, color: theme.textMuted, bold: false } : null,
|
|
279
|
+
headerConfig.showProvider && runtimeSummary?.providerLabel
|
|
280
|
+
? { key: "provider", text: `Provider: ${runtimeSummary.providerLabel}`, color: theme.text, bold: false }
|
|
281
|
+
: null,
|
|
282
|
+
].filter((line): line is HeaderMetadataLine => Boolean(line));
|
|
283
|
+
const metadataLines = metadataLinesRaw.map((line) => ({
|
|
284
|
+
...line,
|
|
285
|
+
text: clampMetadataText(line.text, metadataWidth),
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
// Add a 1-row gap between the version line and workspace line in wide mode
|
|
289
|
+
// so the two pieces of metadata have breathing room beside the logo.
|
|
290
|
+
const hasMetadataGap = heroLayout.mode === "wide"
|
|
291
|
+
&& metadataLines.length <= 3
|
|
292
|
+
&& metadataLines.some((l) => l.key === "brand")
|
|
293
|
+
&& metadataLines.some((l) => l.key === "workspace");
|
|
294
|
+
const metadataColumn = (
|
|
295
|
+
<Box flexDirection="column" flexGrow={1} flexShrink={1} width={metadataWidth}>
|
|
296
|
+
{metadataLines.map((line) =>
|
|
297
|
+
hasMetadataGap && line.key === "workspace" ? (
|
|
298
|
+
<Box key={line.key} marginTop={1}>
|
|
299
|
+
<Text color={line.color} bold={line.bold} wrap="truncate">{line.text}</Text>
|
|
300
|
+
</Box>
|
|
301
|
+
) : (
|
|
302
|
+
<Text key={line.key} color={line.color} bold={line.bold} wrap="truncate">{line.text}</Text>
|
|
303
|
+
)
|
|
304
|
+
)}
|
|
305
|
+
</Box>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// Canonical UBUME wordmark — uses per-line LOGO palette defined in each theme.
|
|
309
|
+
// No `bold`: bold on Unicode block/box-drawing characters causes per-glyph
|
|
310
|
+
// spacing artifacts in common terminal fonts (Ptyxis, GNOME Terminal).
|
|
311
|
+
// wrap="truncate" keeps each row on exactly one terminal line.
|
|
312
|
+
const logoColumn = (
|
|
313
|
+
<Box flexDirection="column" flexShrink={0}>
|
|
314
|
+
{selectedLogo.map((line, i) => {
|
|
315
|
+
let lineColor = theme.logoPrimary;
|
|
316
|
+
if (selectedLogo.length === 6) {
|
|
317
|
+
if (i === 2 || i === 3) lineColor = theme.logoSecondary;
|
|
318
|
+
else if (i === 4 || i === 5) lineColor = theme.logoShadow;
|
|
319
|
+
}
|
|
320
|
+
return (
|
|
321
|
+
<Text key={i} color={lineColor} wrap="truncate">{line}</Text>
|
|
322
|
+
);
|
|
323
|
+
})}
|
|
324
|
+
</Box>
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
if (heroLayout.mode !== "compact") {
|
|
328
|
+
const isSideBySide = heroLayout.mode === "wide" || heroLayout.mode === "medium";
|
|
329
|
+
const metadataTopOffset = 0;
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<Box flexDirection="column" paddingX={1} width="100%">
|
|
333
|
+
{heroLayout.topMarginRows > 0 && (
|
|
334
|
+
<Box height={heroLayout.topMarginRows} />
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
{isSideBySide ? (
|
|
338
|
+
<Box flexDirection="row" width="100%" alignItems="flex-start">
|
|
339
|
+
{logoColumn}
|
|
340
|
+
<Box width={heroLayout.metadataGapColumns} flexShrink={0} />
|
|
341
|
+
<Box flexDirection="column" flexGrow={1} paddingTop={metadataTopOffset}>
|
|
342
|
+
{updateAvailable && (
|
|
343
|
+
<>
|
|
344
|
+
<UpdateAvailableCard
|
|
345
|
+
latestVersion={updateAvailable.latestVersion}
|
|
346
|
+
currentVersion={updateAvailable.currentVersion}
|
|
347
|
+
updateCommand={updateAvailable.updateCommand}
|
|
348
|
+
width={metadataWidth}
|
|
349
|
+
/>
|
|
350
|
+
<Box height={UPDATE_CARD_GAP_ROWS} />
|
|
351
|
+
</>
|
|
352
|
+
)}
|
|
353
|
+
{metadataColumn}
|
|
354
|
+
</Box>
|
|
355
|
+
</Box>
|
|
356
|
+
) : (
|
|
357
|
+
<Box flexDirection="column" width="100%">
|
|
358
|
+
{logoColumn}
|
|
359
|
+
{heroLayout.metadataGapRows > 0 && (
|
|
360
|
+
<Box height={heroLayout.metadataGapRows} />
|
|
361
|
+
)}
|
|
362
|
+
{metadataColumn}
|
|
363
|
+
{updateAvailable && (
|
|
364
|
+
<Text color={theme.warning} wrap="truncate">{`Update available: Ubume ${formatVersionLabel(updateAvailable.latestVersion)} — Run: ${updateAvailable.updateCommand ?? UBUME_UPDATE_COMMAND}`}</Text>
|
|
365
|
+
)}
|
|
366
|
+
</Box>
|
|
367
|
+
)}
|
|
368
|
+
|
|
369
|
+
{heroLayout.bottomMarginRows > 0 && (
|
|
370
|
+
<Box height={heroLayout.bottomMarginRows} />
|
|
371
|
+
)}
|
|
372
|
+
</Box>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Compact / micro / activity-collapsed: single-line header.
|
|
377
|
+
const compactMetadataWidth = contentWidth;
|
|
378
|
+
const compactWorkspaceValueWidth = Math.max(1, compactMetadataWidth - getTextWidth("Workspace: "));
|
|
379
|
+
const compactWorkspaceDisplay = shortenHeaderWorkspaceLabel(workspaceLabel, compactWorkspaceValueWidth);
|
|
380
|
+
// A leading ✦ accent makes the single-line header read as a deliberate
|
|
381
|
+
// compact Ubume header rather than a broken fallback.
|
|
382
|
+
const compactParts: React.ReactNode[] = [
|
|
383
|
+
<Text key="accent" color={theme.accent} bold>{"✦ "}</Text>,
|
|
384
|
+
];
|
|
385
|
+
if (headerConfig.showBrand) {
|
|
386
|
+
compactParts.push(
|
|
387
|
+
<Text key="brand" color={theme.text} bold>{brandLabel}</Text>,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
if (headerConfig.showAuthStatus) {
|
|
391
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-auth" color={theme.textDim}>{" · "}</Text>);
|
|
392
|
+
compactParts.push(<Text key="auth" color={theme.text}>{authLabel}</Text>);
|
|
393
|
+
}
|
|
394
|
+
if (headerConfig.showWorkspace) {
|
|
395
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-ws" color={theme.textDim}>{" · "}</Text>);
|
|
396
|
+
compactParts.push(<Text key="ws" color={theme.textMuted} wrap="truncate">{`Workspace: ${compactWorkspaceDisplay}`}</Text>);
|
|
397
|
+
}
|
|
398
|
+
if (headerConfig.showProvider && runtimeSummary?.providerLabel) {
|
|
399
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-provider" color={theme.textDim}>{" · "}</Text>);
|
|
400
|
+
compactParts.push(<Text key="provider" color={theme.text} wrap="truncate">{`Provider: ${runtimeSummary.providerLabel}`}</Text>);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<Box flexDirection="column" paddingX={1} width="100%">
|
|
405
|
+
{heroLayout.topMarginRows > 0 && (
|
|
406
|
+
<Box height={heroLayout.topMarginRows} />
|
|
407
|
+
)}
|
|
408
|
+
<Box flexDirection="row" width="100%">
|
|
409
|
+
{compactParts}
|
|
410
|
+
</Box>
|
|
411
|
+
{heroLayout.compactHintRows > 0 && (
|
|
412
|
+
<Text color={theme.textDim} wrap="truncate">{clampMetadataText(RECOMMENDED_FULL_HEADER_HINT, compactMetadataWidth)}</Text>
|
|
413
|
+
)}
|
|
414
|
+
{heroLayout.bottomMarginRows > 0 && (
|
|
415
|
+
<Box height={heroLayout.bottomMarginRows} />
|
|
416
|
+
)}
|
|
417
|
+
</Box>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Memoize to prevent re-renders during streaming when props haven't changed
|
|
422
|
+
export const MemoizedTopHeader = memo(TopHeader, (prev, next) => {
|
|
423
|
+
return (
|
|
424
|
+
prev.authState === next.authState &&
|
|
425
|
+
prev.workspaceLabel === next.workspaceLabel &&
|
|
426
|
+
prev.layout.cols === next.layout.cols &&
|
|
427
|
+
prev.layout.rows === next.layout.rows &&
|
|
428
|
+
prev.layout.mode === next.layout.mode &&
|
|
429
|
+
prev.runtimeSummary === next.runtimeSummary &&
|
|
430
|
+
prev.headerConfig === next.headerConfig &&
|
|
431
|
+
prev.updateAvailable === next.updateAvailable
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Minimum terminal cols to render any logo art (the compact 1-row variant).
|
|
436
|
+
export const MIN_LOGO_TERMINAL_WIDTH = LOGO_COMPACT_MIN_COLS;
|
|
437
|
+
|
|
438
|
+
// Re-export for consumers that reference these constants directly.
|
|
439
|
+
export { LOGO_LARGE_MIN_COLS, LOGO_MEDIUM_MIN_COLS, LOGO_COMPACT_MIN_COLS } from "../render/logoVariants.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { UBUME_UPDATE_COMMAND, formatVersionLabel } from "../../core/version/updateCheck.js";
|
|
4
|
+
import { clampVisualText } from "../layout.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
|
+
|
|
7
|
+
export const UPDATE_CARD_CONTENT_ROWS = 4; // title + available + using + command
|
|
8
|
+
export const UPDATE_CARD_ROWS = UPDATE_CARD_CONTENT_ROWS + 2; // +2 for top/bottom border rows
|
|
9
|
+
|
|
10
|
+
export interface UpdateAvailableCardProps {
|
|
11
|
+
latestVersion: string;
|
|
12
|
+
currentVersion: string;
|
|
13
|
+
updateCommand?: string;
|
|
14
|
+
/** Total box width including borders. Long lines are truncated to fit. */
|
|
15
|
+
width?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function UpdateAvailableCard({ latestVersion, currentVersion, updateCommand = UBUME_UPDATE_COMMAND, width }: UpdateAvailableCardProps) {
|
|
19
|
+
const theme = useTheme();
|
|
20
|
+
const command = `Run: ${updateCommand}`;
|
|
21
|
+
// Inner content width = boxWidth - 2 (left/right border cols)
|
|
22
|
+
const innerWidth = width !== undefined ? Math.max(8, width - 2) : undefined;
|
|
23
|
+
|
|
24
|
+
function clamp(text: string): string {
|
|
25
|
+
return innerWidth !== undefined ? clampVisualText(text, innerWidth) : text;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Box
|
|
30
|
+
borderStyle="round"
|
|
31
|
+
borderColor={theme.accent}
|
|
32
|
+
flexDirection="column"
|
|
33
|
+
width={width}
|
|
34
|
+
flexShrink={0}
|
|
35
|
+
>
|
|
36
|
+
<Text color={theme.text} bold>{clamp("Update available")}</Text>
|
|
37
|
+
<Text color={theme.textMuted}>{clamp(`Ubume ${formatVersionLabel(latestVersion)}`)}</Text>
|
|
38
|
+
<Text color={theme.textMuted}>{clamp(`Using ${formatVersionLabel(currentVersion)}`)}</Text>
|
|
39
|
+
<Text color={theme.textDim}>{clamp(command)}</Text>
|
|
40
|
+
</Box>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const BUSY_STATUS_FRAME_MS = 900;
|
|
2
|
+
export const BUSY_STATUS_FRAMES = [" . ", " .. ", " ..."] as const;
|
|
3
|
+
|
|
4
|
+
export function getBusyStatusFrame(frameIndex: number): string {
|
|
5
|
+
const safeIndex = Math.max(0, Math.floor(frameIndex));
|
|
6
|
+
return BUSY_STATUS_FRAMES[safeIndex % BUSY_STATUS_FRAMES.length]!;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isAnimatedBusyState(kind: string): boolean {
|
|
10
|
+
return kind === "THINKING" || kind === "RESPONDING" || kind === "SHELL_RUNNING";
|
|
11
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { formatTerminalAnswerInline } from "../render/terminalAnswerFormat.js";
|
|
2
|
+
|
|
3
|
+
function stripOuterQuotes(s: string): string {
|
|
4
|
+
if ((s.startsWith("'") && s.endsWith("'")) || (s.startsWith('"') && s.endsWith('"'))) {
|
|
5
|
+
return s.slice(1, -1);
|
|
6
|
+
}
|
|
7
|
+
return s;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function decodeEscapedQuotes(s: string): string {
|
|
11
|
+
return s.replace(/\\"/g, '"');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function cleanCommand(command: string): string {
|
|
15
|
+
return formatTerminalAnswerInline(decodeEscapedQuotes(command.trim()));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Strips shell-wrapper invocations from a command string and returns the inner
|
|
20
|
+
* command that is actually being executed.
|
|
21
|
+
*
|
|
22
|
+
* Handles:
|
|
23
|
+
* "C:\Program Files\PowerShell\7\pwsh.exe" -Command '...'
|
|
24
|
+
* pwsh.exe -Command "..."
|
|
25
|
+
* powershell.exe -Command '...'
|
|
26
|
+
* cmd.exe /c "..."
|
|
27
|
+
* bash -lc '...'
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeCommand(command: string): string {
|
|
30
|
+
// Full quoted path ending with pwsh.exe / powershell.exe
|
|
31
|
+
let match = command.match(/^"[^"]*(?:pwsh|powershell)(?:\.exe)?"\s+-Command\s+(.+)$/si);
|
|
32
|
+
if (match) return cleanCommand(stripOuterQuotes(match[1].trim()));
|
|
33
|
+
|
|
34
|
+
// Bare pwsh.exe / powershell.exe on PATH
|
|
35
|
+
match = command.match(/^(?:pwsh|powershell)(?:\.exe)?\s+-Command\s+(.+)$/si);
|
|
36
|
+
if (match) return cleanCommand(stripOuterQuotes(match[1].trim()));
|
|
37
|
+
|
|
38
|
+
// cmd.exe /c "..."
|
|
39
|
+
match = command.match(/^cmd(?:\.exe)?\s+\/[cC]\s+(.+)$/s);
|
|
40
|
+
if (match) return cleanCommand(stripOuterQuotes(match[1].trim()));
|
|
41
|
+
|
|
42
|
+
// bash -lc "..." or bash -lc '...'
|
|
43
|
+
match = command.match(/^bash\s+-lc\s+(.+)$/s);
|
|
44
|
+
if (match) return cleanCommand(stripOuterQuotes(match[1].trim()));
|
|
45
|
+
|
|
46
|
+
return cleanCommand(command);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Maps a normalized command to a short human-readable label.
|
|
51
|
+
* Returns null when no label applies and the command itself should be shown.
|
|
52
|
+
*/
|
|
53
|
+
export function getFriendlyActionLabel(normalizedCommand: string): string | null {
|
|
54
|
+
const cmd = normalizedCommand.trim();
|
|
55
|
+
|
|
56
|
+
if (/^(?:Get-ChildItem|dir|ls)\b/i.test(cmd)) return "List files";
|
|
57
|
+
if (/^rg\s+--files\b/i.test(cmd)) return "List files";
|
|
58
|
+
if (/^(?:Get-Content|cat)\b/i.test(cmd)) return "Read file";
|
|
59
|
+
if (/^git\s+status\b/.test(cmd)) return "Check git status";
|
|
60
|
+
if (/^git\s+diff\b/.test(cmd)) return "Inspect changes";
|
|
61
|
+
if (/^(?:bun|npm)\s+install\b/.test(cmd)) return "Install dependencies";
|
|
62
|
+
if (/^(?:bun|npm)\s+test\b/.test(cmd)) return "Run tests";
|
|
63
|
+
if (/^(?:tsc\b|bun\s+run\s+typecheck\b)/.test(cmd)) return "Run typecheck";
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Screen } from "../../session/types.js";
|
|
2
|
+
|
|
3
|
+
export const FOCUS_IDS = {
|
|
4
|
+
composer: "composer",
|
|
5
|
+
resumePicker: "resume-picker",
|
|
6
|
+
planReviewPanel: "plan-review-panel",
|
|
7
|
+
backendPicker: "backend-picker",
|
|
8
|
+
providerPicker: "provider-picker",
|
|
9
|
+
providerSetup: "provider-setup",
|
|
10
|
+
modelPicker: "model-picker",
|
|
11
|
+
modePicker: "mode-picker",
|
|
12
|
+
reasoningPicker: "reasoning-picker",
|
|
13
|
+
themePicker: "theme-picker",
|
|
14
|
+
settingsPanel: "settings-panel",
|
|
15
|
+
authPanel: "auth-panel",
|
|
16
|
+
permissionsPanel: "permissions-panel",
|
|
17
|
+
permissionsApprovalPicker: "permissions-approval-picker",
|
|
18
|
+
permissionsSandboxPicker: "permissions-sandbox-picker",
|
|
19
|
+
permissionsNetworkPicker: "permissions-network-picker",
|
|
20
|
+
permissionsAddWritableRoot: "permissions-add-writable-root",
|
|
21
|
+
permissionsRemoveWritableRoot: "permissions-remove-writable-root",
|
|
22
|
+
importConfirmationPanel: "import-confirmation",
|
|
23
|
+
toolApprovalPanel: "tool-approval",
|
|
24
|
+
updatePrompt: "update-prompt",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type FocusTargetId = (typeof FOCUS_IDS)[keyof typeof FOCUS_IDS];
|
|
28
|
+
|
|
29
|
+
export function getFocusTargetForScreen(screen: Screen): FocusTargetId {
|
|
30
|
+
switch (screen) {
|
|
31
|
+
case "resume-picker":
|
|
32
|
+
return FOCUS_IDS.resumePicker;
|
|
33
|
+
case "backend-picker":
|
|
34
|
+
return FOCUS_IDS.backendPicker;
|
|
35
|
+
case "provider-picker":
|
|
36
|
+
return FOCUS_IDS.providerPicker;
|
|
37
|
+
case "provider-setup":
|
|
38
|
+
return FOCUS_IDS.providerSetup;
|
|
39
|
+
case "model-picker":
|
|
40
|
+
return FOCUS_IDS.modelPicker;
|
|
41
|
+
case "mode-picker":
|
|
42
|
+
return FOCUS_IDS.modePicker;
|
|
43
|
+
case "reasoning-picker":
|
|
44
|
+
return FOCUS_IDS.reasoningPicker;
|
|
45
|
+
case "theme-picker":
|
|
46
|
+
return FOCUS_IDS.themePicker;
|
|
47
|
+
case "settings-panel":
|
|
48
|
+
return FOCUS_IDS.settingsPanel;
|
|
49
|
+
case "auth-panel":
|
|
50
|
+
return FOCUS_IDS.authPanel;
|
|
51
|
+
case "permissions-panel":
|
|
52
|
+
return FOCUS_IDS.permissionsPanel;
|
|
53
|
+
case "permissions-approval-picker":
|
|
54
|
+
return FOCUS_IDS.permissionsApprovalPicker;
|
|
55
|
+
case "permissions-sandbox-picker":
|
|
56
|
+
return FOCUS_IDS.permissionsSandboxPicker;
|
|
57
|
+
case "permissions-network-picker":
|
|
58
|
+
return FOCUS_IDS.permissionsNetworkPicker;
|
|
59
|
+
case "permissions-add-writable-root":
|
|
60
|
+
return FOCUS_IDS.permissionsAddWritableRoot;
|
|
61
|
+
case "permissions-remove-writable-root":
|
|
62
|
+
return FOCUS_IDS.permissionsRemoveWritableRoot;
|
|
63
|
+
case "import-confirmation":
|
|
64
|
+
return FOCUS_IDS.importConfirmationPanel;
|
|
65
|
+
case "tool-approval":
|
|
66
|
+
return FOCUS_IDS.toolApprovalPanel;
|
|
67
|
+
case "update-prompt":
|
|
68
|
+
return FOCUS_IDS.updatePrompt;
|
|
69
|
+
case "main":
|
|
70
|
+
default:
|
|
71
|
+
return FOCUS_IDS.composer;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProviderImageAttachment } from "../../core/providerRuntime/types.js";
|
|
2
|
+
import { createAtomicContentToken, IMAGE_ATTACHMENT_PATTERN } from "./pastedContent.js";
|
|
3
|
+
|
|
4
|
+
export type ImageAttachmentRegistry = Map<string, ProviderImageAttachment>;
|
|
5
|
+
|
|
6
|
+
export function createImageAttachmentToken(attachment: ProviderImageAttachment): string {
|
|
7
|
+
return createAtomicContentToken(`[Image: ${attachment.name}]`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function selectImageAttachments(value: string, registry: ImageAttachmentRegistry): ProviderImageAttachment[] {
|
|
11
|
+
IMAGE_ATTACHMENT_PATTERN.lastIndex = 0;
|
|
12
|
+
const attachments: ProviderImageAttachment[] = [];
|
|
13
|
+
for (const match of value.matchAll(IMAGE_ATTACHMENT_PATTERN)) {
|
|
14
|
+
const attachment = registry.get(match[0]);
|
|
15
|
+
if (attachment) attachments.push(attachment);
|
|
16
|
+
}
|
|
17
|
+
return attachments;
|
|
18
|
+
}
|