@opengeni/react 0.11.0 → 0.13.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 +1 -1
- package/dist/{chunk-5C7RGAWA.js → chunk-NFYVQWIB.js} +170 -64
- package/dist/chunk-NFYVQWIB.js.map +1 -0
- package/dist/index.d.ts +791 -145
- package/dist/index.js +5447 -2213
- package/dist/index.js.map +1 -1
- package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +1 -1
- package/package.json +17 -12
- package/src/approvals.ts +16 -4
- package/src/client.ts +32 -5
- package/src/commands/index.ts +1 -7
- package/src/commands/registry.ts +46 -12
- package/src/components/chat-composer.tsx +135 -39
- package/src/components/code-editor.tsx +28 -36
- package/src/components/command-palette.tsx +26 -5
- package/src/components/desktop-viewer.tsx +29 -16
- package/src/components/diff-view.tsx +27 -189
- package/src/components/enrollment-consent.tsx +28 -10
- package/src/components/enrollment-device-flow.tsx +8 -5
- package/src/components/file-browser.tsx +190 -56
- package/src/components/fleet-tile.tsx +13 -4
- package/src/components/machine-card.tsx +16 -4
- package/src/components/machine-dock-bar.tsx +20 -8
- package/src/components/machine-metrics.tsx +31 -8
- package/src/components/machine-status-pill.tsx +26 -5
- package/src/components/machines-dashboard.tsx +8 -2
- package/src/components/markdown.tsx +39 -9
- package/src/components/message-timeline.tsx +633 -109
- package/src/components/pierre-diff.tsx +83 -15
- package/src/components/pierre-file.tsx +10 -9
- package/src/components/sandbox-files.tsx +110 -227
- package/src/components/sandbox-terminal.tsx +256 -88
- package/src/components/sandbox-workspace.tsx +823 -0
- package/src/components/session-status.tsx +28 -2
- package/src/components/workbench-changes.tsx +541 -0
- package/src/components/workspace-dock.tsx +85 -22
- package/src/hooks/internal.ts +5 -2
- package/src/hooks/use-available-models.ts +7 -2
- package/src/hooks/use-billing-usage.ts +4 -1
- package/src/hooks/use-codex-accounts.ts +74 -44
- package/src/hooks/use-composer.ts +57 -36
- package/src/hooks/use-environments.ts +92 -47
- package/src/hooks/use-file-attachments.ts +101 -55
- package/src/hooks/use-goal.ts +52 -16
- package/src/hooks/use-machine-chip.ts +134 -0
- package/src/hooks/use-machines.ts +34 -16
- package/src/hooks/use-packs.ts +24 -19
- package/src/hooks/use-relay-frame-stream.ts +5 -2
- package/src/hooks/use-rigs.ts +335 -0
- package/src/hooks/use-sandbox-files.ts +213 -39
- package/src/hooks/use-sandbox-git.ts +188 -11
- package/src/hooks/use-sandbox-terminal.ts +18 -14
- package/src/hooks/use-scheduled-tasks.ts +10 -2
- package/src/hooks/use-session-capabilities.ts +77 -20
- package/src/hooks/use-session-control.ts +49 -21
- package/src/hooks/use-session-events.ts +48 -20
- package/src/hooks/use-session-lineage.ts +119 -0
- package/src/hooks/use-session.ts +43 -28
- package/src/hooks/use-slash-commands.ts +6 -4
- package/src/hooks/use-turn-queue.ts +74 -147
- package/src/hooks/use-windowed-sections.ts +204 -0
- package/src/hooks/use-workspace-capture.ts +233 -0
- package/src/hooks/use-workspace-edit.ts +231 -0
- package/src/hooks/use-workspace-sessions.ts +48 -5
- package/src/hooks/use-workspaces.ts +18 -15
- package/src/index.ts +128 -23
- package/src/lib/format.ts +44 -6
- package/src/lib/xterm-renderer.ts +65 -0
- package/src/lib/xterm-theme.ts +224 -18
- package/src/machines.ts +13 -3
- package/src/provider.tsx +3 -1
- package/src/timeline/activity-rail.tsx +213 -54
- package/src/timeline/disclosure-context.tsx +12 -2
- package/src/timeline/index.ts +21 -2
- package/src/timeline/parsers.ts +44 -10
- package/src/timeline/projection.ts +435 -84
- package/src/timeline/shared.tsx +111 -24
- package/src/timeline/tool-diff.tsx +24 -20
- package/src/timeline/tool-renderers.tsx +98 -21
- package/src/timeline/turn-summary.tsx +76 -23
- package/src/timeline/types.ts +96 -12
- package/styles/tokens.css +2 -2
- package/dist/chunk-5C7RGAWA.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -12,31 +12,71 @@ export type { ClientOverride, OpenGeniContextValue, OpenGeniProviderProps } from
|
|
|
12
12
|
export { useSession, isTitleEvent } from "./hooks/use-session";
|
|
13
13
|
export type { UseSessionOptions, UseSessionResult } from "./hooks/use-session";
|
|
14
14
|
export { useSessionEvents } from "./hooks/use-session-events";
|
|
15
|
-
export type {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
export type {
|
|
16
|
+
SessionEventsConnectionState,
|
|
17
|
+
UseSessionEventsOptions,
|
|
18
|
+
UseSessionEventsResult,
|
|
19
|
+
} from "./hooks/use-session-events";
|
|
20
20
|
export {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
useComposer,
|
|
22
|
+
composeSendInput,
|
|
23
|
+
shouldSubmitOnKey,
|
|
24
|
+
FILE_ONLY_MESSAGE_TEXT,
|
|
25
|
+
} from "./hooks/use-composer";
|
|
26
|
+
export type {
|
|
27
|
+
ComposerMode,
|
|
28
|
+
ComposerSendExtras,
|
|
29
|
+
ComposerState,
|
|
30
|
+
UseComposerOptions,
|
|
31
|
+
} from "./hooks/use-composer";
|
|
32
|
+
export { useFileAttachments } from "./hooks/use-file-attachments";
|
|
33
|
+
export type {
|
|
34
|
+
FileAttachment,
|
|
35
|
+
UseFileAttachmentsOptions,
|
|
36
|
+
UseFileAttachmentsResult,
|
|
37
|
+
} from "./hooks/use-file-attachments";
|
|
38
|
+
export { useTurnQueue, isTurnQueueEvent } from "./hooks/use-turn-queue";
|
|
29
39
|
export type { UseTurnQueueOptions, UseTurnQueueResult } from "./hooks/use-turn-queue";
|
|
30
40
|
export { useGoal, isGoalEvent } from "./hooks/use-goal";
|
|
31
41
|
export type { UseGoalOptions, UseGoalResult } from "./hooks/use-goal";
|
|
32
42
|
export { useSessionControl } from "./hooks/use-session-control";
|
|
33
|
-
export type {
|
|
43
|
+
export type {
|
|
44
|
+
UseSessionControlOptions,
|
|
45
|
+
UseSessionControlResult,
|
|
46
|
+
} from "./hooks/use-session-control";
|
|
34
47
|
export { useScheduledTasks } from "./hooks/use-scheduled-tasks";
|
|
35
|
-
export type {
|
|
48
|
+
export type {
|
|
49
|
+
UseScheduledTasksOptions,
|
|
50
|
+
UseScheduledTasksResult,
|
|
51
|
+
} from "./hooks/use-scheduled-tasks";
|
|
36
52
|
export { useWorkspaceSessions } from "./hooks/use-workspace-sessions";
|
|
37
|
-
export type {
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
export type {
|
|
54
|
+
UseWorkspaceSessionsOptions,
|
|
55
|
+
UseWorkspaceSessionsResult,
|
|
56
|
+
} from "./hooks/use-workspace-sessions";
|
|
57
|
+
export { useSessionLineage, isLineageRefreshEvent } from "./hooks/use-session-lineage";
|
|
58
|
+
export type {
|
|
59
|
+
UseSessionLineageOptions,
|
|
60
|
+
UseSessionLineageResult,
|
|
61
|
+
} from "./hooks/use-session-lineage";
|
|
62
|
+
export { useVariableSets, useEnvironments } from "./hooks/use-environments";
|
|
63
|
+
export type {
|
|
64
|
+
UseVariableSetsOptions,
|
|
65
|
+
UseVariableSetsResult,
|
|
66
|
+
UseEnvironmentsOptions,
|
|
67
|
+
UseEnvironmentsResult,
|
|
68
|
+
} from "./hooks/use-environments";
|
|
69
|
+
export { useRigs, useRig, useRigVersions, useRigChanges } from "./hooks/use-rigs";
|
|
70
|
+
export type {
|
|
71
|
+
UseRigsOptions,
|
|
72
|
+
UseRigsResult,
|
|
73
|
+
UseRigOptions,
|
|
74
|
+
UseRigResult,
|
|
75
|
+
UseRigVersionsOptions,
|
|
76
|
+
UseRigVersionsResult,
|
|
77
|
+
UseRigChangesOptions,
|
|
78
|
+
UseRigChangesResult,
|
|
79
|
+
} from "./hooks/use-rigs";
|
|
40
80
|
export { usePacks } from "./hooks/use-packs";
|
|
41
81
|
export type { UsePacksOptions, UsePacksResult } from "./hooks/use-packs";
|
|
42
82
|
export { useWorkspaces } from "./hooks/use-workspaces";
|
|
@@ -44,7 +84,10 @@ export type { UseWorkspacesOptions, UseWorkspacesResult } from "./hooks/use-work
|
|
|
44
84
|
export { useBillingUsage } from "./hooks/use-billing-usage";
|
|
45
85
|
export type { UseBillingUsageOptions, UseBillingUsageResult } from "./hooks/use-billing-usage";
|
|
46
86
|
export { useAvailableModels } from "./hooks/use-available-models";
|
|
47
|
-
export type {
|
|
87
|
+
export type {
|
|
88
|
+
UseAvailableModelsOptions,
|
|
89
|
+
UseAvailableModelsResult,
|
|
90
|
+
} from "./hooks/use-available-models";
|
|
48
91
|
|
|
49
92
|
// Sandbox surfacing (Phase 5): capability negotiation + terminal/files/diff/desktop
|
|
50
93
|
export { useSessionCapabilities } from "./hooks/use-session-capabilities";
|
|
@@ -84,6 +127,27 @@ export type {
|
|
|
84
127
|
export { useSandboxGit } from "./hooks/use-sandbox-git";
|
|
85
128
|
export type { UseSandboxGitOptions, UseSandboxGitResult } from "./hooks/use-sandbox-git";
|
|
86
129
|
|
|
130
|
+
// Workbench v2 turn-end capture (the cold-paint data layer)
|
|
131
|
+
export { useWorkspaceCapture } from "./hooks/use-workspace-capture";
|
|
132
|
+
export type {
|
|
133
|
+
UseWorkspaceCaptureOptions,
|
|
134
|
+
UseWorkspaceCaptureResult,
|
|
135
|
+
} from "./hooks/use-workspace-capture";
|
|
136
|
+
export { useWorkspaceEdit } from "./hooks/use-workspace-edit";
|
|
137
|
+
export type {
|
|
138
|
+
UseWorkspaceEditOptions,
|
|
139
|
+
UseWorkspaceEditResult,
|
|
140
|
+
WorkspaceEditConflict,
|
|
141
|
+
WorkspaceEditState,
|
|
142
|
+
} from "./hooks/use-workspace-edit";
|
|
143
|
+
export { deriveMachineChip, formatAsOf, useMachineChip } from "./hooks/use-machine-chip";
|
|
144
|
+
export type {
|
|
145
|
+
DeriveMachineChipInput,
|
|
146
|
+
MachineChip,
|
|
147
|
+
MachineChipState,
|
|
148
|
+
UseMachineChipOptions,
|
|
149
|
+
} from "./hooks/use-machine-chip";
|
|
150
|
+
|
|
87
151
|
// Pending-approvals projection
|
|
88
152
|
export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
|
|
89
153
|
export type { PendingApproval } from "./approvals";
|
|
@@ -91,6 +155,7 @@ export type { PendingApproval } from "./approvals";
|
|
|
91
155
|
// Timeline projection
|
|
92
156
|
export {
|
|
93
157
|
buildTimeline,
|
|
158
|
+
creditExhaustedFromEvents,
|
|
94
159
|
extractSessionRef,
|
|
95
160
|
groupTimeline,
|
|
96
161
|
sessionStatusFromEvents,
|
|
@@ -99,6 +164,7 @@ export {
|
|
|
99
164
|
export type {
|
|
100
165
|
ActivityItem,
|
|
101
166
|
AgentMessageItem,
|
|
167
|
+
AuthNeededItem,
|
|
102
168
|
GoalItem,
|
|
103
169
|
NoticeItem,
|
|
104
170
|
ReasoningItem,
|
|
@@ -113,7 +179,12 @@ export type {
|
|
|
113
179
|
} from "./timeline";
|
|
114
180
|
|
|
115
181
|
// Tool-renderer registry + the per-tool renderers (the timeline's extension API)
|
|
116
|
-
export {
|
|
182
|
+
export {
|
|
183
|
+
createDefaultToolRegistry,
|
|
184
|
+
createToolRegistry,
|
|
185
|
+
defaultToolRegistry,
|
|
186
|
+
rawTypeOf,
|
|
187
|
+
} from "./timeline";
|
|
117
188
|
export type {
|
|
118
189
|
CreateToolRegistryOptions,
|
|
119
190
|
ToolRegistry,
|
|
@@ -205,7 +276,11 @@ export type { MessageTimelineProps } from "./components/message-timeline";
|
|
|
205
276
|
export { Markdown } from "./components/markdown";
|
|
206
277
|
export type { MarkdownProps } from "./components/markdown";
|
|
207
278
|
export { SessionStatus, StatusDot, SESSION_STATUS_META } from "./components/session-status";
|
|
208
|
-
export type {
|
|
279
|
+
export type {
|
|
280
|
+
SessionStatusProps,
|
|
281
|
+
StatusDotProps,
|
|
282
|
+
SessionStatusMeta,
|
|
283
|
+
} from "./components/session-status";
|
|
209
284
|
export { FleetTile, sessionDisplayTitle } from "./components/fleet-tile";
|
|
210
285
|
export type { FleetTileProps } from "./components/fleet-tile";
|
|
211
286
|
|
|
@@ -229,12 +304,33 @@ export type { DesktopViewerProps } from "./components/desktop-viewer";
|
|
|
229
304
|
export { WorkspaceDock } from "./components/workspace-dock";
|
|
230
305
|
export type { WorkspaceDockProps, WorkspaceTab } from "./components/workspace-dock";
|
|
231
306
|
|
|
307
|
+
// The embeddable dock "brain" (Workbench v2, M4): the whole session workspace as
|
|
308
|
+
// one component + the tab-building hook + the pre-paint default-tab helper.
|
|
309
|
+
export {
|
|
310
|
+
SandboxWorkspace,
|
|
311
|
+
useSandboxWorkspaceTabs,
|
|
312
|
+
initialWorkspaceTab,
|
|
313
|
+
WORKBENCH_TAB_CHANGES,
|
|
314
|
+
WORKBENCH_TAB_FILES,
|
|
315
|
+
} from "./components/sandbox-workspace";
|
|
316
|
+
export type {
|
|
317
|
+
SandboxWorkspaceProps,
|
|
318
|
+
UseSandboxWorkspaceTabsOptions,
|
|
319
|
+
UseSandboxWorkspaceTabsResult,
|
|
320
|
+
WorkspaceMachine,
|
|
321
|
+
WorkspaceNotification,
|
|
322
|
+
} from "./components/sandbox-workspace";
|
|
323
|
+
|
|
232
324
|
// Connected-machine UI moved to the "@opengeni/react/machines" subpath; re-exported
|
|
233
325
|
// here for back-compat (#144).
|
|
234
326
|
export * from "./machines";
|
|
235
327
|
// Multi-account Codex (P1): accounts list + active-switch hook.
|
|
236
328
|
export { useCodexAccounts, isCodexAccountEvent } from "./hooks/use-codex-accounts";
|
|
237
|
-
export type {
|
|
329
|
+
export type {
|
|
330
|
+
CodexAccountsClientLike,
|
|
331
|
+
UseCodexAccountsOptions,
|
|
332
|
+
UseCodexAccountsResult,
|
|
333
|
+
} from "./hooks/use-codex-accounts";
|
|
238
334
|
|
|
239
335
|
// Sandbox helpers
|
|
240
336
|
export { gitFileDiffToPatch } from "./lib/git-patch";
|
|
@@ -242,4 +338,13 @@ export { xtermThemeFromTokens } from "./lib/xterm-theme";
|
|
|
242
338
|
|
|
243
339
|
// Utilities
|
|
244
340
|
export { cn } from "./lib/cn";
|
|
245
|
-
export {
|
|
341
|
+
export {
|
|
342
|
+
CREDIT_EXHAUSTION_MESSAGE,
|
|
343
|
+
formatBytes,
|
|
344
|
+
formatRelativeTime,
|
|
345
|
+
humanizeFailureReason,
|
|
346
|
+
isCreditExhaustion,
|
|
347
|
+
stringifyPayload,
|
|
348
|
+
truncate,
|
|
349
|
+
tryParseJson,
|
|
350
|
+
} from "./lib/format";
|
package/src/lib/format.ts
CHANGED
|
@@ -83,32 +83,70 @@ export function tryParseJson(text: string): unknown {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The canonical credit-death sentence. Credit exhaustion is the one failure a
|
|
88
|
+
* user can fix themselves — the copy must say what happened (empty balance),
|
|
89
|
+
* what to do (add credits), and what is safe (nothing was lost). Crucially it
|
|
90
|
+
* must NOT say "send a message to revive": a revive turn burns credits the
|
|
91
|
+
* workspace no longer has.
|
|
92
|
+
*/
|
|
93
|
+
export const CREDIT_EXHAUSTION_MESSAGE =
|
|
94
|
+
"Out of OpenGeni credits — this workspace's balance is empty. Add credits to continue; the conversation is preserved.";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Does this failure/completion payload (or raw error string) mean the
|
|
98
|
+
* workspace ran out of OpenGeni credits? Matches the engine's
|
|
99
|
+
* "insufficient OpenGeni credits" text (case-insensitive, substring — it
|
|
100
|
+
* arrives both bare and wrapped in "Activity task failed: …") and the
|
|
101
|
+
* budget-exhausted segment limit the engine stamps on a turn it ended early.
|
|
102
|
+
*/
|
|
103
|
+
export function isCreditExhaustion(
|
|
104
|
+
input: { error?: string | null; detail?: string | null; segmentLimit?: string | null } | string,
|
|
105
|
+
): boolean {
|
|
106
|
+
if (typeof input === "string") {
|
|
107
|
+
return input.toLowerCase().includes("insufficient opengeni credits");
|
|
108
|
+
}
|
|
109
|
+
if (input.segmentLimit === "budget_exhausted") {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
for (const text of [input.error, input.detail]) {
|
|
113
|
+
if (typeof text === "string" && text.toLowerCase().includes("insufficient opengeni credits")) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
86
120
|
/**
|
|
87
121
|
* Humanize engine/provider failure text before it reaches the timeline or a
|
|
88
122
|
* failure banner. Raw provider errors leak the wrong audience's instructions —
|
|
89
123
|
* "Incorrect API key … find your API key at platform.openai.com" tells a
|
|
90
124
|
* managed-deployment USER to fix credentials only an OPERATOR controls (and is
|
|
91
|
-
* flatly wrong for Azure or subscription-backed engines). Auth and
|
|
92
|
-
* failures collapse to one neutral, honest sentence; every
|
|
93
|
-
* through untouched. Raw payloads stay available in the
|
|
125
|
+
* flatly wrong for Azure or subscription-backed engines). Auth, quota, and
|
|
126
|
+
* credit-exhaustion failures collapse to one neutral, honest sentence; every
|
|
127
|
+
* other reason passes through untouched. Raw payloads stay available in the
|
|
128
|
+
* debug surfaces.
|
|
94
129
|
*/
|
|
95
130
|
export function humanizeFailureReason(reason: string | null): string | null {
|
|
96
131
|
if (!reason) {
|
|
97
132
|
return reason;
|
|
98
133
|
}
|
|
134
|
+
if (isCreditExhaustion(reason)) {
|
|
135
|
+
return CREDIT_EXHAUSTION_MESSAGE;
|
|
136
|
+
}
|
|
99
137
|
const normalized = reason.toLowerCase();
|
|
100
138
|
const authFailure =
|
|
101
139
|
normalized.includes("incorrect api key") ||
|
|
102
140
|
normalized.includes("invalid api key") ||
|
|
103
141
|
normalized.includes("invalid_api_key") ||
|
|
104
142
|
normalized.includes("platform.openai.com/account/api-keys") ||
|
|
105
|
-
(normalized.includes("401") &&
|
|
143
|
+
(normalized.includes("401") &&
|
|
144
|
+
(normalized.includes("api key") || normalized.includes("unauthorized")));
|
|
106
145
|
if (authFailure) {
|
|
107
146
|
return "The model provider rejected this deployment's engine credentials. Sending messages won't help until the deployment's engine configuration is fixed.";
|
|
108
147
|
}
|
|
109
148
|
const quotaFailure =
|
|
110
|
-
normalized.includes("insufficient_quota") ||
|
|
111
|
-
normalized.includes("exceeded your current quota");
|
|
149
|
+
normalized.includes("insufficient_quota") || normalized.includes("exceeded your current quota");
|
|
112
150
|
if (quotaFailure) {
|
|
113
151
|
return "The model provider refused the request: this deployment's provider quota is exhausted.";
|
|
114
152
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* ----------------------------------------------------------------------------
|
|
2
|
+
xterm renderer selection ladder: WebGL → 2D canvas → DOM.
|
|
3
|
+
|
|
4
|
+
WebGL is the fast path (GPU-composited cells — the whole point of the burst
|
|
5
|
+
responsiveness win). But a WebGL context can fail to init (headless / no GPU /
|
|
6
|
+
blocklisted driver) or be LOST at runtime (GPU reset, tab backgrounded). On
|
|
7
|
+
either, we dispose the addon and step down one tier so the terminal keeps
|
|
8
|
+
painting via the 2D canvas addon, and failing that xterm's built-in DOM
|
|
9
|
+
renderer (no addon).
|
|
10
|
+
|
|
11
|
+
`attachRenderer` is pure over its `loaders` (each closes over the xterm
|
|
12
|
+
instance and constructs+loads its addon, throwing on failure), so the ladder
|
|
13
|
+
+ context-loss downgrade unit-test without a real WebGL context.
|
|
14
|
+
-------------------------------------------------------------------------- */
|
|
15
|
+
|
|
16
|
+
export type RendererTier = "webgl" | "canvas" | "dom";
|
|
17
|
+
|
|
18
|
+
export type RendererAddon = { dispose: () => void };
|
|
19
|
+
|
|
20
|
+
/** A loader constructs + `loadAddon`s its renderer, wiring `onLoss` to the
|
|
21
|
+
* addon's context-loss event, and throws if the renderer can't initialize. */
|
|
22
|
+
export type RendererLoaders = {
|
|
23
|
+
webgl?: ((onLoss: () => void) => Promise<RendererAddon>) | undefined;
|
|
24
|
+
canvas?: (() => Promise<RendererAddon>) | undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const ORDER: RendererTier[] = ["webgl", "canvas", "dom"];
|
|
28
|
+
|
|
29
|
+
/** The next lower renderer tier, or null at the bottom (DOM). Pure. */
|
|
30
|
+
export function nextRendererTier(tier: RendererTier): RendererTier | null {
|
|
31
|
+
const i = ORDER.indexOf(tier);
|
|
32
|
+
return i >= 0 && i < ORDER.length - 1 ? ORDER[i + 1]! : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Attach the highest renderer tier at or below `prefer` that initializes,
|
|
37
|
+
* reporting the settled tier via `onTier`. On a WebGL/canvas context loss the
|
|
38
|
+
* loader's `onLoss` re-enters at the next tier down, so `onTier` is called again
|
|
39
|
+
* with the downgraded tier. Returns the tier chosen on this pass.
|
|
40
|
+
*/
|
|
41
|
+
export async function attachRenderer(
|
|
42
|
+
prefer: RendererTier,
|
|
43
|
+
loaders: RendererLoaders,
|
|
44
|
+
onTier: (tier: RendererTier) => void,
|
|
45
|
+
): Promise<RendererTier> {
|
|
46
|
+
const start = Math.max(0, ORDER.indexOf(prefer));
|
|
47
|
+
for (let i = start; i < ORDER.length; i++) {
|
|
48
|
+
const tier = ORDER[i]!;
|
|
49
|
+
if (tier === "dom") break;
|
|
50
|
+
const loader = tier === "webgl" ? loaders.webgl : loaders.canvas;
|
|
51
|
+
if (!loader) continue;
|
|
52
|
+
try {
|
|
53
|
+
const downgrade = nextRendererTier(tier) ?? "dom";
|
|
54
|
+
await loader(() => {
|
|
55
|
+
void attachRenderer(downgrade, loaders, onTier);
|
|
56
|
+
});
|
|
57
|
+
onTier(tier);
|
|
58
|
+
return tier;
|
|
59
|
+
} catch {
|
|
60
|
+
// Initialization failed — fall through to the next tier down.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
onTier("dom");
|
|
64
|
+
return "dom";
|
|
65
|
+
}
|
package/src/lib/xterm-theme.ts
CHANGED
|
@@ -1,31 +1,237 @@
|
|
|
1
1
|
import type { XtermTheme } from "../components/sandbox-terminal";
|
|
2
2
|
|
|
3
|
+
/* ----------------------------------------------------------------------------
|
|
4
|
+
xterm theming + font resolution from the `--og-*` token system.
|
|
5
|
+
|
|
6
|
+
xterm paints glyphs into a canvas (WebGL / 2D) and builds its font strings by
|
|
7
|
+
hand, so it can consume NEITHER CSS custom properties (`var(--og-font-mono)`
|
|
8
|
+
never resolves in a canvas font string) NOR a partial palette (an unset ANSI
|
|
9
|
+
slot falls back to xterm's stock VGA color). This module resolves both to
|
|
10
|
+
concrete values: a full 16-color ANSI ITheme designed against the og palette
|
|
11
|
+
for BOTH themes, and a concrete monospace family + px size.
|
|
12
|
+
|
|
13
|
+
The color-building + font-resolution cores are pure over a token `read`
|
|
14
|
+
function so they unit-test without a DOM; thin `*FromTokens` / `resolve*`
|
|
15
|
+
wrappers do the `getComputedStyle` read on the client.
|
|
16
|
+
-------------------------------------------------------------------------- */
|
|
17
|
+
|
|
18
|
+
export type ThemeMode = "dark" | "light";
|
|
19
|
+
|
|
20
|
+
/** Reads a resolved token value (already `getComputedStyle`-flattened). */
|
|
21
|
+
export type TokenReader = (name: string) => string | undefined;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The 16 ANSI colors + selection/cursor treatment, designed against the og
|
|
25
|
+
* OKLCH palette. Hues are borrowed from the og status ramp so terminal color
|
|
26
|
+
* reads as part of the same system: red≈status-failed(22), green≈status-idle
|
|
27
|
+
* (155), yellow≈status-running(80), blue≈accent(255), magenta≈status-waiting
|
|
28
|
+
* (305), cyan≈200. Dark uses lifted lightness on the dark slate ground; light
|
|
29
|
+
* uses deeper, more saturated inks on the near-white ground. Bright variants
|
|
30
|
+
* add lightness + a touch of chroma. `black`/`white` are neutral slate ramp
|
|
31
|
+
* stops (never pure #000/#fff — that clashes with the calm neutral ground).
|
|
32
|
+
*/
|
|
33
|
+
export type AnsiPalette = {
|
|
34
|
+
black: string;
|
|
35
|
+
red: string;
|
|
36
|
+
green: string;
|
|
37
|
+
yellow: string;
|
|
38
|
+
blue: string;
|
|
39
|
+
magenta: string;
|
|
40
|
+
cyan: string;
|
|
41
|
+
white: string;
|
|
42
|
+
brightBlack: string;
|
|
43
|
+
brightRed: string;
|
|
44
|
+
brightGreen: string;
|
|
45
|
+
brightYellow: string;
|
|
46
|
+
brightBlue: string;
|
|
47
|
+
brightMagenta: string;
|
|
48
|
+
brightCyan: string;
|
|
49
|
+
brightWhite: string;
|
|
50
|
+
/** Selection wash + its foreground; sits over the terminal ground. */
|
|
51
|
+
selectionBackground: string;
|
|
52
|
+
selectionForeground: string;
|
|
53
|
+
selectionInactiveBackground: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const DARK_ANSI: AnsiPalette = {
|
|
57
|
+
black: "oklch(0.3 0.014 260)",
|
|
58
|
+
red: "oklch(0.68 0.17 22)",
|
|
59
|
+
green: "oklch(0.75 0.13 155)",
|
|
60
|
+
yellow: "oklch(0.8 0.11 80)",
|
|
61
|
+
blue: "oklch(0.7 0.14 255)",
|
|
62
|
+
magenta: "oklch(0.72 0.13 305)",
|
|
63
|
+
cyan: "oklch(0.75 0.1 210)",
|
|
64
|
+
white: "oklch(0.8 0.01 260)",
|
|
65
|
+
brightBlack: "oklch(0.45 0.014 260)",
|
|
66
|
+
brightRed: "oklch(0.76 0.17 22)",
|
|
67
|
+
brightGreen: "oklch(0.82 0.14 155)",
|
|
68
|
+
brightYellow: "oklch(0.86 0.12 85)",
|
|
69
|
+
brightBlue: "oklch(0.78 0.13 255)",
|
|
70
|
+
brightMagenta: "oklch(0.8 0.13 305)",
|
|
71
|
+
brightCyan: "oklch(0.83 0.1 200)",
|
|
72
|
+
brightWhite: "oklch(0.97 0.005 260)",
|
|
73
|
+
selectionBackground: "oklch(0.72 0.15 255 / 0.34)",
|
|
74
|
+
selectionForeground: "oklch(0.985 0.005 260)",
|
|
75
|
+
selectionInactiveBackground: "oklch(0.72 0.02 260 / 0.22)",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const LIGHT_ANSI: AnsiPalette = {
|
|
79
|
+
black: "oklch(0.35 0.015 260)",
|
|
80
|
+
red: "oklch(0.52 0.19 22)",
|
|
81
|
+
green: "oklch(0.5 0.13 155)",
|
|
82
|
+
yellow: "oklch(0.56 0.13 80)",
|
|
83
|
+
blue: "oklch(0.52 0.18 255)",
|
|
84
|
+
magenta: "oklch(0.52 0.16 305)",
|
|
85
|
+
cyan: "oklch(0.52 0.1 210)",
|
|
86
|
+
white: "oklch(0.7 0.01 260)",
|
|
87
|
+
brightBlack: "oklch(0.5 0.014 260)",
|
|
88
|
+
brightRed: "oklch(0.55 0.2 22)",
|
|
89
|
+
brightGreen: "oklch(0.54 0.14 155)",
|
|
90
|
+
brightYellow: "oklch(0.6 0.14 80)",
|
|
91
|
+
brightBlue: "oklch(0.48 0.19 255)",
|
|
92
|
+
brightMagenta: "oklch(0.54 0.17 305)",
|
|
93
|
+
brightCyan: "oklch(0.54 0.11 210)",
|
|
94
|
+
brightWhite: "oklch(0.3 0.015 260)",
|
|
95
|
+
selectionBackground: "oklch(0.55 0.18 255 / 0.24)",
|
|
96
|
+
selectionForeground: "oklch(0.21 0.015 260)",
|
|
97
|
+
selectionInactiveBackground: "oklch(0.6 0.02 260 / 0.16)",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** The designed ANSI-16 palette for a theme mode. Pure. */
|
|
101
|
+
export function ogAnsiPalette(mode: ThemeMode): AnsiPalette {
|
|
102
|
+
return mode === "light" ? LIGHT_ANSI : DARK_ANSI;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build a COMPLETE xterm ITheme from token values. Background/foreground/cursor
|
|
107
|
+
* come from the live `--og-*` tokens (so an embedder's rebrand flows through);
|
|
108
|
+
* the ANSI-16 + selection come from the designed palette for `mode`. Every ANSI
|
|
109
|
+
* slot is always set — xterm never falls back to its stock VGA colors. Pure.
|
|
110
|
+
*/
|
|
111
|
+
export function buildXtermTheme(read: TokenReader, mode: ThemeMode): XtermTheme {
|
|
112
|
+
const ansi = ogAnsiPalette(mode);
|
|
113
|
+
const bgFallback = mode === "light" ? "oklch(0.978 0.003 260)" : "oklch(0.155 0.012 260)";
|
|
114
|
+
const fgFallback = mode === "light" ? "oklch(0.21 0.015 260)" : "oklch(0.955 0.005 260)";
|
|
115
|
+
const bg = read("--og-color-bg") ?? bgFallback;
|
|
116
|
+
const fg = read("--og-color-fg") ?? fgFallback;
|
|
117
|
+
const accent = read("--og-color-accent") ?? ansi.brightBlue;
|
|
118
|
+
return {
|
|
119
|
+
// Match the dock surface the terminal paints on (`--og-color-bg`).
|
|
120
|
+
background: bg,
|
|
121
|
+
foreground: fg,
|
|
122
|
+
cursor: accent,
|
|
123
|
+
cursorAccent: bg,
|
|
124
|
+
selectionBackground: ansi.selectionBackground,
|
|
125
|
+
selectionForeground: ansi.selectionForeground,
|
|
126
|
+
selectionInactiveBackground: ansi.selectionInactiveBackground,
|
|
127
|
+
black: ansi.black,
|
|
128
|
+
red: ansi.red,
|
|
129
|
+
green: ansi.green,
|
|
130
|
+
yellow: ansi.yellow,
|
|
131
|
+
blue: ansi.blue,
|
|
132
|
+
magenta: ansi.magenta,
|
|
133
|
+
cyan: ansi.cyan,
|
|
134
|
+
white: ansi.white,
|
|
135
|
+
brightBlack: ansi.brightBlack,
|
|
136
|
+
brightRed: ansi.brightRed,
|
|
137
|
+
brightGreen: ansi.brightGreen,
|
|
138
|
+
brightYellow: ansi.brightYellow,
|
|
139
|
+
brightBlue: ansi.brightBlue,
|
|
140
|
+
brightMagenta: ansi.brightMagenta,
|
|
141
|
+
brightCyan: ansi.brightCyan,
|
|
142
|
+
brightWhite: ansi.brightWhite,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Which og theme is active for `el`, matching the `use-theme-type` convention
|
|
147
|
+
* (nearest `data-og-theme` / `.og-light` ancestor; else the computed
|
|
148
|
+
* `color-scheme`; else dark, the first-class default). */
|
|
149
|
+
function detectMode(el: Element, style: CSSStyleDeclaration): ThemeMode {
|
|
150
|
+
const closest = el.closest?.bind(el);
|
|
151
|
+
if (closest) {
|
|
152
|
+
if (closest('[data-og-theme="light"]') || closest(".og-light")) return "light";
|
|
153
|
+
const explicit = closest("[data-og-theme]");
|
|
154
|
+
if (explicit) return explicit.getAttribute("data-og-theme") === "light" ? "light" : "dark";
|
|
155
|
+
}
|
|
156
|
+
const scheme = (
|
|
157
|
+
style.getPropertyValue("color-scheme").trim() ||
|
|
158
|
+
style.colorScheme ||
|
|
159
|
+
""
|
|
160
|
+
).toLowerCase();
|
|
161
|
+
if (scheme.includes("light") && !scheme.includes("dark")) return "light";
|
|
162
|
+
return "dark";
|
|
163
|
+
}
|
|
164
|
+
|
|
3
165
|
/**
|
|
4
|
-
* Derive
|
|
5
|
-
* Reads the COMPUTED values so xterm
|
|
6
|
-
*
|
|
7
|
-
*
|
|
166
|
+
* Derive a COMPLETE xterm `ITheme` from the live `--og-*` token system on the
|
|
167
|
+
* DOM. Reads the COMPUTED values so xterm gets concrete colors, detects the
|
|
168
|
+
* active theme mode, and fills every ANSI slot. Call on mount + re-derive on a
|
|
169
|
+
* `data-og-theme` flip. SSR-safe: returns undefined off the DOM.
|
|
8
170
|
*
|
|
9
|
-
*
|
|
171
|
+
* `root` should be an element INSIDE the themed subtree (e.g. the terminal
|
|
172
|
+
* container) so nested `[data-og-theme]` themes resolve correctly; when omitted
|
|
173
|
+
* it falls back to the nearest `[data-og-theme]` element, then documentElement.
|
|
10
174
|
*/
|
|
11
175
|
export function xtermThemeFromTokens(root?: HTMLElement | null): XtermTheme | undefined {
|
|
12
176
|
if (typeof window === "undefined" || typeof getComputedStyle === "undefined") return undefined;
|
|
13
|
-
const el =
|
|
177
|
+
const el =
|
|
178
|
+
root ??
|
|
179
|
+
(typeof document !== "undefined"
|
|
180
|
+
? ((document.querySelector("[data-og-theme]") as HTMLElement | null) ??
|
|
181
|
+
document.documentElement)
|
|
182
|
+
: null);
|
|
183
|
+
if (!el) return undefined;
|
|
14
184
|
const style = getComputedStyle(el);
|
|
15
|
-
const read = (name
|
|
185
|
+
const read: TokenReader = (name) => {
|
|
16
186
|
const value = style.getPropertyValue(name).trim();
|
|
17
187
|
return value || undefined;
|
|
18
188
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
189
|
+
return buildXtermTheme(read, detectMode(el, style));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Font resolution ──────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
export type ResolvedTerminalFont = { fontFamily: string; fontSize: number; lineHeight: number };
|
|
195
|
+
|
|
196
|
+
/** Concrete fallback if the token is unset or (defensively) still a `var()`. */
|
|
197
|
+
const FALLBACK_MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace";
|
|
198
|
+
|
|
199
|
+
export type FontOverrides = { fontFamily?: string | undefined; fontSize?: number | undefined };
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Resolve xterm's font from token values. The critical bug this fixes:
|
|
203
|
+
* `fontFamily: "var(--og-font-mono)"` is measured by xterm via a canvas font
|
|
204
|
+
* string where `var()` does NOT resolve, so xterm silently mis-measures every
|
|
205
|
+
* glyph. Here the family is a CONCRETE stack; a `var(` in the input is treated
|
|
206
|
+
* as unresolved and replaced with the fallback. Pure over a token `read`.
|
|
207
|
+
*/
|
|
208
|
+
export function resolveTerminalFontFromReader(
|
|
209
|
+
read: TokenReader,
|
|
210
|
+
overrides?: FontOverrides,
|
|
211
|
+
): ResolvedTerminalFont {
|
|
212
|
+
const rawFamily = overrides?.fontFamily ?? read("--og-font-mono");
|
|
213
|
+
const fontFamily =
|
|
214
|
+
rawFamily && rawFamily.trim() && !rawFamily.includes("var(") ? rawFamily.trim() : FALLBACK_MONO;
|
|
215
|
+
|
|
216
|
+
let fontSize = overrides?.fontSize;
|
|
217
|
+
if (fontSize == null) {
|
|
218
|
+
const raw = read("--og-code-font-size");
|
|
219
|
+
const parsed = raw ? Number.parseFloat(raw) : Number.NaN;
|
|
220
|
+
fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 13;
|
|
28
221
|
}
|
|
29
|
-
|
|
30
|
-
return
|
|
222
|
+
|
|
223
|
+
return { fontFamily, fontSize, lineHeight: 1.3 };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** DOM wrapper: resolve the terminal font from an element's computed tokens. */
|
|
227
|
+
export function resolveTerminalFont(
|
|
228
|
+
el: HTMLElement,
|
|
229
|
+
overrides?: FontOverrides,
|
|
230
|
+
): ResolvedTerminalFont {
|
|
231
|
+
const style = getComputedStyle(el);
|
|
232
|
+
const read: TokenReader = (name) => {
|
|
233
|
+
const value = style.getPropertyValue(name).trim();
|
|
234
|
+
return value || undefined;
|
|
235
|
+
};
|
|
236
|
+
return resolveTerminalFontFromReader(read, overrides);
|
|
31
237
|
}
|
package/src/machines.ts
CHANGED
|
@@ -37,9 +37,15 @@ export type { MachineCardProps } from "./components/machine-card";
|
|
|
37
37
|
export { MachinesDashboard } from "./components/machines-dashboard";
|
|
38
38
|
export type { MachinesDashboardProps } from "./components/machines-dashboard";
|
|
39
39
|
export { MachineDockBar, SharedMachineDisclosure } from "./components/machine-dock-bar";
|
|
40
|
-
export type {
|
|
40
|
+
export type {
|
|
41
|
+
MachineDockBarProps,
|
|
42
|
+
SharedMachineDisclosureProps,
|
|
43
|
+
} from "./components/machine-dock-bar";
|
|
41
44
|
export { EnrollmentDeviceFlow } from "./components/enrollment-device-flow";
|
|
42
|
-
export type {
|
|
45
|
+
export type {
|
|
46
|
+
DeviceFlowPhase,
|
|
47
|
+
EnrollmentDeviceFlowProps,
|
|
48
|
+
} from "./components/enrollment-device-flow";
|
|
43
49
|
export { EnrollmentConsent } from "./components/enrollment-consent";
|
|
44
50
|
export type {
|
|
45
51
|
EnrollmentConsentMachine,
|
|
@@ -47,4 +53,8 @@ export type {
|
|
|
47
53
|
EnrollmentConsentProps,
|
|
48
54
|
} from "./components/enrollment-consent";
|
|
49
55
|
export { useMachines } from "./hooks/use-machines";
|
|
50
|
-
export type {
|
|
56
|
+
export type {
|
|
57
|
+
MachinesClientLike,
|
|
58
|
+
UseMachinesOptions,
|
|
59
|
+
UseMachinesResult,
|
|
60
|
+
} from "./hooks/use-machines";
|
package/src/provider.tsx
CHANGED
|
@@ -45,7 +45,9 @@ export function useOpenGeni(override: ClientOverride = {}): OpenGeniContextValue
|
|
|
45
45
|
* Resolve the client only — for hooks that are not workspace-scoped
|
|
46
46
|
* (`useWorkspaces`, `useBillingUsage`).
|
|
47
47
|
*/
|
|
48
|
-
export function useOpenGeniClient(
|
|
48
|
+
export function useOpenGeniClient(
|
|
49
|
+
override: Pick<ClientOverride, "client"> = {},
|
|
50
|
+
): SessionClientLike {
|
|
49
51
|
const context = useContext(OpenGeniContext);
|
|
50
52
|
const client = override.client ?? context?.client;
|
|
51
53
|
if (!client) {
|