@ggui-ai/iframe-runtime 0.1.0-rc.3 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/channel-transport.d.ts +15 -15
- package/dist/channel-transport.js +32 -35
- package/dist/channels/channel-payload.d.ts +2 -2
- package/dist/channels/channel-payload.js +1 -1
- package/dist/channels/data.d.ts +14 -19
- package/dist/channels/data.js +13 -31
- package/dist/channels/drain-ack.d.ts +2 -2
- package/dist/channels/drain-ack.js +1 -1
- package/dist/channels/feedback.d.ts +1 -1
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/index.js +1 -1
- package/dist/channels/props-update.d.ts +33 -37
- package/dist/channels/props-update.js +30 -106
- package/dist/channels/render-frame.d.ts +52 -0
- package/dist/channels/render-frame.js +53 -0
- package/dist/channels/system.d.ts +1 -1
- package/dist/compiled-validators.d.ts +15 -0
- package/dist/compiled-validators.js +31 -0
- package/dist/context-observer.d.ts +68 -8
- package/dist/context-observer.js +25 -45
- package/dist/events-polling.d.ts +95 -0
- package/dist/events-polling.js +143 -0
- package/dist/gadget-loader.d.ts +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/host-context-emitter.d.ts +84 -20
- package/dist/host-context-emitter.js +159 -29
- package/dist/iframe-runtime.js +98 -59
- package/dist/index.d.ts +18 -13
- package/dist/index.js +1 -1
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +5 -5
- package/dist/mcp-app-iframe-host.d.ts +9 -9
- package/dist/mcp-app-iframe-host.js +16 -17
- package/dist/meta-parse.d.ts +44 -0
- package/dist/meta-parse.js +390 -0
- package/dist/observability.d.ts +6 -6
- package/dist/protocol-error.d.ts +7 -7
- package/dist/provisional-renderer.d.ts +1 -1
- package/dist/provisional-renderer.js +1 -1
- package/dist/react-renderer.d.ts +10 -11
- package/dist/react-renderer.js +8 -8
- package/dist/registry-subscribe.d.ts +27 -9
- package/dist/registry-subscribe.js +32 -27
- package/dist/render-item.d.ts +83 -0
- package/dist/{stack-item-renderer.js → render-item.js} +106 -106
- package/dist/runtime.d.ts +183 -131
- package/dist/runtime.js +1264 -1330
- package/dist/status-dom.d.ts +52 -47
- package/dist/status-dom.js +51 -34
- package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
- package/dist/system-cards/NoCredentialsCard.js +1 -1
- package/dist/system-cards/ProtocolProbeCard.js +2 -2
- package/dist/system-cards/index.d.ts +4 -4
- package/dist/system-cards/index.js +4 -4
- package/dist/types.d.ts +70 -34
- package/dist/types.js +1 -1
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +2 -2
- package/dist/wire-config.d.ts +54 -82
- package/dist/wire-config.js +61 -93
- package/package.json +9 -8
- package/dist/bootstrap.d.ts +0 -72
- package/dist/bootstrap.js +0 -500
- package/dist/canvas/animator/animator-host.d.ts +0 -63
- package/dist/canvas/animator/animator-host.js +0 -105
- package/dist/canvas/animator/animator-pill.d.ts +0 -42
- package/dist/canvas/animator/animator-pill.js +0 -119
- package/dist/canvas/animator/state-machine.d.ts +0 -106
- package/dist/canvas/animator/state-machine.js +0 -150
- package/dist/canvas/canvas-shell.d.ts +0 -77
- package/dist/canvas/canvas-shell.js +0 -117
- package/dist/canvas/display-mode-policy.d.ts +0 -104
- package/dist/canvas/display-mode-policy.js +0 -98
- package/dist/canvas/mount.d.ts +0 -150
- package/dist/canvas/mount.js +0 -202
- package/dist/canvas/nav-stack.d.ts +0 -133
- package/dist/canvas/nav-stack.js +0 -172
- package/dist/channels/push.d.ts +0 -47
- package/dist/channels/push.js +0 -54
- package/dist/stack-item-renderer.d.ts +0 -103
- package/dist/stack.d.ts +0 -90
- package/dist/stack.js +0 -118
package/dist/canvas/nav-stack.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NavStackModel.
|
|
3
|
-
*
|
|
4
|
-
* Canvas-local navigation stack. **Distinct from the server's
|
|
5
|
-
* `StackModel`**:
|
|
6
|
-
*
|
|
7
|
-
* - Server's `StackModel` (`@ggui-ai/iframe-runtime/stack.ts`)
|
|
8
|
-
* tracks every stack item the agent has pushed in this session.
|
|
9
|
-
* The durable shape; mutated only by server-side push/pop/replace
|
|
10
|
-
* deliveries.
|
|
11
|
-
* - NavStackModel tracks the USER'S NAVIGATION HISTORY through
|
|
12
|
-
* those items. When the user back-navigates, the popped item
|
|
13
|
-
* leaves navStack but stays in server's StackModel.
|
|
14
|
-
*
|
|
15
|
-
* These can diverge:
|
|
16
|
-
*
|
|
17
|
-
* ```
|
|
18
|
-
* server StackModel: [a, b, c] ← all items the agent has pushed
|
|
19
|
-
* navStack: [a, b] ← user has back-navigated away from c
|
|
20
|
-
* activeItem: b ← top of navStack; the mounted screen
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* The server-side `activeStackItemId` field (on AckPayload +
|
|
24
|
-
* SessionRecord) follows navStack-top via `canvas_navigated` outbound
|
|
25
|
-
* envelopes, so `ggui_consume` returns actions for the active item
|
|
26
|
-
* regardless of what the agent has pushed since.
|
|
27
|
-
*
|
|
28
|
-
* Pure model — no DOM, no React. Methods return the new state (or
|
|
29
|
-
* mutate in-place; v1 mutates for simplicity since the canvas shell
|
|
30
|
-
* holds the only reference). The renderer consumes the model via a
|
|
31
|
-
* version counter (bumped on every mutation) so React picks up
|
|
32
|
-
* changes without deep equality.
|
|
33
|
-
*
|
|
34
|
-
* Two listener seams (distinct semantics):
|
|
35
|
-
*
|
|
36
|
-
* - `onMutation` fires on EVERY state mutation including in-place
|
|
37
|
-
* replace (ggui_update patch). The canvas shell uses this to
|
|
38
|
-
* re-read the version counter and re-render — without it, a
|
|
39
|
-
* props-only update on the active item bumps the version but
|
|
40
|
-
* React never schedules a render and the screen shows stale props.
|
|
41
|
-
* - `onNavChange` fires only when the active item actually changes
|
|
42
|
-
* (new push, pop, reset that lands on a different top). The
|
|
43
|
-
* live-channel `canvas_navigated` envelope is emitted from here, so
|
|
44
|
-
* an in-place replace doesn't spuriously tell the server the
|
|
45
|
-
* user back-navigated.
|
|
46
|
-
*/
|
|
47
|
-
export class NavStackModel {
|
|
48
|
-
items = [];
|
|
49
|
-
mutationListener = null;
|
|
50
|
-
navChangeListener = null;
|
|
51
|
-
versionCounter = 0;
|
|
52
|
-
/**
|
|
53
|
-
* Replace the entire nav stack. Used on subscribe ack to seed
|
|
54
|
-
* navStack from the server's `activeStackItemId` + the snapshot's
|
|
55
|
-
* stack array.
|
|
56
|
-
*/
|
|
57
|
-
reset(items) {
|
|
58
|
-
const previous = this.peek();
|
|
59
|
-
this.items = [...items];
|
|
60
|
-
this.versionCounter += 1;
|
|
61
|
-
this.mutationListener?.();
|
|
62
|
-
this.navChangeListener?.({
|
|
63
|
-
direction: 'forward',
|
|
64
|
-
activeItemId: this.peek()?.id ?? null,
|
|
65
|
-
previousActiveItemId: previous?.id ?? null,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Append an item to the top. Called when a `stack_item_appended`
|
|
70
|
-
* envelope arrives (server pushed a new item).
|
|
71
|
-
*
|
|
72
|
-
* If an item with the same id already exists, the existing entry is
|
|
73
|
-
* REPLACED IN PLACE (preserves nav position) — matches the server-
|
|
74
|
-
* side StackModel's upsert semantics so `ggui_update` patches don't
|
|
75
|
-
* shift the user's nav state. The mutation listener still fires (so
|
|
76
|
-
* React re-renders with the new props) but the nav-change listener
|
|
77
|
-
* is suppressed (active item hasn't changed).
|
|
78
|
-
*/
|
|
79
|
-
push(item) {
|
|
80
|
-
const existingIdx = this.items.findIndex((i) => i.id === item.id);
|
|
81
|
-
if (existingIdx >= 0) {
|
|
82
|
-
this.items[existingIdx] = item;
|
|
83
|
-
this.versionCounter += 1;
|
|
84
|
-
this.mutationListener?.();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const previous = this.peek();
|
|
88
|
-
this.items.push(item);
|
|
89
|
-
this.versionCounter += 1;
|
|
90
|
-
this.mutationListener?.();
|
|
91
|
-
this.navChangeListener?.({
|
|
92
|
-
direction: 'forward',
|
|
93
|
-
activeItemId: item.id,
|
|
94
|
-
previousActiveItemId: previous?.id ?? null,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Pop the top item. Returns the new active item (or null if the
|
|
99
|
-
* stack is now empty). No-op if already empty.
|
|
100
|
-
*/
|
|
101
|
-
pop() {
|
|
102
|
-
if (this.items.length === 0)
|
|
103
|
-
return null;
|
|
104
|
-
const popped = this.items.pop() ?? null;
|
|
105
|
-
this.versionCounter += 1;
|
|
106
|
-
const active = this.peek();
|
|
107
|
-
this.mutationListener?.();
|
|
108
|
-
this.navChangeListener?.({
|
|
109
|
-
direction: 'back',
|
|
110
|
-
activeItemId: active?.id ?? null,
|
|
111
|
-
previousActiveItemId: popped?.id ?? null,
|
|
112
|
-
});
|
|
113
|
-
return active;
|
|
114
|
-
}
|
|
115
|
-
/** Top of the nav stack (the currently-mounted item), or null when empty. */
|
|
116
|
-
peek() {
|
|
117
|
-
return this.items[this.items.length - 1] ?? null;
|
|
118
|
-
}
|
|
119
|
-
/** Read-only snapshot of the full nav stack in order (root → top). */
|
|
120
|
-
snapshot() {
|
|
121
|
-
return this.items;
|
|
122
|
-
}
|
|
123
|
-
/** Item count. */
|
|
124
|
-
size() {
|
|
125
|
-
return this.items.length;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Monotonic mutation counter — bumped on every push/pop/reset.
|
|
129
|
-
* React consumers can use this as the `useSyncExternalStore`
|
|
130
|
-
* snapshot value: comparing version numbers is O(1).
|
|
131
|
-
*/
|
|
132
|
-
version() {
|
|
133
|
-
return this.versionCounter;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Install a listener that fires on every state mutation (push, pop,
|
|
137
|
-
* reset, in-place replace). React consumers bridge this to a
|
|
138
|
-
* version-counter `useState` so the component re-renders for every
|
|
139
|
-
* change — including in-place replaces from `ggui_update` patches
|
|
140
|
-
* where the active item is unchanged but its props have moved.
|
|
141
|
-
*
|
|
142
|
-
* Single-subscriber model — the canvas shell is the only caller.
|
|
143
|
-
* Returns an unsubscribe function.
|
|
144
|
-
*/
|
|
145
|
-
onMutation(listener) {
|
|
146
|
-
this.mutationListener = listener;
|
|
147
|
-
return () => {
|
|
148
|
-
if (this.mutationListener === listener)
|
|
149
|
-
this.mutationListener = null;
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Install a listener that fires only on nav-changing mutations
|
|
154
|
-
* (push of new id, pop, reset). Used by the canvas shell to emit
|
|
155
|
-
* the live-channel `canvas_navigated` envelope so the server can
|
|
156
|
-
* update `activeStackItemId` and abort in-flight cold-gen for the
|
|
157
|
-
* previous active item.
|
|
158
|
-
*
|
|
159
|
-
* In-place replaces from `ggui_update` patches do NOT fire this
|
|
160
|
-
* listener — the active item hasn't moved. Use {@link onMutation}
|
|
161
|
-
* for React re-render signaling.
|
|
162
|
-
*
|
|
163
|
-
* Single-subscriber model. Returns an unsubscribe function.
|
|
164
|
-
*/
|
|
165
|
-
onNavChange(listener) {
|
|
166
|
-
this.navChangeListener = listener;
|
|
167
|
-
return () => {
|
|
168
|
-
if (this.navChangeListener === listener)
|
|
169
|
-
this.navChangeListener = null;
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
}
|
package/dist/channels/push.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `push` channel handler — folds an inbound `push` frame into the
|
|
3
|
-
* stack model, re-renders the placeholder DOM, refreshes the status
|
|
4
|
-
* line, then (when triad wiring is present) applies the new stack
|
|
5
|
-
* through the React renderer + activates the channel-transport router
|
|
6
|
-
* against the new top of stack.
|
|
7
|
-
*
|
|
8
|
-
* The placeholder side-effects (stack model upsert + DOM render +
|
|
9
|
-
* status update) are unconditional — they're load-bearing for the
|
|
10
|
-
* C7a-era specs that boot WITHOUT triad wiring (boot.test.ts in
|
|
11
|
-
* particular). Triad-only consumers (production runtime) pass the
|
|
12
|
-
* `getStackRenderer` + `getChannelTransport` thunks; tests that
|
|
13
|
-
* exercise the placeholder path only leave them off.
|
|
14
|
-
*
|
|
15
|
-
* Pre-B3b: the placeholder fold lived in a separate
|
|
16
|
-
* `handleServerMessage` call in `runtime.ts`, fired before triad
|
|
17
|
-
* dispatch. B3b absorbed both into this single handler so the
|
|
18
|
-
* registry-owned WS transport is the sole dispatch site.
|
|
19
|
-
*/
|
|
20
|
-
import type { ChannelHandler } from '@ggui-ai/channel-client';
|
|
21
|
-
import type { PushPayload } from '@ggui-ai/protocol';
|
|
22
|
-
import type { ChannelTransportRouter } from '../channel-transport.js';
|
|
23
|
-
import type { StackRenderer } from '../stack-item-renderer.js';
|
|
24
|
-
import type { StackModel } from '../stack.js';
|
|
25
|
-
import { type StatusRefs } from '../status-dom.js';
|
|
26
|
-
export interface PushHandlerDeps {
|
|
27
|
-
readonly stackModel: StackModel;
|
|
28
|
-
/**
|
|
29
|
-
* Status DOM refs the placeholder fold updates. Required — even
|
|
30
|
-
* triad-mode consumers want the status text to reflect the live
|
|
31
|
-
* count for operator-visible boot debugging.
|
|
32
|
-
*/
|
|
33
|
-
readonly statusRefs: StatusRefs;
|
|
34
|
-
/**
|
|
35
|
-
* Triad-mode hook — returns the React stack renderer. Absent in
|
|
36
|
-
* placeholder-only mode (tests, and callers that don't mount the
|
|
37
|
-
* React stack).
|
|
38
|
-
*/
|
|
39
|
-
readonly getStackRenderer?: () => StackRenderer;
|
|
40
|
-
/**
|
|
41
|
-
* Triad-mode hook — returns the per-channel transport router so the
|
|
42
|
-
* push handler can activate / tear down `source.tool` channels on
|
|
43
|
-
* the new top of stack. Absent in placeholder-only mode.
|
|
44
|
-
*/
|
|
45
|
-
readonly getChannelTransport?: () => ChannelTransportRouter;
|
|
46
|
-
}
|
|
47
|
-
export declare function createPushHandler(deps: PushHandlerDeps): ChannelHandler<PushPayload>;
|
package/dist/channels/push.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `push` channel handler — folds an inbound `push` frame into the
|
|
3
|
-
* stack model, re-renders the placeholder DOM, refreshes the status
|
|
4
|
-
* line, then (when triad wiring is present) applies the new stack
|
|
5
|
-
* through the React renderer + activates the channel-transport router
|
|
6
|
-
* against the new top of stack.
|
|
7
|
-
*
|
|
8
|
-
* The placeholder side-effects (stack model upsert + DOM render +
|
|
9
|
-
* status update) are unconditional — they're load-bearing for the
|
|
10
|
-
* C7a-era specs that boot WITHOUT triad wiring (boot.test.ts in
|
|
11
|
-
* particular). Triad-only consumers (production runtime) pass the
|
|
12
|
-
* `getStackRenderer` + `getChannelTransport` thunks; tests that
|
|
13
|
-
* exercise the placeholder path only leave them off.
|
|
14
|
-
*
|
|
15
|
-
* Pre-B3b: the placeholder fold lived in a separate
|
|
16
|
-
* `handleServerMessage` call in `runtime.ts`, fired before triad
|
|
17
|
-
* dispatch. B3b absorbed both into this single handler so the
|
|
18
|
-
* registry-owned WS transport is the sole dispatch site.
|
|
19
|
-
*/
|
|
20
|
-
import { refreshStackDom, setConnectedStatus, } from '../status-dom.js';
|
|
21
|
-
export function createPushHandler(deps) {
|
|
22
|
-
return {
|
|
23
|
-
type: 'push',
|
|
24
|
-
onMessage: async (payload) => {
|
|
25
|
-
// 1. Placeholder fold — unconditional. Mirrors the pre-B3b
|
|
26
|
-
// handleServerMessage path so non-triad consumers see the
|
|
27
|
-
// correct stack count + placeholder DOM.
|
|
28
|
-
deps.stackModel.upsert(payload.stackItem);
|
|
29
|
-
refreshStackDom(deps.statusRefs, deps.stackModel);
|
|
30
|
-
setConnectedStatus(deps.statusRefs, deps.stackModel);
|
|
31
|
-
// 2. Triad-mode work — only fires when the production runtime
|
|
32
|
-
// supplied the renderer + transport thunks.
|
|
33
|
-
const stackRenderer = deps.getStackRenderer?.();
|
|
34
|
-
const channelTransport = deps.getChannelTransport?.();
|
|
35
|
-
if (stackRenderer === undefined)
|
|
36
|
-
return;
|
|
37
|
-
const snapshot = deps.stackModel.snapshot();
|
|
38
|
-
await stackRenderer.applyStack(snapshot);
|
|
39
|
-
if (channelTransport === undefined)
|
|
40
|
-
return;
|
|
41
|
-
const top = snapshot[snapshot.length - 1];
|
|
42
|
-
if (top !== undefined &&
|
|
43
|
-
top.type !== 'mcpApps' &&
|
|
44
|
-
top.type !== 'system') {
|
|
45
|
-
channelTransport.applyStackItem({
|
|
46
|
-
stackItemId: top.id,
|
|
47
|
-
...(top.streamSpec !== undefined
|
|
48
|
-
? { streamSpec: top.streamSpec }
|
|
49
|
-
: {}),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { SessionStackEntry } from '@ggui-ai/protocol';
|
|
3
|
-
import { type WireConfig } from '@ggui-ai/wire';
|
|
4
|
-
import type { StreamBus } from './wire-config.js';
|
|
5
|
-
export interface StackItemRendererOptions {
|
|
6
|
-
/** The stack item to render. */
|
|
7
|
-
readonly stackItem: SessionStackEntry;
|
|
8
|
-
/** Wire config for this item — pre-scoped via the renderer's
|
|
9
|
-
* `buildScopedConfig(item)` (see
|
|
10
|
-
* `wire-config.ts::RootWireConfigBundle`). */
|
|
11
|
-
readonly scopedWireConfig: WireConfig | null;
|
|
12
|
-
/** Shared stream bus — provisional renderer subscribes to
|
|
13
|
-
* `_ggui:preview` for this item. */
|
|
14
|
-
readonly streamBus: StreamBus;
|
|
15
|
-
/** Session id — used by the mcp-apps iframe host for proxy URLs. */
|
|
16
|
-
readonly sessionId: string;
|
|
17
|
-
/** ggui-server base URL (for mcp-apps proxy). Empty = same-origin. */
|
|
18
|
-
readonly mcpAppsServerBaseUrl?: string;
|
|
19
|
-
/** Theme id passed to ReactComponentRenderer for scoped CSS. */
|
|
20
|
-
readonly themeId?: string;
|
|
21
|
-
/**
|
|
22
|
-
* Theme color mode. Resolves the dark variant of `themeId` when set
|
|
23
|
-
* to `'dark'`. Falls through to `'light'` if absent — matches the
|
|
24
|
-
* design registry's `getTheme(id, mode?)` default.
|
|
25
|
-
*/
|
|
26
|
-
readonly themeMode?: 'light' | 'dark';
|
|
27
|
-
readonly cssOverrides?: string;
|
|
28
|
-
readonly onError?: (err: Error) => void;
|
|
29
|
-
readonly onRequestRepair?: (err: Error) => void;
|
|
30
|
-
/**
|
|
31
|
-
* Optional outer wrapper applied AROUND the GguiWireProvider — used
|
|
32
|
-
* by `bootProduction` to install the `<ContextStateHost>` provider
|
|
33
|
-
* tree. Composition order:
|
|
34
|
-
*
|
|
35
|
-
* wrapOuter(<GguiWireProvider><userComponent/></GguiWireProvider>)
|
|
36
|
-
*
|
|
37
|
-
* `wrapOuter` returns the wrapped node; passing `undefined` is
|
|
38
|
-
* equivalent to the identity function (no extra wrapping). The
|
|
39
|
-
* self-contained boot path inlines its own ContextStateHost via the
|
|
40
|
-
* top-level `renderWrapper`; this seam exists for the WS-driven
|
|
41
|
-
* triad path where the per-item React mount is the only mount
|
|
42
|
-
* surface.
|
|
43
|
-
*/
|
|
44
|
-
readonly wrapOuter?: (mountedTree: ReactNode) => ReactNode;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Discriminator for WHICH renderer is currently mounted under this
|
|
48
|
-
* stack-item handle. Transitions: 'provisional' → 'react' when
|
|
49
|
-
* componentCode lands; 'react' → 'react' on code/prop updates;
|
|
50
|
-
* 'mcpApps' is terminal for its lifetime (mcp-apps items never
|
|
51
|
-
* become components + vice versa).
|
|
52
|
-
*/
|
|
53
|
-
type MountedKind = 'provisional' | 'react' | 'mcpApps' | 'none';
|
|
54
|
-
export interface StackItemHandle {
|
|
55
|
-
/**
|
|
56
|
-
* Apply an update to the stack-item handle. Kind changes (e.g.
|
|
57
|
-
* provisional → react when componentCode lands) tear down the old
|
|
58
|
-
* mount and stand up the replacement; same-kind updates route to
|
|
59
|
-
* the inner renderer's `update()`.
|
|
60
|
-
*/
|
|
61
|
-
update(next: StackItemRendererOptions): Promise<void>;
|
|
62
|
-
/** Tear down the active mount + unsubscribe from the StreamBus. */
|
|
63
|
-
unmount(): void;
|
|
64
|
-
/** Current render kind. Test-only — not load-bearing. */
|
|
65
|
-
readonly kind: MountedKind;
|
|
66
|
-
}
|
|
67
|
-
export declare function renderStackItem(container: HTMLElement, opts: StackItemRendererOptions): Promise<StackItemHandle>;
|
|
68
|
-
export interface StackRenderContext {
|
|
69
|
-
readonly containerFor: (stackItemId: string) => HTMLElement;
|
|
70
|
-
readonly getScopedWireConfig: (item: SessionStackEntry) => WireConfig | null;
|
|
71
|
-
readonly streamBus: StreamBus;
|
|
72
|
-
readonly sessionId: string;
|
|
73
|
-
readonly mcpAppsServerBaseUrl?: string;
|
|
74
|
-
readonly themeId?: string;
|
|
75
|
-
/** Theme color mode — see {@link StackItemRendererOptions.themeMode}. */
|
|
76
|
-
readonly themeMode?: 'light' | 'dark';
|
|
77
|
-
readonly onError?: (err: Error) => void;
|
|
78
|
-
readonly onRequestRepair?: (err: Error) => void;
|
|
79
|
-
/**
|
|
80
|
-
* Optional outer wrapper for each stack-item React mount — see
|
|
81
|
-
* {@link StackItemRendererOptions.wrapOuter}. `bootProduction` uses
|
|
82
|
-
* this to inject `<ContextStateHost>` around every per-item React
|
|
83
|
-
* root, mirroring the wrap `bootSelfContained` performs at its
|
|
84
|
-
* single React root. Returning the input unchanged is equivalent
|
|
85
|
-
* to passing `undefined`.
|
|
86
|
-
*/
|
|
87
|
-
readonly getOuterWrapper?: (item: SessionStackEntry) => ((mountedTree: ReactNode) => ReactNode) | undefined;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Minimal stack-level orchestrator — keyed by stackItem.id, mounts
|
|
91
|
-
* new items, updates existing ones, unmounts removed ones.
|
|
92
|
-
*
|
|
93
|
-
* The production renderer tree uses this; tests can drive the
|
|
94
|
-
* individual `renderStackItem` entry point directly.
|
|
95
|
-
*/
|
|
96
|
-
export declare class StackRenderer {
|
|
97
|
-
private readonly ctx;
|
|
98
|
-
private handles;
|
|
99
|
-
constructor(ctx: StackRenderContext);
|
|
100
|
-
applyStack(stack: readonly SessionStackEntry[]): Promise<void>;
|
|
101
|
-
unmountAll(): void;
|
|
102
|
-
}
|
|
103
|
-
export {};
|
package/dist/stack.d.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stack state + structural-placeholder DOM rendering.
|
|
3
|
-
*
|
|
4
|
-
* This module owns the placeholder render path: each stack item is
|
|
5
|
-
* drawn as a structural placeholder showing id + description/prompt.
|
|
6
|
-
* The React mount path that executes `componentCode` lives elsewhere
|
|
7
|
-
* and swaps in over the same DOM seam.
|
|
8
|
-
*
|
|
9
|
-
* The model + the DOM render are split so:
|
|
10
|
-
* - `StackModel` is testable in isolation (no DOM needed for unit
|
|
11
|
-
* tests around upsert / replace / clear semantics);
|
|
12
|
-
* - `renderStackInto(el, model)` is the only place that touches DOM,
|
|
13
|
-
* making it the seam the React mount path swaps out without
|
|
14
|
-
* disturbing the WS message handler.
|
|
15
|
-
*
|
|
16
|
-
* Stack ordering follows the wire shape: the ack's initial `stack` is
|
|
17
|
-
* displayed in array order; each `push` either replaces an item with
|
|
18
|
-
* the same id (preserving its position) or appends at the tail.
|
|
19
|
-
*/
|
|
20
|
-
import type { SessionStackEntry } from '@ggui-ai/protocol';
|
|
21
|
-
/**
|
|
22
|
-
* Construction options for {@link StackModel}.
|
|
23
|
-
*
|
|
24
|
-
* @public
|
|
25
|
-
*/
|
|
26
|
-
export interface StackModelOptions {
|
|
27
|
-
/**
|
|
28
|
-
* When present, the model renders only the single stack entry with
|
|
29
|
-
* this `id` — the rest of the session stack is filtered out on
|
|
30
|
-
* `setAll` and `upsert` ignores every entry whose id ≠ this value.
|
|
31
|
-
* Enables single-item renderer mode, driven by the
|
|
32
|
-
* `bootstrap.stackItemId` field set on a per-item session
|
|
33
|
-
* resource.
|
|
34
|
-
*
|
|
35
|
-
* Absent → multi-item mode: the model renders the entire stack as
|
|
36
|
-
* the server delivers it.
|
|
37
|
-
*
|
|
38
|
-
* The filter is immutable for a model's lifetime (no setter).
|
|
39
|
-
* Consumers that need a different filter MUST construct a new
|
|
40
|
-
* model — matches the "model is the truth" doctrine below and
|
|
41
|
-
* makes filter-change intent explicit in code review.
|
|
42
|
-
*/
|
|
43
|
-
readonly filterToItemId?: string;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* In-memory model of the visible stack. Owned by the runtime; mutated
|
|
47
|
-
* via the methods (no setter — keeps the "model is the truth" doctrine
|
|
48
|
-
* obvious in code reviews).
|
|
49
|
-
*/
|
|
50
|
-
export declare class StackModel {
|
|
51
|
-
private items;
|
|
52
|
-
private readonly filterToItemId;
|
|
53
|
-
constructor(options?: StackModelOptions);
|
|
54
|
-
/**
|
|
55
|
-
* Replace the entire stack — used after the subscribe ack lands.
|
|
56
|
-
*
|
|
57
|
-
* Under `filterToItemId` (single-item mode), the incoming list is
|
|
58
|
-
* filtered to just the matching entry. If no entry matches, the
|
|
59
|
-
* model ends up empty — the server no longer includes the pinned
|
|
60
|
-
* item and the renderer's empty-state is the correct surface.
|
|
61
|
-
*/
|
|
62
|
-
setAll(items: readonly SessionStackEntry[]): void;
|
|
63
|
-
/**
|
|
64
|
-
* Upsert a stack item. If an entry with the same id already exists
|
|
65
|
-
* its slot is replaced in place (preserves ordering); otherwise the
|
|
66
|
-
* item is appended at the tail. Mirrors `GguiSession.tsx`'s push
|
|
67
|
-
* reducer.
|
|
68
|
-
*
|
|
69
|
-
* Under `filterToItemId` (single-item mode): entries whose id does
|
|
70
|
-
* not match the filter are ignored. The renderer's live-channel
|
|
71
|
-
* subscription still delivers every push for the session (subscribe
|
|
72
|
-
* remains session-scoped); this filter is the client-side pin that
|
|
73
|
-
* keeps the per-card iframe bound to its one stack entry.
|
|
74
|
-
*/
|
|
75
|
-
upsert(item: SessionStackEntry): void;
|
|
76
|
-
/** Read-only view — callers MUST NOT mutate. */
|
|
77
|
-
snapshot(): readonly SessionStackEntry[];
|
|
78
|
-
/** Item count — convenience for status-line rendering. */
|
|
79
|
-
size(): number;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Render the stack model into the target list element as structural
|
|
83
|
-
* `<li data-ggui-stack-item="<id>">` placeholders. Idempotent — every
|
|
84
|
-
* call replaces the element's children based on the current model.
|
|
85
|
-
*
|
|
86
|
-
* The DOM shape (data attributes + classnames) is intentionally
|
|
87
|
-
* stable: E2E tests and the React-mount path both need to address
|
|
88
|
-
* stack-item rows by `data-ggui-stack-item` selector.
|
|
89
|
-
*/
|
|
90
|
-
export declare function renderStackInto(target: HTMLElement, model: StackModel): void;
|
package/dist/stack.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory model of the visible stack. Owned by the runtime; mutated
|
|
3
|
-
* via the methods (no setter — keeps the "model is the truth" doctrine
|
|
4
|
-
* obvious in code reviews).
|
|
5
|
-
*/
|
|
6
|
-
export class StackModel {
|
|
7
|
-
items = [];
|
|
8
|
-
filterToItemId;
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
this.filterToItemId = options.filterToItemId;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Replace the entire stack — used after the subscribe ack lands.
|
|
14
|
-
*
|
|
15
|
-
* Under `filterToItemId` (single-item mode), the incoming list is
|
|
16
|
-
* filtered to just the matching entry. If no entry matches, the
|
|
17
|
-
* model ends up empty — the server no longer includes the pinned
|
|
18
|
-
* item and the renderer's empty-state is the correct surface.
|
|
19
|
-
*/
|
|
20
|
-
setAll(items) {
|
|
21
|
-
if (this.filterToItemId !== undefined) {
|
|
22
|
-
const target = this.filterToItemId;
|
|
23
|
-
this.items = items.filter((item) => item.id === target);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
this.items = [...items];
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Upsert a stack item. If an entry with the same id already exists
|
|
30
|
-
* its slot is replaced in place (preserves ordering); otherwise the
|
|
31
|
-
* item is appended at the tail. Mirrors `GguiSession.tsx`'s push
|
|
32
|
-
* reducer.
|
|
33
|
-
*
|
|
34
|
-
* Under `filterToItemId` (single-item mode): entries whose id does
|
|
35
|
-
* not match the filter are ignored. The renderer's live-channel
|
|
36
|
-
* subscription still delivers every push for the session (subscribe
|
|
37
|
-
* remains session-scoped); this filter is the client-side pin that
|
|
38
|
-
* keeps the per-card iframe bound to its one stack entry.
|
|
39
|
-
*/
|
|
40
|
-
upsert(item) {
|
|
41
|
-
if (this.filterToItemId !== undefined && item.id !== this.filterToItemId) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const idx = this.items.findIndex((existing) => existing.id === item.id);
|
|
45
|
-
if (idx >= 0) {
|
|
46
|
-
const next = [...this.items];
|
|
47
|
-
next[idx] = item;
|
|
48
|
-
this.items = next;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
this.items = [...this.items, item];
|
|
52
|
-
}
|
|
53
|
-
/** Read-only view — callers MUST NOT mutate. */
|
|
54
|
-
snapshot() {
|
|
55
|
-
return this.items;
|
|
56
|
-
}
|
|
57
|
-
/** Item count — convenience for status-line rendering. */
|
|
58
|
-
size() {
|
|
59
|
-
return this.items.length;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Best-effort textual label for a stack item — title-ish for the
|
|
64
|
-
* placeholder UI. Tries `description` → `prompt` → a generic fallback.
|
|
65
|
-
*
|
|
66
|
-
* The wire shape keeps these fields optional on every entry kind
|
|
67
|
-
* (generated, McpApps), so the absence-handling has to be honest.
|
|
68
|
-
*/
|
|
69
|
-
function labelFor(item) {
|
|
70
|
-
if (typeof item.description === 'string' && item.description.length > 0) {
|
|
71
|
-
return item.description;
|
|
72
|
-
}
|
|
73
|
-
// Only generated component items carry `prompt`. McpApps + system
|
|
74
|
-
// variants surface their own description (or a generic fallback).
|
|
75
|
-
if (item.type !== 'mcpApps' && item.type !== 'system') {
|
|
76
|
-
if (typeof item.prompt === 'string' && item.prompt.length > 0) {
|
|
77
|
-
return item.prompt;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return '(untitled stack item)';
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Render the stack model into the target list element as structural
|
|
84
|
-
* `<li data-ggui-stack-item="<id>">` placeholders. Idempotent — every
|
|
85
|
-
* call replaces the element's children based on the current model.
|
|
86
|
-
*
|
|
87
|
-
* The DOM shape (data attributes + classnames) is intentionally
|
|
88
|
-
* stable: E2E tests and the React-mount path both need to address
|
|
89
|
-
* stack-item rows by `data-ggui-stack-item` selector.
|
|
90
|
-
*/
|
|
91
|
-
export function renderStackInto(target, model) {
|
|
92
|
-
// Replace children rather than mutate-in-place — the placeholder
|
|
93
|
-
// doesn't need diffing, and the wire-frame burst rate is low enough
|
|
94
|
-
// (push events arrive roughly per agent turn) that full-replace is
|
|
95
|
-
// cheap.
|
|
96
|
-
target.replaceChildren();
|
|
97
|
-
const items = model.snapshot();
|
|
98
|
-
if (items.length === 0) {
|
|
99
|
-
const empty = target.ownerDocument.createElement('li');
|
|
100
|
-
empty.textContent = '(no stack items yet)';
|
|
101
|
-
empty.setAttribute('data-ggui-empty', 'true');
|
|
102
|
-
target.appendChild(empty);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
for (const item of items) {
|
|
106
|
-
const li = target.ownerDocument.createElement('li');
|
|
107
|
-
li.setAttribute('data-ggui-stack-item', item.id);
|
|
108
|
-
const idEl = target.ownerDocument.createElement('span');
|
|
109
|
-
idEl.className = 'id';
|
|
110
|
-
idEl.textContent = item.id;
|
|
111
|
-
li.appendChild(idEl);
|
|
112
|
-
const labelEl = target.ownerDocument.createElement('span');
|
|
113
|
-
labelEl.className = 'label';
|
|
114
|
-
labelEl.textContent = labelFor(item);
|
|
115
|
-
li.appendChild(labelEl);
|
|
116
|
-
target.appendChild(li);
|
|
117
|
-
}
|
|
118
|
-
}
|