@opengeni/react 0.15.0 → 0.23.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 +110 -10
- package/dist/{chunk-TOJR776I.js → chunk-6XUS5VFM.js} +45 -369
- package/dist/chunk-6XUS5VFM.js.map +1 -0
- package/dist/chunk-CATATY77.js +2393 -0
- package/dist/chunk-CATATY77.js.map +1 -0
- package/dist/chunk-I3BJZIG5.js +439 -0
- package/dist/chunk-I3BJZIG5.js.map +1 -0
- package/dist/chunk-TK7G6XLT.js +18 -0
- package/dist/chunk-TK7G6XLT.js.map +1 -0
- package/dist/chunk-UAHPKLB6.js +2237 -0
- package/dist/chunk-UAHPKLB6.js.map +1 -0
- package/dist/chunk-UQS7OQDE.js +591 -0
- package/dist/chunk-UQS7OQDE.js.map +1 -0
- package/dist/chunk-YDNWMKXO.js +102 -0
- package/dist/chunk-YDNWMKXO.js.map +1 -0
- package/dist/composer-CVm5uoUI.d.ts +585 -0
- package/dist/composer.d.ts +6 -0
- package/dist/composer.js +56 -0
- package/dist/composer.js.map +1 -0
- package/dist/index.d.ts +1288 -1785
- package/dist/index.js +7140 -8817
- package/dist/index.js.map +1 -1
- package/dist/machines.d.ts +412 -3
- package/dist/machines.js +3 -1
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
- package/dist/queue-surface-implementation-3UW3MIIR.js +890 -0
- package/dist/queue-surface-implementation-3UW3MIIR.js.map +1 -0
- package/dist/session-Db00TRwl.d.ts +446 -0
- package/dist/session-context-rgrfElMl.d.ts +58 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +54 -0
- package/dist/session.js.map +1 -0
- package/dist/use-composer-By1wn3MM.d.ts +99 -0
- package/package.json +20 -4
- package/src/approvals.ts +5 -3
- package/src/client.ts +44 -0
- package/src/components/approval-surface.tsx +198 -0
- package/src/components/chat-composer.tsx +94 -993
- package/src/components/code-editor.tsx +104 -26
- package/src/components/command-palette.tsx +8 -2
- package/src/components/composer-transcription-control.tsx +211 -0
- package/src/components/composer.tsx +1536 -0
- package/src/components/desktop-viewer.tsx +3 -3
- package/src/components/enrollment-consent.tsx +2 -2
- package/src/components/file-browser.tsx +367 -29
- package/src/components/human-input-form.tsx +417 -0
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machines-dashboard.tsx +3 -3
- package/src/components/message-timeline.tsx +205 -25
- package/src/components/model-picker.tsx +12 -3
- package/src/components/pierre-diff.tsx +32 -6
- package/src/components/queue-draft-policy.ts +18 -0
- package/src/components/queue-surface-implementation.tsx +1037 -0
- package/src/components/queue-surface-state.tsx +94 -0
- package/src/components/queue-surface.tsx +42 -559
- package/src/components/sandbox-files.tsx +138 -14
- package/src/components/sandbox-terminal.tsx +8 -2
- package/src/components/sandbox-workspace.tsx +339 -119
- package/src/components/workbench-changes.tsx +84 -33
- package/src/components/workspace-dock.tsx +188 -18
- package/src/composer.ts +60 -0
- package/src/hooks/internal.ts +115 -34
- package/src/hooks/use-composer.ts +385 -104
- package/src/hooks/use-human-input.ts +131 -0
- package/src/hooks/use-machine-chip.ts +2 -2
- package/src/hooks/use-machines.ts +27 -11
- package/src/hooks/use-sandbox-files.ts +310 -62
- package/src/hooks/use-sandbox-git.ts +154 -40
- package/src/hooks/use-sandbox-terminal.ts +28 -6
- package/src/hooks/use-session-capabilities.ts +38 -9
- package/src/hooks/use-session-control.ts +37 -8
- package/src/hooks/use-session-events.ts +522 -48
- package/src/hooks/use-session-mcp-approval-policy.ts +161 -0
- package/src/hooks/use-slash-commands.ts +38 -38
- package/src/hooks/use-transcription.ts +757 -0
- package/src/hooks/use-turn-queue.ts +134 -66
- package/src/hooks/use-windowed-sections.ts +2 -2
- package/src/hooks/use-workspace-capture.ts +146 -40
- package/src/hooks/use-workspace-edit.ts +50 -14
- package/src/hooks/use-workspace-sessions.ts +3 -0
- package/src/human-input.ts +72 -0
- package/src/index.ts +85 -6
- package/src/lib/noto-sans-arabic-loader.ts +21 -0
- package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
- package/src/lib/use-portal-token-style.ts +51 -0
- package/src/lib/use-unicode-fonts.ts +57 -0
- package/src/provider.tsx +4 -69
- package/src/session-context.ts +160 -0
- package/src/session.ts +89 -0
- package/src/timeline/parsers.ts +35 -0
- package/src/timeline/projection.ts +12 -4
- package/src/timeline/tool-renderers.tsx +41 -0
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +10 -9
- package/styles/index.css +56 -0
- package/styles/index.d.ts +2 -0
- package/styles/tokens.css +11 -6
- package/styles/tokens.d.ts +2 -0
- package/dist/chunk-TOJR776I.js.map +0 -1
- package/dist/machines-BpdwuQcD.d.ts +0 -449
package/README.md
CHANGED
|
@@ -4,16 +4,17 @@ React hooks and styled components for OpenGeni, built on
|
|
|
4
4
|
[`@opengeni/sdk`](../sdk): live session streaming, a chat composer, a message
|
|
5
5
|
timeline that renders streaming deltas / tool calls / spawned-worker status,
|
|
6
6
|
session status badges, and fleet tiles for workspace overviews. Two opt-in
|
|
7
|
-
surfaces layer on top: a **sandbox
|
|
7
|
+
surfaces layer on top: a **sandbox workspace** workbench (files, terminal, diff,
|
|
8
8
|
and an optional desktop stream) and, at the
|
|
9
9
|
[`@opengeni/react/machines`](#connected-machines-opengenireactmachines) subpath,
|
|
10
10
|
the **Connected Machines** dashboard + enrollment flow.
|
|
11
11
|
|
|
12
12
|
The default root import (`@opengeni/react`) is the clean sandbox-agnostic
|
|
13
|
-
surface — the chat/timeline hooks and components plus the sandbox
|
|
14
|
-
suite.
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
surface — the chat/timeline hooks and components plus the sandbox workspace
|
|
14
|
+
suite. Advanced chat-composer composition lives under
|
|
15
|
+
`@opengeni/react/composer`; Connected-Machine UI lives under
|
|
16
|
+
`@opengeni/react/machines`. (The root barrel still re-exports the machines
|
|
17
|
+
island for back-compat, deprecated per #144.)
|
|
17
18
|
|
|
18
19
|
Design-system-first: every visual decision routes through CSS-variable tokens
|
|
19
20
|
(`styles/tokens.css`) — color, typography, radius, shadow, motion. Dark mode is
|
|
@@ -24,6 +25,27 @@ animation. Override the tokens to rebrand everything.
|
|
|
24
25
|
|
|
25
26
|
## Install & styles
|
|
26
27
|
|
|
28
|
+
Hosts that render their own UI should import the session-only surface:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import {
|
|
32
|
+
useComposer,
|
|
33
|
+
useSessionControl,
|
|
34
|
+
useSessionEvents,
|
|
35
|
+
useTurnQueue,
|
|
36
|
+
} from "@opengeni/react/session";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That subpath contains session hooks, approval helpers, and the pure timeline
|
|
40
|
+
projection only. It does not load the styled composer, timeline, workbench,
|
|
41
|
+
CSS, or their optional editor/terminal peers. Pass `{ client, workspaceId }` to
|
|
42
|
+
each hook when the host intentionally does not mount `OpenGeniProvider`. The
|
|
43
|
+
exported `SessionClientLike` is deliberately narrow: a tenant-safe proxy needs
|
|
44
|
+
only session events, composer draft/send, queue, pause/resume, and approval
|
|
45
|
+
operations. Workspace-level Resume is an optional authority.
|
|
46
|
+
|
|
47
|
+
The styled root surface uses Tailwind v4 and the package CSS entries:
|
|
48
|
+
|
|
27
49
|
The package ships TypeScript source plus two CSS entries. In your Tailwind v4
|
|
28
50
|
entry CSS:
|
|
29
51
|
|
|
@@ -84,6 +106,82 @@ export function App() {
|
|
|
84
106
|
}
|
|
85
107
|
```
|
|
86
108
|
|
|
109
|
+
## Composer customization (`@opengeni/react/composer`)
|
|
110
|
+
|
|
111
|
+
Use `ChatComposer` for the standard layout and its `controlsStart`, `header`,
|
|
112
|
+
and `messages` props for small additions. For a different layout, import the
|
|
113
|
+
headless controller and compound primitives as a namespace. The controller is
|
|
114
|
+
the one behavior path for keyboard routing, guarded queue/steer submission,
|
|
115
|
+
attachments, commands, pause/resume, focus, confirmations, and feedback.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import * as Composer from "@opengeni/react/composer";
|
|
119
|
+
|
|
120
|
+
function InsertTranscript() {
|
|
121
|
+
const composer = Composer.useChatComposer();
|
|
122
|
+
return (
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
onClick={() => {
|
|
126
|
+
const separator = composer.value.trim().length > 0 ? " " : "";
|
|
127
|
+
composer.setValue(`${composer.value}${separator}Transcribed text`);
|
|
128
|
+
composer.focusInput();
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
Insert transcript
|
|
132
|
+
</button>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function CustomComposer({ sessionComposer, attachments, effectiveControl }) {
|
|
137
|
+
const controller = Composer.useChatComposerController({
|
|
138
|
+
delivery: sessionComposer,
|
|
139
|
+
draft: sessionComposer,
|
|
140
|
+
control: sessionComposer,
|
|
141
|
+
attachments,
|
|
142
|
+
effectiveControl,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<Composer.Root controller={controller}>
|
|
147
|
+
<Composer.Frame>
|
|
148
|
+
<Composer.CommandPalette />
|
|
149
|
+
<Composer.Surface>
|
|
150
|
+
<Composer.PausedState />
|
|
151
|
+
<Composer.RestoredResources />
|
|
152
|
+
<Composer.Attachments />
|
|
153
|
+
<Composer.Input />
|
|
154
|
+
{controller.confirmState ? (
|
|
155
|
+
<Composer.Confirmation />
|
|
156
|
+
) : (
|
|
157
|
+
<Composer.Footer>
|
|
158
|
+
<Composer.Controls>
|
|
159
|
+
<Composer.AttachButton />
|
|
160
|
+
<InsertTranscript />
|
|
161
|
+
</Composer.Controls>
|
|
162
|
+
<Composer.Actions>
|
|
163
|
+
<Composer.PauseButton />
|
|
164
|
+
<Composer.SendButton />
|
|
165
|
+
</Composer.Actions>
|
|
166
|
+
</Composer.Footer>
|
|
167
|
+
)}
|
|
168
|
+
</Composer.Surface>
|
|
169
|
+
</Composer.Frame>
|
|
170
|
+
<Composer.Help />
|
|
171
|
+
<Composer.Status />
|
|
172
|
+
</Composer.Root>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
For a pre-session or otherwise limited composer, pass only `delivery`; `draft`
|
|
178
|
+
and `control` are optional capabilities rather than no-op requirements. Custom
|
|
179
|
+
controls should call `controller.submit("queue" | "steer")` (or read it through
|
|
180
|
+
`useChatComposer`) instead of calling a delivery adapter directly, so upload,
|
|
181
|
+
disabled, in-flight, and slash-command guards stay intact. Accessory-local UI
|
|
182
|
+
state remains application-owned; durable draft and session state remain in
|
|
183
|
+
`useComposer`.
|
|
184
|
+
|
|
87
185
|
## Hooks
|
|
88
186
|
|
|
89
187
|
- `useSessionEvents(sessionId)` — loads a compact, bounded tail window by
|
|
@@ -108,7 +206,8 @@ export function App() {
|
|
|
108
206
|
`steerTurn`, and `removeTurn`. Mutations carry observed revisions and conflicts
|
|
109
207
|
refetch server truth. Live-updates on `turn.*` and `session.queue.*` events — pass the
|
|
110
208
|
`events` log from `useSessionEvents` to reuse its stream, or let it tail the
|
|
111
|
-
session itself.
|
|
209
|
+
session itself. Providerless self-streams reconcile authoritative queue/draft
|
|
210
|
+
state after the SSE connection opens, closing the initial-read handoff race.
|
|
112
211
|
- `useGoal(sessionId, { events })` — goal state + autonomy counters
|
|
113
212
|
(`autoContinuations`, `noProgressStreak`) with `pause(rationale?)` /
|
|
114
213
|
`resume()`. Goal-less sessions yield `goal: null`. Live-updates on `goal.*`
|
|
@@ -165,11 +264,12 @@ intentional changes should regenerate those snapshots and review the diff.
|
|
|
165
264
|
|
|
166
265
|
## Components
|
|
167
266
|
|
|
168
|
-
- `ChatComposer` — auto-growing textarea, Enter-to-send (IME-safe),
|
|
169
|
-
|
|
267
|
+
- `ChatComposer` — auto-growing textarea, Enter-to-send (IME-safe), pause/resume
|
|
268
|
+
controls, inline error recovery. Slots for app chrome:
|
|
170
269
|
`controlsStart` (footer controls like model pickers / attach buttons),
|
|
171
270
|
`header` (e.g. attachment chips above the field), and `onPaste`
|
|
172
|
-
(paste-image-to-attach).
|
|
271
|
+
(paste-image-to-attach). Its advanced controller and compound primitives are
|
|
272
|
+
exported from `@opengeni/react/composer`.
|
|
173
273
|
- `MessageTimeline` — the session timeline with stick-to-bottom scrolling, a
|
|
174
274
|
"jump to latest" affordance, streaming caret, collapsible activity clusters,
|
|
175
275
|
and worker cards (wire `onOpenSession` to drill into a worker). Pass
|
|
@@ -253,7 +353,7 @@ end-to-end embedder story (create-on-machine, discover, swap, enroll, revoke).
|
|
|
253
353
|
|
|
254
354
|
## Optional peer dependencies
|
|
255
355
|
|
|
256
|
-
The chat/timeline surface has none. The sandbox
|
|
356
|
+
The chat/timeline surface has none. The sandbox workspace and diff surfaces pull
|
|
257
357
|
their heavy libraries from **optional** `peerDependencies`, so you install only
|
|
258
358
|
what the surfaces you mount need:
|
|
259
359
|
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-TK7G6XLT.js";
|
|
4
|
+
import {
|
|
5
|
+
OpenGeniContext,
|
|
6
|
+
formatBytes,
|
|
7
|
+
formatRelativeTime,
|
|
8
|
+
useMutationRunner,
|
|
9
|
+
useOpenGeni,
|
|
10
|
+
usePolledValue
|
|
11
|
+
} from "./chunk-I3BJZIG5.js";
|
|
12
|
+
|
|
1
13
|
// src/types/machines.ts
|
|
2
14
|
function connectionStatusForState(state) {
|
|
3
15
|
switch (state) {
|
|
@@ -17,20 +29,6 @@ function connectionStatusForState(state) {
|
|
|
17
29
|
}
|
|
18
30
|
}
|
|
19
31
|
|
|
20
|
-
// src/lib/cn.ts
|
|
21
|
-
import { clsx } from "clsx";
|
|
22
|
-
import { extendTailwindMerge } from "tailwind-merge";
|
|
23
|
-
var twMerge = extendTailwindMerge({
|
|
24
|
-
extend: {
|
|
25
|
-
classGroups: {
|
|
26
|
-
"font-size": [{ text: ["og-xs", "og-sm", "og-base", "og-md"] }]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
function cn(...inputs) {
|
|
31
|
-
return twMerge(clsx(inputs));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
32
|
// src/components/machine-status-pill.tsx
|
|
35
33
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
36
34
|
var CONNECTION_STATUS_META = {
|
|
@@ -153,116 +151,6 @@ function MachineStatusPill({
|
|
|
153
151
|
);
|
|
154
152
|
}
|
|
155
153
|
|
|
156
|
-
// src/lib/format.ts
|
|
157
|
-
function formatRelativeTime(iso, now = /* @__PURE__ */ new Date()) {
|
|
158
|
-
const then = new Date(iso).getTime();
|
|
159
|
-
if (Number.isNaN(then)) {
|
|
160
|
-
return "";
|
|
161
|
-
}
|
|
162
|
-
const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1e3));
|
|
163
|
-
if (seconds < 10) {
|
|
164
|
-
return "now";
|
|
165
|
-
}
|
|
166
|
-
if (seconds < 60) {
|
|
167
|
-
return `${seconds}s`;
|
|
168
|
-
}
|
|
169
|
-
const minutes = Math.floor(seconds / 60);
|
|
170
|
-
if (minutes < 60) {
|
|
171
|
-
return `${minutes}m`;
|
|
172
|
-
}
|
|
173
|
-
const hours = Math.floor(minutes / 60);
|
|
174
|
-
if (hours < 24) {
|
|
175
|
-
return `${hours}h`;
|
|
176
|
-
}
|
|
177
|
-
const days = Math.floor(hours / 24);
|
|
178
|
-
if (days < 14) {
|
|
179
|
-
return `${days}d`;
|
|
180
|
-
}
|
|
181
|
-
return new Date(iso).toLocaleDateString();
|
|
182
|
-
}
|
|
183
|
-
function formatBytes(bytes) {
|
|
184
|
-
if (bytes < 1024) {
|
|
185
|
-
return `${bytes} B`;
|
|
186
|
-
}
|
|
187
|
-
const units = ["KB", "MB", "GB"];
|
|
188
|
-
let value = bytes / 1024;
|
|
189
|
-
for (const unit of units) {
|
|
190
|
-
if (value < 1024 || unit === "GB") {
|
|
191
|
-
return `${value.toFixed(value < 10 ? 1 : 0)} ${unit}`;
|
|
192
|
-
}
|
|
193
|
-
value /= 1024;
|
|
194
|
-
}
|
|
195
|
-
return `${bytes} B`;
|
|
196
|
-
}
|
|
197
|
-
function truncate(text, maxLength) {
|
|
198
|
-
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
199
|
-
if (collapsed.length <= maxLength) {
|
|
200
|
-
return collapsed;
|
|
201
|
-
}
|
|
202
|
-
return `${collapsed.slice(0, Math.max(0, maxLength - 1)).trimEnd()}\u2026`;
|
|
203
|
-
}
|
|
204
|
-
function stringifyPayload(value) {
|
|
205
|
-
if (value === null || value === void 0) {
|
|
206
|
-
return "";
|
|
207
|
-
}
|
|
208
|
-
if (typeof value === "string") {
|
|
209
|
-
const parsed = tryParseJson(value);
|
|
210
|
-
if (parsed !== void 0 && typeof parsed === "object") {
|
|
211
|
-
return stringifyPayload(parsed);
|
|
212
|
-
}
|
|
213
|
-
return value;
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
return JSON.stringify(value, null, 2) ?? String(value);
|
|
217
|
-
} catch {
|
|
218
|
-
return String(value);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
function tryParseJson(text) {
|
|
222
|
-
const trimmed = text.trim();
|
|
223
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[") && !trimmed.startsWith('"')) {
|
|
224
|
-
return void 0;
|
|
225
|
-
}
|
|
226
|
-
try {
|
|
227
|
-
return JSON.parse(trimmed);
|
|
228
|
-
} catch {
|
|
229
|
-
return void 0;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
var CREDIT_EXHAUSTION_MESSAGE = "Out of OpenGeni credits \u2014 this workspace's balance is empty. Add credits to continue; the conversation is preserved.";
|
|
233
|
-
function isCreditExhaustion(input) {
|
|
234
|
-
if (typeof input === "string") {
|
|
235
|
-
return input.toLowerCase().includes("insufficient opengeni credits");
|
|
236
|
-
}
|
|
237
|
-
if (input.segmentLimit === "budget_exhausted") {
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
for (const text of [input.error, input.detail]) {
|
|
241
|
-
if (typeof text === "string" && text.toLowerCase().includes("insufficient opengeni credits")) {
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
function humanizeFailureReason(reason) {
|
|
248
|
-
if (!reason) {
|
|
249
|
-
return reason;
|
|
250
|
-
}
|
|
251
|
-
if (isCreditExhaustion(reason)) {
|
|
252
|
-
return CREDIT_EXHAUSTION_MESSAGE;
|
|
253
|
-
}
|
|
254
|
-
const normalized = reason.toLowerCase();
|
|
255
|
-
const authFailure = normalized.includes("incorrect api key") || normalized.includes("invalid api key") || normalized.includes("invalid_api_key") || normalized.includes("platform.openai.com/account/api-keys") || normalized.includes("401") && (normalized.includes("api key") || normalized.includes("unauthorized"));
|
|
256
|
-
if (authFailure) {
|
|
257
|
-
return "The model provider rejected this deployment's engine credentials. Sending messages won't help until the deployment's engine configuration is fixed.";
|
|
258
|
-
}
|
|
259
|
-
const quotaFailure = normalized.includes("insufficient_quota") || normalized.includes("exceeded your current quota");
|
|
260
|
-
if (quotaFailure) {
|
|
261
|
-
return "The model provider refused the request: this deployment's provider quota is exhausted.";
|
|
262
|
-
}
|
|
263
|
-
return reason;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
154
|
// src/components/machine-metrics.tsx
|
|
267
155
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
268
156
|
function pct(value) {
|
|
@@ -741,7 +629,7 @@ function MachineCard({
|
|
|
741
629
|
onAttach?.(machine);
|
|
742
630
|
},
|
|
743
631
|
className: cn(
|
|
744
|
-
"rounded-og-sm border border-og-border px-2.5 py-1 text-og-xs font-medium text-og-fg-muted transition-colors pointer-coarse:min-h-
|
|
632
|
+
"rounded-og-sm border border-og-border px-2.5 py-1 text-og-xs font-medium text-og-fg-muted transition-colors pointer-coarse:min-h-11",
|
|
745
633
|
"hover:border-og-border-strong hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50"
|
|
746
634
|
),
|
|
747
635
|
children: attaching ? "Switching\u2026" : "Attach"
|
|
@@ -1260,7 +1148,7 @@ function EmptyState({ onEnroll }) {
|
|
|
1260
1148
|
type: "button",
|
|
1261
1149
|
"data-enroll-cta": true,
|
|
1262
1150
|
onClick: onEnroll,
|
|
1263
|
-
className: "inline-flex items-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong pointer-coarse:min-h-
|
|
1151
|
+
className: "inline-flex items-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong pointer-coarse:min-h-11",
|
|
1264
1152
|
children: [
|
|
1265
1153
|
/* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
1266
1154
|
"Enroll a machine"
|
|
@@ -1300,7 +1188,7 @@ function Header({
|
|
|
1300
1188
|
type: "button",
|
|
1301
1189
|
"data-enroll-cta": true,
|
|
1302
1190
|
onClick: onEnroll,
|
|
1303
|
-
className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg pointer-coarse:min-h-
|
|
1191
|
+
className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg pointer-coarse:min-h-11",
|
|
1304
1192
|
children: [
|
|
1305
1193
|
/* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
1306
1194
|
"Enroll machine"
|
|
@@ -1344,7 +1232,7 @@ function MachinesDashboard({
|
|
|
1344
1232
|
"data-machines-retry": true,
|
|
1345
1233
|
onClick: onRefresh,
|
|
1346
1234
|
disabled: loading,
|
|
1347
|
-
className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-status-failed/30 px-2 py-1 text-og-xs font-medium transition-colors hover:border-og-status-failed/50 disabled:cursor-not-allowed disabled:opacity-60 pointer-coarse:min-h-
|
|
1235
|
+
className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-status-failed/30 px-2 py-1 text-og-xs font-medium transition-colors hover:border-og-status-failed/50 disabled:cursor-not-allowed disabled:opacity-60 pointer-coarse:min-h-11",
|
|
1348
1236
|
children: [
|
|
1349
1237
|
/* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true }),
|
|
1350
1238
|
loading ? "Retrying\u2026" : "Retry"
|
|
@@ -1751,7 +1639,7 @@ function EnrollmentConsent({
|
|
|
1751
1639
|
"data-deny": true,
|
|
1752
1640
|
disabled: busy,
|
|
1753
1641
|
onClick: () => onDeny?.(),
|
|
1754
|
-
className: "flex-1 rounded-og-sm border border-og-border px-3 py-2 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg disabled:opacity-50 pointer-coarse:min-h-
|
|
1642
|
+
className: "flex-1 rounded-og-sm border border-og-border px-3 py-2 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg disabled:opacity-50 pointer-coarse:min-h-11",
|
|
1755
1643
|
children: "Cancel"
|
|
1756
1644
|
}
|
|
1757
1645
|
),
|
|
@@ -1762,7 +1650,7 @@ function EnrollmentConsent({
|
|
|
1762
1650
|
"data-approve": true,
|
|
1763
1651
|
disabled: busy,
|
|
1764
1652
|
onClick: () => onApprove?.(allowScreenControl),
|
|
1765
|
-
className: "flex-1 rounded-og-sm bg-og-status-failed px-3 py-2 text-og-sm font-semibold text-og-accent-fg transition-colors hover:opacity-90 disabled:opacity-50 pointer-coarse:min-h-
|
|
1653
|
+
className: "flex-1 rounded-og-sm bg-og-status-failed px-3 py-2 text-og-sm font-semibold text-og-accent-fg transition-colors hover:opacity-90 disabled:opacity-50 pointer-coarse:min-h-11",
|
|
1766
1654
|
children: busy ? "Connecting\u2026" : "Grant full access"
|
|
1767
1655
|
}
|
|
1768
1656
|
)
|
|
@@ -1807,26 +1695,15 @@ function ConsentResult({
|
|
|
1807
1695
|
}
|
|
1808
1696
|
|
|
1809
1697
|
// src/hooks/use-machines.ts
|
|
1810
|
-
import { useCallback as
|
|
1698
|
+
import { useCallback as useCallback2, useRef as useRef3, useState as useState4 } from "react";
|
|
1811
1699
|
|
|
1812
1700
|
// src/provider.tsx
|
|
1813
1701
|
import {
|
|
1814
1702
|
OpenGeniApiContractMismatchError,
|
|
1815
1703
|
OPENGENI_API_CONTRACT_REVISION
|
|
1816
1704
|
} from "@opengeni/sdk";
|
|
1817
|
-
import {
|
|
1818
|
-
createContext,
|
|
1819
|
-
useCallback,
|
|
1820
|
-
useContext,
|
|
1821
|
-
useEffect,
|
|
1822
|
-
useMemo,
|
|
1823
|
-
useRef as useRef2,
|
|
1824
|
-
useState as useState3
|
|
1825
|
-
} from "react";
|
|
1705
|
+
import { useCallback, useEffect, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
1826
1706
|
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1827
|
-
var OpenGeniContext = createContext(null);
|
|
1828
|
-
var NOOP_REGISTER_RECONCILER = () => () => void 0;
|
|
1829
|
-
var NOOP_RECONCILE_SESSION = async () => void 0;
|
|
1830
1707
|
var CONTRACT_RELOAD_STORAGE_PREFIX = "opengeni.reloadForApiContract:";
|
|
1831
1708
|
function OpenGeniProvider({
|
|
1832
1709
|
client,
|
|
@@ -1972,205 +1849,6 @@ function reloadForContractMismatchOnce(mismatch) {
|
|
|
1972
1849
|
sessionStorage.setItem(key, OPENGENI_API_CONTRACT_REVISION);
|
|
1973
1850
|
window.setTimeout(() => window.location.reload(), 150);
|
|
1974
1851
|
}
|
|
1975
|
-
function useOpenGeni(override = {}) {
|
|
1976
|
-
const context = useContext(OpenGeniContext);
|
|
1977
|
-
const client = override.client ?? context?.client;
|
|
1978
|
-
const workspaceId = override.workspaceId ?? context?.workspaceId;
|
|
1979
|
-
if (!client || !workspaceId) {
|
|
1980
|
-
throw new Error(
|
|
1981
|
-
"@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook."
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
return {
|
|
1985
|
-
client,
|
|
1986
|
-
workspaceId,
|
|
1987
|
-
workspaceControlEvent: context?.workspaceControlEvent ?? null,
|
|
1988
|
-
workspaceControlConnectionState: context?.workspaceControlConnectionState ?? "idle",
|
|
1989
|
-
registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,
|
|
1990
|
-
reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
function useOpenGeniClient(override = {}) {
|
|
1994
|
-
const context = useContext(OpenGeniContext);
|
|
1995
|
-
const client = override.client ?? context?.client;
|
|
1996
|
-
if (!client) {
|
|
1997
|
-
throw new Error(
|
|
1998
|
-
"@opengeni/react: no OpenGeni client available. Wrap the tree in <OpenGeniProvider> or pass { client } to the hook."
|
|
1999
|
-
);
|
|
2000
|
-
}
|
|
2001
|
-
return client;
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
// src/hooks/internal.ts
|
|
2005
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
2006
|
-
function usePolledValue(load, options = {}) {
|
|
2007
|
-
const enabled = options.enabled ?? true;
|
|
2008
|
-
const pollIntervalMs = options.pollIntervalMs;
|
|
2009
|
-
const [data, setData] = useState4(null);
|
|
2010
|
-
const [loading, setLoading] = useState4(enabled);
|
|
2011
|
-
const [error, setError] = useState4(null);
|
|
2012
|
-
const generation = useRef3(0);
|
|
2013
|
-
const loadRef = useRef3(load);
|
|
2014
|
-
useEffect2(() => {
|
|
2015
|
-
if (loadRef.current !== load) {
|
|
2016
|
-
loadRef.current = load;
|
|
2017
|
-
setData(null);
|
|
2018
|
-
setError(null);
|
|
2019
|
-
}
|
|
2020
|
-
}, [load]);
|
|
2021
|
-
const run = useCallback2(async () => {
|
|
2022
|
-
const ticket = ++generation.current;
|
|
2023
|
-
try {
|
|
2024
|
-
const result = await load();
|
|
2025
|
-
if (ticket === generation.current) {
|
|
2026
|
-
setData(result);
|
|
2027
|
-
setError(null);
|
|
2028
|
-
setLoading(false);
|
|
2029
|
-
}
|
|
2030
|
-
} catch (cause) {
|
|
2031
|
-
if (ticket === generation.current) {
|
|
2032
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
2033
|
-
setLoading(false);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
}, [load]);
|
|
2037
|
-
useEffect2(() => {
|
|
2038
|
-
if (!enabled) {
|
|
2039
|
-
setLoading(false);
|
|
2040
|
-
return;
|
|
2041
|
-
}
|
|
2042
|
-
setLoading(true);
|
|
2043
|
-
void run();
|
|
2044
|
-
if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
|
|
2045
|
-
return () => {
|
|
2046
|
-
generation.current += 1;
|
|
2047
|
-
};
|
|
2048
|
-
}
|
|
2049
|
-
const timer = setInterval(() => void run(), pollIntervalMs);
|
|
2050
|
-
return () => {
|
|
2051
|
-
clearInterval(timer);
|
|
2052
|
-
generation.current += 1;
|
|
2053
|
-
};
|
|
2054
|
-
}, [run, enabled, pollIntervalMs]);
|
|
2055
|
-
return { data, loading, error, refresh: run };
|
|
2056
|
-
}
|
|
2057
|
-
function useMutationRunner() {
|
|
2058
|
-
const [mutating, setMutating] = useState4(false);
|
|
2059
|
-
const [mutationError, setMutationError] = useState4(null);
|
|
2060
|
-
const inFlight = useRef3(0);
|
|
2061
|
-
const mounted = useRef3(true);
|
|
2062
|
-
useEffect2(() => {
|
|
2063
|
-
mounted.current = true;
|
|
2064
|
-
return () => {
|
|
2065
|
-
mounted.current = false;
|
|
2066
|
-
};
|
|
2067
|
-
}, []);
|
|
2068
|
-
const run = useCallback2(async (operation) => {
|
|
2069
|
-
inFlight.current += 1;
|
|
2070
|
-
if (mounted.current) {
|
|
2071
|
-
setMutating(true);
|
|
2072
|
-
setMutationError(null);
|
|
2073
|
-
}
|
|
2074
|
-
try {
|
|
2075
|
-
return await operation();
|
|
2076
|
-
} catch (cause) {
|
|
2077
|
-
if (mounted.current) {
|
|
2078
|
-
setMutationError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
2079
|
-
}
|
|
2080
|
-
return null;
|
|
2081
|
-
} finally {
|
|
2082
|
-
inFlight.current -= 1;
|
|
2083
|
-
if (mounted.current && inFlight.current === 0) {
|
|
2084
|
-
setMutating(false);
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
}, []);
|
|
2088
|
-
return {
|
|
2089
|
-
mutating,
|
|
2090
|
-
mutationError,
|
|
2091
|
-
clearMutationError: useCallback2(() => setMutationError(null), []),
|
|
2092
|
-
run
|
|
2093
|
-
};
|
|
2094
|
-
}
|
|
2095
|
-
function useSessionEventTrigger(client, workspaceId, sessionId, match, onEvent, options = {}) {
|
|
2096
|
-
const enabled = options.enabled ?? true;
|
|
2097
|
-
const events = options.events;
|
|
2098
|
-
const sharedFeed = events !== void 0;
|
|
2099
|
-
const matchRef = useRef3(match);
|
|
2100
|
-
matchRef.current = match;
|
|
2101
|
-
const onEventRef = useRef3(onEvent);
|
|
2102
|
-
onEventRef.current = onEvent;
|
|
2103
|
-
const consumedRef = useRef3(0);
|
|
2104
|
-
const feedKeyRef = useRef3(null);
|
|
2105
|
-
useEffect2(() => {
|
|
2106
|
-
if (!sharedFeed || !enabled || !sessionId) {
|
|
2107
|
-
return;
|
|
2108
|
-
}
|
|
2109
|
-
const feedKey = `${workspaceId}\0${sessionId}`;
|
|
2110
|
-
const firstSequence = events[0]?.sequence ?? 0;
|
|
2111
|
-
if (feedKeyRef.current !== feedKey || firstSequence > consumedRef.current + 1) {
|
|
2112
|
-
feedKeyRef.current = feedKey;
|
|
2113
|
-
consumedRef.current = 0;
|
|
2114
|
-
}
|
|
2115
|
-
for (const event of events) {
|
|
2116
|
-
if (event.sequence <= consumedRef.current) {
|
|
2117
|
-
continue;
|
|
2118
|
-
}
|
|
2119
|
-
consumedRef.current = event.sequence;
|
|
2120
|
-
if (matchRef.current(event)) {
|
|
2121
|
-
onEventRef.current(event);
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
}, [sharedFeed, enabled, events, workspaceId, sessionId]);
|
|
2125
|
-
useEffect2(() => {
|
|
2126
|
-
if (sharedFeed || !enabled || !sessionId) {
|
|
2127
|
-
return;
|
|
2128
|
-
}
|
|
2129
|
-
const controller = new AbortController();
|
|
2130
|
-
void (async () => {
|
|
2131
|
-
try {
|
|
2132
|
-
const session = await client.getSession(workspaceId, sessionId);
|
|
2133
|
-
if (controller.signal.aborted) {
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
const stream = client.streamEvents(workspaceId, sessionId, {
|
|
2137
|
-
after: session.lastSequence,
|
|
2138
|
-
signal: controller.signal
|
|
2139
|
-
});
|
|
2140
|
-
for await (const event of stream) {
|
|
2141
|
-
if (matchRef.current(event)) {
|
|
2142
|
-
onEventRef.current(event);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
} catch {
|
|
2146
|
-
}
|
|
2147
|
-
})();
|
|
2148
|
-
return () => {
|
|
2149
|
-
controller.abort();
|
|
2150
|
-
};
|
|
2151
|
-
}, [sharedFeed, enabled, client, workspaceId, sessionId]);
|
|
2152
|
-
}
|
|
2153
|
-
function useDebouncedCallback(callback, delayMs = 150) {
|
|
2154
|
-
const callbackRef = useRef3(callback);
|
|
2155
|
-
callbackRef.current = callback;
|
|
2156
|
-
const timerRef = useRef3(null);
|
|
2157
|
-
useEffect2(() => {
|
|
2158
|
-
return () => {
|
|
2159
|
-
if (timerRef.current !== null) {
|
|
2160
|
-
clearTimeout(timerRef.current);
|
|
2161
|
-
}
|
|
2162
|
-
};
|
|
2163
|
-
}, []);
|
|
2164
|
-
return useCallback2(() => {
|
|
2165
|
-
if (timerRef.current !== null) {
|
|
2166
|
-
clearTimeout(timerRef.current);
|
|
2167
|
-
}
|
|
2168
|
-
timerRef.current = setTimeout(() => {
|
|
2169
|
-
timerRef.current = null;
|
|
2170
|
-
callbackRef.current();
|
|
2171
|
-
}, delayMs);
|
|
2172
|
-
}, [delayMs]);
|
|
2173
|
-
}
|
|
2174
1852
|
|
|
2175
1853
|
// src/hooks/use-machines.ts
|
|
2176
1854
|
var EMPTY = { activeSandboxId: null, activeEpoch: 0, machines: [] };
|
|
@@ -2178,9 +1856,18 @@ function useMachines(options = {}) {
|
|
|
2178
1856
|
const { client, workspaceId } = useOpenGeni(options);
|
|
2179
1857
|
const machinesClient = options.machinesClient ?? client;
|
|
2180
1858
|
const sessionId = options.sessionId;
|
|
2181
|
-
const
|
|
2182
|
-
|
|
2183
|
-
|
|
1859
|
+
const identityKey = `${workspaceId}\0${sessionId ?? ""}`;
|
|
1860
|
+
const identityRef = useRef3(identityKey);
|
|
1861
|
+
identityRef.current = identityKey;
|
|
1862
|
+
const load = useCallback2(
|
|
1863
|
+
async (signal) => {
|
|
1864
|
+
return await machinesClient.listMachines(workspaceId, {
|
|
1865
|
+
...sessionId ? { sessionId } : {},
|
|
1866
|
+
...signal ? { signal } : {}
|
|
1867
|
+
});
|
|
1868
|
+
},
|
|
1869
|
+
[machinesClient, workspaceId, sessionId]
|
|
1870
|
+
);
|
|
2184
1871
|
const {
|
|
2185
1872
|
data: loadedData,
|
|
2186
1873
|
loading,
|
|
@@ -2190,27 +1877,31 @@ function useMachines(options = {}) {
|
|
|
2190
1877
|
pollIntervalMs: options.pollIntervalMs,
|
|
2191
1878
|
enabled: options.enabled
|
|
2192
1879
|
});
|
|
2193
|
-
const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
|
|
2194
|
-
const [
|
|
1880
|
+
const { run, mutating, mutationError, clearMutationError } = useMutationRunner(identityKey);
|
|
1881
|
+
const [attachState, setAttachState] = useState4(() => ({ identity: identityKey, sandboxId: null }));
|
|
1882
|
+
const attachingSandboxId = attachState.identity === identityKey ? attachState.sandboxId : null;
|
|
2195
1883
|
const data = loadedData ?? EMPTY;
|
|
2196
1884
|
const canAttach = sessionId !== void 0 && (typeof machinesClient.attachMachine === "function" || typeof machinesClient.swapActiveSandbox === "function");
|
|
2197
|
-
const attach =
|
|
1885
|
+
const attach = useCallback2(
|
|
2198
1886
|
async (sandboxId) => {
|
|
2199
1887
|
if (sessionId === void 0) return false;
|
|
2200
1888
|
const runSwap = machinesClient.attachMachine ? () => machinesClient.attachMachine(workspaceId, sessionId, sandboxId) : machinesClient.swapActiveSandbox ? () => machinesClient.swapActiveSandbox(workspaceId, sessionId, { target: sandboxId }) : null;
|
|
2201
1889
|
if (!runSwap) return false;
|
|
2202
|
-
|
|
1890
|
+
const ownedIdentity = identityKey;
|
|
1891
|
+
setAttachState({ identity: ownedIdentity, sandboxId });
|
|
2203
1892
|
const result = await run(async () => {
|
|
2204
1893
|
await runSwap();
|
|
2205
1894
|
return true;
|
|
2206
1895
|
});
|
|
2207
|
-
|
|
2208
|
-
|
|
1896
|
+
if (identityRef.current === ownedIdentity) {
|
|
1897
|
+
setAttachState({ identity: ownedIdentity, sandboxId: null });
|
|
1898
|
+
if (result) await refresh();
|
|
1899
|
+
}
|
|
2209
1900
|
return result === true;
|
|
2210
1901
|
},
|
|
2211
|
-
[machinesClient, workspaceId, sessionId, run, refresh]
|
|
1902
|
+
[machinesClient, workspaceId, sessionId, identityKey, run, refresh]
|
|
2212
1903
|
);
|
|
2213
|
-
const fetchSeries =
|
|
1904
|
+
const fetchSeries = useCallback2(
|
|
2214
1905
|
async (enrollmentId, window2 = "1h") => {
|
|
2215
1906
|
if (!machinesClient.machineMetricsSeries) return [];
|
|
2216
1907
|
return await machinesClient.machineMetricsSeries(workspaceId, enrollmentId, { window: window2 });
|
|
@@ -2236,21 +1927,6 @@ function useMachines(options = {}) {
|
|
|
2236
1927
|
|
|
2237
1928
|
export {
|
|
2238
1929
|
OpenGeniProvider,
|
|
2239
|
-
useOpenGeni,
|
|
2240
|
-
useOpenGeniClient,
|
|
2241
|
-
usePolledValue,
|
|
2242
|
-
useMutationRunner,
|
|
2243
|
-
useSessionEventTrigger,
|
|
2244
|
-
useDebouncedCallback,
|
|
2245
|
-
formatRelativeTime,
|
|
2246
|
-
formatBytes,
|
|
2247
|
-
truncate,
|
|
2248
|
-
stringifyPayload,
|
|
2249
|
-
tryParseJson,
|
|
2250
|
-
CREDIT_EXHAUSTION_MESSAGE,
|
|
2251
|
-
isCreditExhaustion,
|
|
2252
|
-
humanizeFailureReason,
|
|
2253
|
-
cn,
|
|
2254
1930
|
useMachines,
|
|
2255
1931
|
connectionStatusForState,
|
|
2256
1932
|
CONNECTION_STATUS_META,
|
|
@@ -2277,4 +1953,4 @@ export {
|
|
|
2277
1953
|
EnrollmentDeviceFlow,
|
|
2278
1954
|
EnrollmentConsent
|
|
2279
1955
|
};
|
|
2280
|
-
//# sourceMappingURL=chunk-
|
|
1956
|
+
//# sourceMappingURL=chunk-6XUS5VFM.js.map
|