@unoverse-platform/studio 0.1.102 → 0.1.104

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.
Files changed (79) hide show
  1. package/package.json +4 -2
  2. package/screens/AtomsView.tsx +2 -1
  3. package/screens/ComponentsView.tsx +2 -1
  4. package/screens/NativeAppHost.tsx +1 -2
  5. package/screens/TemplatesLibraryView.tsx +14 -1
  6. package/screens/TemplatesView.tsx +2 -1
  7. package/screens/index.ts +4 -0
  8. package/screens/react/unoverse.tsx +281 -0
  9. package/vendor/base/src/agent-mcp/index.ts +1 -1
  10. package/vendor/base/src/agent-mcp/invoke.ts +6 -2
  11. package/vendor/base/src/agent-mcp/tools.ts +14 -5
  12. package/vendor/base/src/agent-mcp/types.ts +6 -0
  13. package/vendor/base/src/auth/auth.ts +90 -23
  14. package/vendor/base/src/definitions/load/compile/sections.ts +79 -0
  15. package/vendor/base/src/definitions/load/compile/template.ts +23 -2
  16. package/vendor/base/src/definitions/model.ts +9 -0
  17. package/vendor/base/src/definitions/registry/catalog.ts +6 -7
  18. package/vendor/base/src/executor.ts +25 -1
  19. package/vendor/base/src/manifests/executor/toolBridge.ts +25 -19
  20. package/vendor/base/src/manifests/runtime/channels/final.ts +4 -1
  21. package/vendor/base/src/manifests/runtime/duplex/session.ts +3 -9
  22. package/vendor/base/src/manifests/runtime/tools/toolloop.ts +28 -25
  23. package/vendor/base/src/platform/serviceCalls.ts +13 -0
  24. package/vendor/sdk/appEngine.ts +363 -0
  25. package/vendor/sdk/lib/actions.ts +36 -0
  26. package/vendor/sdk/lib/citations.ts +48 -0
  27. package/vendor/sdk/lib/core/apps.ts +18 -0
  28. package/vendor/sdk/lib/core/client.ts +3 -3
  29. package/vendor/sdk/lib/core/store.ts +6 -0
  30. package/vendor/sdk/lib/dom/app.ts +546 -0
  31. package/vendor/sdk/lib/dom/chrome.ts +125 -0
  32. package/vendor/sdk/lib/dom/component.ts +333 -0
  33. package/vendor/sdk/lib/dom/connection.ts +309 -0
  34. package/vendor/sdk/lib/dom/element.ts +69 -0
  35. package/vendor/sdk/lib/dom/isolate.ts +37 -0
  36. package/vendor/sdk/lib/dom/markdown.ts +388 -0
  37. package/vendor/sdk/lib/dom/markdownParse.ts +440 -0
  38. package/vendor/sdk/lib/dom/mounted.ts +29 -0
  39. package/vendor/sdk/lib/dom/primitives.ts +689 -0
  40. package/vendor/sdk/lib/dom/region.ts +195 -0
  41. package/vendor/sdk/lib/dom/render.ts +177 -0
  42. package/vendor/sdk/lib/dom/theme.ts +38 -0
  43. package/vendor/sdk/lib/dom/types.ts +36 -0
  44. package/vendor/sdk/lib/index.ts +26 -17
  45. package/vendor/sdk/lib/orb/canvas.ts +139 -0
  46. package/vendor/sdk/lib/orb/index.ts +8 -18
  47. package/vendor/sdk/lib/orb/presets.ts +1 -1
  48. package/vendor/sdk/lib/orb/{types.ts → states.ts} +0 -24
  49. package/vendor/sdk/lib/realtime/capture.ts +152 -0
  50. package/vendor/sdk/lib/realtime/playback.ts +129 -0
  51. package/vendor/sdk/lib/realtime/socket.ts +117 -0
  52. package/vendor/sdk/lib/style.ts +45 -37
  53. package/vendor/sdk/lib/voiceService.ts +344 -0
  54. package/vendor/sdk/main.ts +107 -0
  55. package/vendor/sdk/mcpAppBridge.ts +125 -102
  56. package/vendor/sdk/pageInterfaces.ts +452 -0
  57. package/vendor/sdk/responseView.ts +193 -0
  58. package/vite.config.ts +3 -3
  59. package/vendor/sdk/appEngine.tsx +0 -393
  60. package/vendor/sdk/lib/UnoverseComponent.tsx +0 -325
  61. package/vendor/sdk/lib/app.tsx +0 -730
  62. package/vendor/sdk/lib/connection.tsx +0 -433
  63. package/vendor/sdk/lib/isolate.tsx +0 -70
  64. package/vendor/sdk/lib/orb/ThinkingOrb.tsx +0 -131
  65. package/vendor/sdk/lib/orb/theme.ts +0 -83
  66. package/vendor/sdk/lib/primitives.tsx +0 -943
  67. package/vendor/sdk/lib/realtime/useAudioCapture.ts +0 -237
  68. package/vendor/sdk/lib/realtime/useAudioPlayback.ts +0 -157
  69. package/vendor/sdk/lib/realtime/useRealtimeWebSocket.ts +0 -164
  70. package/vendor/sdk/lib/render.tsx +0 -244
  71. package/vendor/sdk/lib/streamed.tsx +0 -30
  72. package/vendor/sdk/lib/theme.ts +0 -55
  73. package/vendor/sdk/lib/transition/MountArrangement.tsx +0 -71
  74. package/vendor/sdk/lib/transition/index.ts +0 -9
  75. package/vendor/sdk/lib/transition/viewTransition.ts +0 -29
  76. package/vendor/sdk/lib/voice.tsx +0 -422
  77. package/vendor/sdk/main.tsx +0 -125
  78. package/vendor/sdk/pageInterfaces.tsx +0 -383
  79. package/vendor/sdk/responseView.tsx +0 -82
@@ -1,325 +0,0 @@
1
- /**
2
- * <UnoverseComponent> — reads a definition by URI from the Unoverse MCP server
3
- * (via ./core) and renders it natively.
4
- *
5
- * ONE COMPONENT, TWO DATA SOURCES. A definition cannot tell which is rendering it, so the
6
- * SDK stopped pretending they were different things:
7
- *
8
- * data source give it state lives in
9
- * a plain object `uri` + `data` a local slice, here
10
- * the live stream `store` + `chatId` + `nodeId` the store's slice
11
- *
12
- * The split used to be two exported components, and a host had to know which one it was
13
- * holding. It never mattered to the component: the same `renderNode` walks the same
14
- * definition, and `setValue` writes the same instance either way.
15
- */
16
- import { useCallback, useEffect, useMemo, useState, useSyncExternalStore, type CSSProperties, type ReactNode } from "react";
17
- import { dispatchAction, matches, type ComponentStore, type ResolvedTheme, type UnoverseClient, type UnoverseDefinition } from "./core";
18
- import { renderNode, keyframesCss, type ActionHandler, type SlotResolver } from "./render";
19
- import { useUnoverseTheme, ensureFontStylesheets } from "./theme";
20
- import { IsolatedRoot } from "./isolate";
21
- import { stateOfUri } from "./core/apps";
22
-
23
- /** Subscribe to an instance in the store, re-rendering when a merge lands. Snapshots THIS
24
- * instance's slice (merge() gives it a fresh identity), so chunks for other components bail
25
- * out in React instead of re-rendering everyone. Tolerates no store: the static path calls
26
- * the same hook, because hooks cannot be conditional. */
27
- export function useUnoverseInstance(store?: ComponentStore, chatId = "", nodeId = "") {
28
- const subscribe = useCallback((cb: () => void) => (store ? store.subscribe(cb) : () => {}), [store]);
29
- const data = useSyncExternalStore(
30
- subscribe,
31
- useCallback(() => (store ? store.get(chatId, nodeId) : undefined), [store, chatId, nodeId]),
32
- );
33
- const type = useSyncExternalStore(
34
- subscribe,
35
- useCallback(() => (store ? store.getType(chatId, nodeId) : undefined), [store, chatId, nodeId]),
36
- );
37
- const componentUrl = useSyncExternalStore(
38
- subscribe,
39
- useCallback(() => (store ? store.getComponentUrl(chatId, nodeId) : undefined), [store, chatId, nodeId]),
40
- );
41
- return { type, data, componentUrl };
42
- }
43
-
44
- export interface UnoverseComponentProps {
45
- client: UnoverseClient;
46
- /** STATIC source: the definition to render, e.g. "unoverse://components/card". */
47
- uri?: string;
48
- /** STATIC source: instance data bound into the definition. */
49
- data?: Record<string, unknown>;
50
- /** LIVE source: the merge-state store. With it, `uri` and `data` come from the instance. */
51
- store?: ComponentStore;
52
- chatId?: string;
53
- nodeId?: string;
54
- /** Neutral turn state merged into the data scope (e.g. `streaming`), so a component can
55
- * reflect it. Live source only. */
56
- extraData?: Record<string, unknown>;
57
- onAction?: ActionHandler;
58
- /**
59
- * Resolved theme. Optional — if omitted, the SDK FETCHES it from the server
60
- * (`unoverse://theme/light`). The SDK bundles no tokens. Pass one to override
61
- * (e.g. the workbench's light/dark toggle); swap it to restyle, zero def changes.
62
- */
63
- theme?: ResolvedTheme;
64
- /**
65
- * Render inside a Shadow DOM for full CSS isolation (default true). The SDK owns the
66
- * isolation boundary so every consumer is protected identically; the canvas/channel no
67
- * longer wrap it themselves. Set false only for a host that manages its own isolation.
68
- */
69
- isolate?: boolean;
70
- /**
71
- * A SURFACE'S SINGLE OCCUPANT FILLS THE SURFACE (docs/design/05): set by a limit-1
72
- * reaction surface so the instance wrapper takes the frame's full height. The
73
- * component still owns its face — this only gives its `height: "full"` a definite
74
- * container. Flow/rail instances stay content-sized (never set there).
75
- */
76
- fill?: boolean;
77
- /**
78
- * WHAT A `Place` IN THIS DEFINITION DRAWS. An app's places are filled by the screen
79
- * (app.tsx); a place inside a component or template is filled by its host, which alone
80
- * knows what belongs there (a fullscreen template's place holds the page's avatar).
81
- * Without it a place draws nothing.
82
- */
83
- place?: (name: string) => ReactNode;
84
- /**
85
- * THE APP'S STATE, when it lives in another store (live source only). A template a website
86
- * page draws keeps its own slice in its own store, and shares one app state with the rest
87
- * of the page: it reads that state beneath its own values, and `setAppValue` writes there.
88
- */
89
- appStore?: ComponentStore;
90
- }
91
-
92
- export function UnoverseComponent({ client, uri, data, store, chatId, nodeId, extraData, onAction, theme, isolate = true, fill = false, place, appStore }: UnoverseComponentProps) {
93
- const [def, setDef] = useState<UnoverseDefinition | null>(null);
94
- const [error, setError] = useState<string | null>(null);
95
- // A component's OWN view state (a tab, a step, an expanded face) moves by writing its own
96
- // slice. That write is not a host concern: a tab click should work wherever a component
97
- // renders, without every surface wiring a handler for it. So the renderer keeps the slice
98
- // and applies `setValue` to it, and STILL forwards every action to `onAction` for hosts
99
- // that care (a channel persisting state, a workflow answering a submit).
100
- const [localSlice, setLocalSlice] = useState<Record<string, unknown>>({});
101
- // LIVE SOURCE. The hook runs either way (hooks cannot be conditional) and no-ops without a
102
- // store, so one component serves both paths without a wrapper deciding for it.
103
- const live = useUnoverseInstance(store, chatId, nodeId);
104
- const appState = useSyncExternalStore(
105
- useCallback((cb: () => void) => (appStore ? appStore.subscribe(cb) : () => {}), [appStore]),
106
- useCallback(() => appStore?.getAppState(), [appStore]),
107
- );
108
- // The SERVER-SENT address first: an org component's URI is org-qualified
109
- // (`unoverse://components/<org>/<name>`) and the bare type cannot rebuild it once two
110
- // orgs share a name. The rebuild-from-type stays as the fallback for events that
111
- // carry no componentUrl. (Still the plain URI, never the withdrawn `@refs` form —
112
- // that suffix broke Studio's local definition route.)
113
- // THE HASH NAMES THE STATE (stateOfUri): the address is read without it, and it seeds the
114
- // public `state` of a static instance that carries none, so `...#page` opens on the page.
115
- const address = store ? (live.componentUrl ?? (live.type ? `unoverse://components/${live.type}` : null)) : (uri ?? null);
116
- const { uri: resolvedUri, state: hashState } = address ? stateOfUri(address) : { uri: null as string | null };
117
- const sourceData = store
118
- ? (extraData ? { ...(live.data ?? {}), ...extraData } : live.data)
119
- : (hashState && data?.state == null ? { state: hashState, ...(data ?? {}) } : data);
120
- // No theme passed → fetch it from the server (the SDK owns zero token values). The
121
- // theme REF comes from the definition itself: a template's `org` names its client's
122
- // theme (`<org>/light`); a universal component previews under the default set
123
- // ("light" — channels pass their org's theme explicitly). Wait for the def before
124
- // fetching (null = no fetch) so the wrong theme never flashes.
125
- const fetched = useUnoverseTheme(client, theme ? null : def ? (def.org ? `${def.org}/light` : "light") : null);
126
- const activeTheme = theme ?? fetched;
127
- // Register the theme's webfonts (served DATA) at document level — see ensureFontStylesheets.
128
- useEffect(() => ensureFontStylesheets(activeTheme), [activeTheme]);
129
-
130
- useEffect(() => {
131
- let alive = true;
132
- setDef(null);
133
- setError(null);
134
- setLocalSlice({});
135
- if (!resolvedUri) return;
136
- client
137
- .readDefinition(resolvedUri)
138
- .then((d) => alive && setDef(d))
139
- .catch((e) => alive && setError(String(e?.message ?? e)));
140
- return () => {
141
- alive = false;
142
- };
143
- }, [client, resolvedUri]);
144
-
145
- // Fill props the instance didn't provide from the definition's defaults; instance `data`
146
- // overrides. This is what lets a partially-emitted component still render its declared
147
- // baseline — e.g. StreamingText's `progressText` default ("Thinking…") shows while the node
148
- // streams and hasn't emitted a progress line. IMPORTANT: a def `default` must therefore be
149
- // a sensible RUNTIME fallback, NOT a demo placeholder — a demo-y default (e.g. a markdown
150
- // showcase on `text`) would mask an empty stream. Keep demo content out of prop defaults.
151
- // Memoized on the def — a streaming component re-renders per chunk; the defaults don't move.
152
- const propDefaults = useMemo(
153
- () =>
154
- Object.fromEntries(
155
- Object.entries((def?.props ?? {}) as Record<string, { default?: unknown }>).map(([k, v]) => [k, v?.default]),
156
- ),
157
- [def],
158
- );
159
-
160
- // ONE ACTION RULE, TWO OWNERS OF THE SLICE. `setValue` writes THIS instance's keys; where
161
- // they land is the only difference between the two sources, and the component never knows.
162
- const handleAction = useMemo<ActionHandler>(
163
- () =>
164
- store
165
- ? (action, scope) =>
166
- dispatchAction(action, scope, {
167
- store,
168
- chatId: chatId ?? "",
169
- nodeId: nodeId ?? "",
170
- appStore,
171
- // Server actions carry the INSTANCE context (which component + its current
172
- // slice) so the channel can forward the submitted state with the action.
173
- sendToServer: onAction
174
- ? (t, payload) =>
175
- onAction(t, payload, {
176
- chatId,
177
- nodeId,
178
- type: live.type,
179
- state: store.get(chatId ?? "", nodeId ?? ""),
180
- })
181
- : undefined,
182
- })
183
- : (action, payload, meta) => {
184
- // No store, so the slice is local. This is what makes a tab work in a preview,
185
- // a content panel or an email-shaped host with no state code in between.
186
- if (action && typeof action === "object" && (action as { type?: string }).type === "setValue") {
187
- const values = ((action as { values?: { key: string; value: unknown }[] }).values ?? []).filter(
188
- (v) => v && typeof v.key === "string",
189
- );
190
- if (values.length)
191
- setLocalSlice((prev) => ({ ...prev, ...Object.fromEntries(values.map((v) => [v.key, v.value])) }));
192
- }
193
- onAction?.(action, payload, meta);
194
- },
195
- [store, chatId, nodeId, onAction, live.type, appStore],
196
- );
197
-
198
- // ARROW KEYS STEP A TEMPLATE. A template's steps (the served stateTree: `on` + its
199
- // substates) move with Left and Right while it holds the main place, through the same
200
- // setValue its own Back and Next make, so the step is saved and reported exactly as a
201
- // click would be. Forms (components) are left alone: their steps are confirmations.
202
- // Typing in a field never steps.
203
- const slice = store ? sourceData : localSlice;
204
- useEffect(() => {
205
- const d = def as (UnoverseDefinition & {
206
- kind?: string;
207
- stateTree?: { name: string; on?: string; states?: { name: string }[] }[];
208
- statePlaces?: Record<string, string>;
209
- }) | null;
210
- if (d?.kind !== "template") return;
211
- const face = String((slice as Record<string, unknown> | undefined)?.state ?? d.state?.state ?? "");
212
- const tree = (d.stateTree ?? []).find((s) => s.name === face);
213
- const steps = tree?.states?.map((s) => s.name) ?? [];
214
- if (!tree?.on || steps.length < 2 || d.statePlaces?.[face] !== "main") return;
215
- const on = tree.on;
216
- const onKey = (e: KeyboardEvent) => {
217
- if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
218
- const t = e.target as HTMLElement | null;
219
- if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return;
220
- const cur = String((slice as Record<string, unknown> | undefined)?.[on] ?? d.state?.[on] ?? steps[0]);
221
- const i = steps.indexOf(cur) + (e.key === "ArrowRight" ? 1 : -1);
222
- if (i < 0 || i >= steps.length) return;
223
- e.preventDefault();
224
- handleAction({ type: "setValue", values: [{ key: on, value: steps[i] }] }, {});
225
- };
226
- document.addEventListener("keydown", onKey);
227
- return () => document.removeEventListener("keydown", onKey);
228
- }, [def, slice, handleAction]);
229
-
230
- // Loading/error hints carry NO styling — the SDK owns zero styles (tokens or hints).
231
- if (error) return <div>Unoverse error: {error}</div>;
232
- if (store && !live.type) return <div>waiting for COMPONENT_INIT…</div>;
233
- if (!def || !activeTheme) return <div>Loading {resolvedUri ?? "component"}…</div>;
234
- // Internal state initial values (def.state) sit beneath the live slice — the
235
- // microapp's own keys (step/phase/…) start at their authored values and move only
236
- // when its actions write them. Props stay the EXTERNAL contract.
237
- // The local slice sits ON TOP: a value the component wrote itself is the current one.
238
- const merged = store
239
- ? { ...propDefaults, ...(appState ?? {}), ...(def?.state ?? {}), ...(sourceData ?? {}) }
240
- : { ...propDefaults, ...(def?.state ?? {}), ...(sourceData ?? {}), ...localSlice };
241
- // Inject the served keyframes + apply the SERVED base render-root (theme.root — DS typography
242
- // + font-smoothing). When ISOLATED, theme.root is applied to the shadow-root container inside
243
- // IsolatedRoot (a REAL box — reliable font cascade in the shadow tree). When NOT isolated the
244
- // host owns isolation, so apply it here on a `display:contents` wrapper (out of layout while
245
- // its inherited props cascade) — the design-system baseline either way.
246
- const keyframes = <style>{keyframesCss(activeTheme)}</style>;
247
- // The shared pieces this definition NAMES travel beside it and are threaded through
248
- // every recursion, so a reference nested in a Switch branch or an Each item resolves too.
249
- // A DEFINITION THAT CARRIES SLOTS RESOLVES THEM — the rule the SDK already applies to
250
- // an app tree, applied here too. Without it a rendered TEMPLATE drew its frame and every
251
- // section resolved to null (render.tsx: `slot ? slot(node) : null`), so a page that
252
- // STREAMED IN rather than being mounted by an app came out blank. Mounted templates were
253
- // fine because `app.tsx` hands that path a resolver; instances never got one, and a
254
- // definition rendering its own parts nowhere is a renderer bug, not a design decision.
255
- //
256
- // Generic and dumb, like the rest of it: the slot's claim picks instances out of the
257
- // SAME store by the face they wear, in the order the store holds them. It knows nothing
258
- // about templates, days or cards — only "which instances match this claim".
259
- //
260
- // TEMPLATES ONLY, and the boundary matters: an ordinary component may carry the
261
- // conversation's FLOW slot (`select: {}` inside a chat's assistant turn), and that one
262
- // belongs to the app's timeline, which scopes it to a single turn's unclaimed
263
- // components. Resolving it here too rendered every instance in the store a second time
264
- // — the composed page appeared in its surface AND in the chat beside it.
265
- const resolveSlot: SlotResolver | undefined = (store && (def as { sections?: unknown }).sections) || place
266
- ? (node, key) => {
267
- if (node.type === "Place") return place?.(String((node as { name?: unknown }).name ?? "")) ?? null;
268
- if (!store) return null;
269
- const claim = node.select?.where;
270
- const pointers = store
271
- .getResponses()
272
- .flatMap((r) => r.components)
273
- .filter((p) => {
274
- const { chatId: c, nodeId: n } = store.split(p);
275
- if (n === nodeId) return false; // never claim the instance drawing the slot
276
- return claim ? matches(claim, (store.get(c, n) ?? {}) as Record<string, unknown>) : true;
277
- })
278
- .slice(0, node.select?.limit ?? undefined);
279
- if (!pointers.length)
280
- return node.fallback ? renderNode(node.fallback, {}, handleAction, activeTheme, key ?? "fb", undefined, def.defs) : null;
281
- return pointers.map((p) => {
282
- const { chatId: c, nodeId: n } = store.split(p);
283
- return (
284
- <UnoverseComponent
285
- key={n}
286
- client={client}
287
- store={store}
288
- chatId={c}
289
- nodeId={n}
290
- onAction={onAction}
291
- theme={activeTheme}
292
- appStore={appStore}
293
- />
294
- );
295
- });
296
- }
297
- : undefined;
298
- const tree = renderNode(def.root, merged, handleAction, activeTheme, undefined, resolveSlot, def.defs);
299
- // The isolation container MIRRORS the component root's declared sizing intent (§4a:
300
- // the component owns its size; the host responds). A root that declares
301
- // `height: "full"` needs a definite chain — the container passes the host's height
302
- // through (100%). Any other root keeps an auto container → fit-to-content, the §5a
303
- // default (a card in a timeline must NOT fill). Without this, a full-height
304
- // component's 100% resolves against an auto box and silently collapses.
305
- const fillsHost = fill || ((def.root as { style?: Record<string, unknown> })?.style?.height ?? null) === "full";
306
- const isolatedRootStyle = {
307
- ...(activeTheme.root as CSSProperties),
308
- ...(fillsHost ? { height: "100%" } : {}),
309
- ...(fill ? { flex: "1 1 auto", minHeight: 0 } : {}),
310
- };
311
- if (isolate) {
312
- return (
313
- <IsolatedRoot rootStyle={isolatedRootStyle}>
314
- {keyframes}
315
- {tree}
316
- </IsolatedRoot>
317
- );
318
- }
319
- return (
320
- <>
321
- {keyframes}
322
- <div style={{ display: "contents", ...(activeTheme.root as CSSProperties) }}>{tree}</div>
323
- </>
324
- );
325
- }