@ggui-ai/iframe-runtime 0.27.0 → 0.28.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/dist/credential-controller.d.ts +25 -4
- package/dist/credential-controller.js +45 -15
- package/dist/events-polling.js +3 -2
- package/dist/iframe-runtime.js +60 -60
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/ladders.d.ts +1 -3
- package/dist/ladders.js +23 -13
- package/dist/runtime.js +2 -2
- package/package.json +8 -8
package/dist/ladders.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ChannelHandler, ChannelLogger, WsTransportHandle } from '@ggui-ai/
|
|
|
2
2
|
import type { AckPayload } from '@ggui-ai/protocol/wire';
|
|
3
3
|
import type { ConnectionStatus } from '@ggui-ai/protocol/transport/websocket';
|
|
4
4
|
import type { McpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
5
|
-
import type
|
|
5
|
+
import { type CredentialController, type ExpirySource, type RefreshOutcome } from './credential-controller.js';
|
|
6
6
|
import { type BuildBridgePollingOptions, type SequenceCursor } from './events-polling.js';
|
|
7
7
|
import { type ConnectFn, type RegistrySubscribeHandle, type PostAckErrorPayload } from './registry-subscribe.js';
|
|
8
8
|
import type { StreamSeqTracker } from './stream-seq.js';
|
|
@@ -58,8 +58,6 @@ export interface LadderSetOptions {
|
|
|
58
58
|
readonly webSocketFactory?: (url: string) => WebSocket;
|
|
59
59
|
readonly eventSourceFactory?: (url: string) => EventSource;
|
|
60
60
|
readonly fetchImpl?: typeof fetch;
|
|
61
|
-
/** Test seam for the bridge-entry expiry check. */
|
|
62
|
-
readonly now?: () => number;
|
|
63
61
|
}
|
|
64
62
|
export interface LadderSet {
|
|
65
63
|
/** Bind the boot ladder. The caller handles its first ack, or its refusal. */
|
package/dist/ladders.js
CHANGED
|
@@ -21,8 +21,11 @@
|
|
|
21
21
|
* is disposed at that switch, so only one bridge loop runs at a time.
|
|
22
22
|
* - **Expiry** is reported to the credential controller from three places:
|
|
23
23
|
* a WS `BOOTSTRAP_EXPIRED` frame (the only auth code that triggers), the
|
|
24
|
-
* first polling `410` whose body is not a replay horizon, and
|
|
25
|
-
* the
|
|
24
|
+
* first polling `410` whose body is not a replay horizon, and a bridge
|
|
25
|
+
* pull while the controller's attempt clock reads the ladder's credential
|
|
26
|
+
* as due — past `expiresAt` by the view's clock, one interval since the
|
|
27
|
+
* last attempt (ggui#1734; a bridge report re-sends on a relay error as the
|
|
28
|
+
* boot does, since the ladder has no other way to a live channel). An
|
|
26
29
|
* adopted credential builds the next ladder.
|
|
27
30
|
*
|
|
28
31
|
* A new ladder omits WS when the one it replaces left WS without ever
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
* caller is told which confirmation fired. Anything else keeps its backoff.
|
|
39
42
|
*/
|
|
40
43
|
import { ChannelRegistry } from '@ggui-ai/live-channel';
|
|
44
|
+
import { REFRESH_RELAY_ERROR_RETRIES, } from './credential-controller.js';
|
|
41
45
|
import { buildBridgePolling, buildEventsPolling, } from './events-polling.js';
|
|
42
46
|
import { classifyChannelFrame, } from './registry-subscribe.js';
|
|
43
47
|
import { domainErrorCodeOf, isErrorToolResult } from './tool-result-error.js';
|
|
@@ -65,7 +69,6 @@ async function isReplayHorizon(response) {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
export function createLadderSet(opts) {
|
|
68
|
-
const now = opts.now ?? Date.now;
|
|
69
72
|
const baseFetch = opts.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
|
|
70
73
|
let nextId = 0;
|
|
71
74
|
const built = new Map();
|
|
@@ -157,7 +160,7 @@ export function createLadderSet(opts) {
|
|
|
157
160
|
const controller = opts.controller;
|
|
158
161
|
if (controller === null || credential === undefined || ladder.disposed || ended)
|
|
159
162
|
return;
|
|
160
|
-
const pending = controller.onExpired(credential, source);
|
|
163
|
+
const pending = controller.onExpired(credential, source, source === 'bridge' ? { retries: REFRESH_RELAY_ERROR_RETRIES } : undefined);
|
|
161
164
|
if (ladder.refresh === undefined)
|
|
162
165
|
ladder.refresh = pending;
|
|
163
166
|
const outcome = await pending;
|
|
@@ -280,7 +283,7 @@ export function createLadderSet(opts) {
|
|
|
280
283
|
const bridge = callTool !== undefined && view !== undefined
|
|
281
284
|
? buildBridgePolling({
|
|
282
285
|
callTool: async (name, args) => {
|
|
283
|
-
|
|
286
|
+
onBridgePull(ladder);
|
|
284
287
|
let result;
|
|
285
288
|
try {
|
|
286
289
|
result = await callTool(name, args);
|
|
@@ -319,15 +322,22 @@ export function createLadderSet(opts) {
|
|
|
319
322
|
...(bridge !== undefined ? { bridge } : {}),
|
|
320
323
|
};
|
|
321
324
|
};
|
|
322
|
-
/**
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
/**
|
|
326
|
+
* Every bridge pull. The first one is the ladder leaving its token rungs
|
|
327
|
+
* (R3 (b), fact 8); each one asks the controller's attempt clock whether the
|
|
328
|
+
* credential is due a refresh (ggui#1734) — expired by the view's clock, one
|
|
329
|
+
* interval since the last attempt — so a view parked here keeps asking, once
|
|
330
|
+
* per interval, until a refresh is granted or the channel ends.
|
|
331
|
+
*/
|
|
332
|
+
const onBridgePull = (ladder) => {
|
|
333
|
+
if (!ladder.bridgeEntered) {
|
|
334
|
+
ladder.bridgeEntered = true;
|
|
335
|
+
switchTo(ladder);
|
|
336
|
+
}
|
|
337
|
+
const credential = ladder.spec.credential;
|
|
338
|
+
if (credential !== undefined && opts.controller?.dueOnBridge(credential) === true) {
|
|
330
339
|
void reportExpired(ladder, 'bridge');
|
|
340
|
+
}
|
|
331
341
|
};
|
|
332
342
|
// The handle arrives through `onBound` as soon as the ladder is bound;
|
|
333
343
|
// the settled result carries the same one, for a `connectFn` that
|
package/dist/runtime.js
CHANGED
|
@@ -34,7 +34,7 @@ import { extractLocatorFromToolResult, parseMetaFromGlobal, parseMetaFromToolRes
|
|
|
34
34
|
import { documentViewRoots, userActivationIsActive, withViewProof } from './view-origin.js';
|
|
35
35
|
import { createStreamSeqTracker } from './stream-seq.js';
|
|
36
36
|
import { createLadderSet } from './ladders.js';
|
|
37
|
-
import {
|
|
37
|
+
import { REFRESH_RELAY_ERROR_RETRIES, createCredentialController, } from './credential-controller.js';
|
|
38
38
|
import { projectHostContext } from '@ggui-ai/protocol/wire';
|
|
39
39
|
import { installDismissIntentListener } from './dismiss-intent.js';
|
|
40
40
|
import { App, PostMessageTransport } from '@modelcontextprotocol/ext-apps';
|
|
@@ -832,7 +832,7 @@ export async function bootSequence(opts) {
|
|
|
832
832
|
// to a live channel, so a relay error there is re-sent (F6); a slice with
|
|
833
833
|
// static content goes on with whatever the refresh answered.
|
|
834
834
|
if (held !== undefined && credentialController !== null) {
|
|
835
|
-
const outcome = await credentialController.onExpired(held, 'boot', hasStaticContent ? undefined : { retries:
|
|
835
|
+
const outcome = await credentialController.onExpired(held, 'boot', hasStaticContent ? undefined : { retries: REFRESH_RELAY_ERROR_RETRIES });
|
|
836
836
|
telemetry?.record('boot.credential_refresh', outcome.kind === 'refused' ? `refused:${outcome.code}` : outcome.kind);
|
|
837
837
|
if (outcome.kind === 'adopted')
|
|
838
838
|
liveMeta = withLiveCredential(meta, outcome.credential);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The iframe-local
|
|
3
|
+
"version": "0.28.0",
|
|
4
|
+
"description": "The iframe-local GGUI runtime: a self-contained ESM bundle that boots inside an MCP Apps host iframe, runs the protocol-version handshake, opens the live-channel WebSocket, and mounts the rendered UI. Bundles React, ReactDOM, and the GGUI design system so generated component code has a single seam to read from.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
7
7
|
"mcp",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"react": "^19.0.0",
|
|
43
43
|
"react-dom": "^19.0.0",
|
|
44
44
|
"zod": "^4.3.6",
|
|
45
|
-
"@ggui-ai/design": "0.
|
|
46
|
-
"@ggui-ai/gadgets": "0.
|
|
47
|
-
"@ggui-ai/live-channel": "0.
|
|
48
|
-
"@ggui-ai/preview-a2ui": "0.
|
|
49
|
-
"@ggui-ai/protocol": "0.
|
|
50
|
-
"@ggui-ai/wire": "0.
|
|
45
|
+
"@ggui-ai/design": "0.28.0",
|
|
46
|
+
"@ggui-ai/gadgets": "0.28.0",
|
|
47
|
+
"@ggui-ai/live-channel": "0.28.0",
|
|
48
|
+
"@ggui-ai/preview-a2ui": "0.28.0",
|
|
49
|
+
"@ggui-ai/protocol": "0.28.0",
|
|
50
|
+
"@ggui-ai/wire": "0.28.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.27.0"
|