@guuey/mcp-apps-host 0.7.1 → 0.8.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.
@@ -25,8 +25,8 @@
25
25
  * door answers rehydration, and ggui's `resources/read` mints the live-
26
26
  * channel material FRESH at read time — strictly fresher than any inlined
27
27
  * bootstrap. A ggui render is therefore just another locator producer; live
28
- * == rehydrated == spec. The arm's narrowing helpers stay exported one
29
- * minor as `@deprecated` (`ggui-render.ts`); nothing here consumes them.
28
+ * == rehydrated == spec. The arm's narrowing helpers were exported one
29
+ * minor as `@deprecated` and removed at 0.8.0 (`ggui-render.ts` deleted).
30
30
  *
31
31
  * ## Why the CHANNEL is returned alongside the resource
32
32
  *
@@ -25,8 +25,8 @@
25
25
  * door answers rehydration, and ggui's `resources/read` mints the live-
26
26
  * channel material FRESH at read time — strictly fresher than any inlined
27
27
  * bootstrap. A ggui render is therefore just another locator producer; live
28
- * == rehydrated == spec. The arm's narrowing helpers stay exported one
29
- * minor as `@deprecated` (`ggui-render.ts`); nothing here consumes them.
28
+ * == rehydrated == spec. The arm's narrowing helpers were exported one
29
+ * minor as `@deprecated` and removed at 0.8.0 (`ggui-render.ts` deleted).
30
30
  *
31
31
  * ## Why the CHANNEL is returned alongside the resource
32
32
  *
package/dist/index.d.ts CHANGED
@@ -4,11 +4,10 @@
4
4
  * the conformance map (guuey#123).
5
5
  */
6
6
  export { asResourcePayload, asUiResource, blockUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, snapshotUiResource, toolResultLocator, toolResultUiResource, uiLocator, type McpUiResourcePayload, } from "./block-ui.js";
7
- export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, type GguiRenderBootstrap, type GguiRenderDescriptor, type GguiShellHtmlOptions, } from "./ggui-render.js";
8
7
  export { resolveViewMount, snapshotViewMount, toolResultViewMount, type LocatorViewMount, type ResolvedViewMount, type UiResourceReader, type ViewMount, type ViewMountChannel, } from "./card-mount.js";
9
8
  export { createMcpUiResourceReader, uiResourceChannel, type CreateMcpUiResourceReaderDeps, type McpResourceReadResult, } from "./reader.js";
10
9
  export { asToolCallResult, createMcpUiActionRelay, unavailableToolCallResult, UI_ACTION_TOOLS, UI_ACTION_UNAVAILABLE_TEXT, UI_SEMANTIC_ACTION_TOOLS, type CreateMcpUiActionRelayDeps, type McpToolCallContent, type McpToolCallResult, type McpToolStructuredContent, type UiActionRequest, } from "./action.js";
11
- export { initializeResult, initialViewHostState, resourceReadResponse, RESOURCES_READ_METHOD, teardownMessage, toolCallResponse, TOOLS_CALL_METHOD, viewHostElapsed, viewHostReceive, type ViewHostBehavior, type ViewHostEffect, type ViewHostOutbound, type ViewHostPhase, type ViewHostInfo, type ViewHostState, type ViewHostTransition, type ViewRequestId, } from "./view-host-protocol.js";
12
- export { attachViewHost, viewDocumentHtml, type AttachViewHostConfig, type ViewFrameLike, type ViewHostEvents, } from "./view-host.js";
10
+ export { initializeResult, initialViewHostState, resourceReadResponse, RESOURCES_READ_METHOD, teardownMessage, toolCallResponse, TOOLS_CALL_METHOD, viewHostElapsed, viewHostReceive, diagnoseCspViolation, type CspViolationLike, type ViewCspDiagnosis, type ViewCspOrigins, type ViewHostBehavior, type ViewHostEffect, type ViewHostOutbound, type ViewHostPhase, type ViewHostInfo, type ViewHostState, type ViewHostTransition, type ViewRequestId, } from "./view-host-protocol.js";
11
+ export { attachViewHost, viewDocumentHtml, type AttachViewHostConfig, type ViewCspEvents, type ViewFrameLike, type ViewHostEvents, } from "./view-host.js";
13
12
  export { attachSandboxPageDelivery, isSandboxProxyReady, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, type SandboxPageDeliveryConfig, } from "./sandbox-page.js";
14
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AAMvB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -4,15 +4,9 @@
4
4
  * the conformance map (guuey#123).
5
5
  */
6
6
  export { asResourcePayload, asUiResource, blockUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, snapshotUiResource, toolResultLocator, toolResultUiResource, uiLocator, } from "./block-ui.js";
7
- // The retired ggui vendor arm (guuey#209, 2026-08-16). Every symbol below
8
- // is `@deprecated` and stays exported for ONE MINOR under the post-launch
9
- // compat rule — removed in the minor after the one shipping this notice.
10
- // Nothing in this package calls them; `toolResultViewMount` hands a ggui
11
- // render back as a locator, and the reader assigns the "ggui" channel.
12
- export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, } from "./ggui-render.js";
13
7
  export { resolveViewMount, snapshotViewMount, toolResultViewMount, } from "./card-mount.js";
14
8
  export { createMcpUiResourceReader, uiResourceChannel, } from "./reader.js";
15
9
  export { asToolCallResult, createMcpUiActionRelay, unavailableToolCallResult, UI_ACTION_TOOLS, UI_ACTION_UNAVAILABLE_TEXT, UI_SEMANTIC_ACTION_TOOLS, } from "./action.js";
16
- export { initializeResult, initialViewHostState, resourceReadResponse, RESOURCES_READ_METHOD, teardownMessage, toolCallResponse, TOOLS_CALL_METHOD, viewHostElapsed, viewHostReceive, } from "./view-host-protocol.js";
10
+ export { initializeResult, initialViewHostState, resourceReadResponse, RESOURCES_READ_METHOD, teardownMessage, toolCallResponse, TOOLS_CALL_METHOD, viewHostElapsed, viewHostReceive, diagnoseCspViolation, } from "./view-host-protocol.js";
17
11
  export { attachViewHost, viewDocumentHtml, } from "./view-host.js";
18
12
  export { attachSandboxPageDelivery, isSandboxProxyReady, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, } from "./sandbox-page.js";
package/dist/react.d.ts CHANGED
@@ -34,14 +34,14 @@
34
34
  */
35
35
  import { type CSSProperties, type ReactNode } from "react";
36
36
  import { type AttachViewHostConfig } from "./view-host.js";
37
- import type { ViewHostPhase } from "./view-host-protocol.js";
37
+ import type { ViewCspDiagnosis, ViewHostPhase } from "./view-host-protocol.js";
38
38
  import type { ResolvedViewMount } from "./card-mount.js";
39
39
  export { attachViewHost, viewDocumentHtml } from "./view-host.js";
40
- export type { AttachViewHostConfig, ViewFrameLike, ViewHostEvents } from "./view-host.js";
40
+ export type { AttachViewHostConfig, ViewCspEvents, ViewFrameLike, ViewHostEvents } from "./view-host.js";
41
41
  export { attachSandboxPageDelivery, isSandboxProxyReady, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, type SandboxPageDeliveryConfig, } from "./sandbox-page.js";
42
- export type { ViewHostPhase } from "./view-host-protocol.js";
42
+ export type { ViewCspDiagnosis, ViewCspOrigins, ViewHostPhase } from "./view-host-protocol.js";
43
43
  export type { ResolvedViewMount, ViewMount, ViewMountChannel } from "./card-mount.js";
44
- export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabilities" | "hostInfo" | "hostContext" | "onCallTool" | "onReadResource" | "onSizeChanged" | "negotiationTimeoutMs"> {
44
+ export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabilities" | "hostInfo" | "hostContext" | "onCallTool" | "onReadResource" | "onSizeChanged" | "negotiationTimeoutMs" | "cspOrigins" | "onCspDiagnosis"> {
45
45
  /** The resolved card to mount (see `toolResultViewMount`/`resolveViewMount`). */
46
46
  mount: ResolvedViewMount;
47
47
  /**
@@ -97,9 +97,12 @@ export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabili
97
97
  * "render nothing". The default: a quiet "Negotiating with view…" line
98
98
  * while `"negotiating"`; a labeled failure for `"no-handshake"` on the
99
99
  * `"ggui"` channel; nothing once `"connected"` (the view owns its
100
- * pixels) and nothing for a silent `"inline"` card.
100
+ * pixels) and nothing for a silent `"inline"` card. The second argument
101
+ * is the CSP diagnosis when the tripwire caught one (see
102
+ * {@link AttachViewHostConfig.cspOrigins}) — the default label folds it
103
+ * in; a custom renderer decides how to show it.
101
104
  */
102
- renderStatus?: (phase: ViewHostPhase) => ReactNode;
105
+ renderStatus?: (phase: ViewHostPhase, diagnosis?: ViewCspDiagnosis) => ReactNode;
103
106
  }
104
107
  /**
105
108
  * Mount a resolved view and play the MCP Apps Host for it. See the module
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjG,OAAO,EAAoC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKtF,MAAM,WAAW,cACf,SAAQ,IAAI,CACV,oBAAoB,EAClB,kBAAkB,GAClB,UAAU,GACV,aAAa,GACb,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,sBAAsB,CACzB;IACD,iFAAiF;IACjF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,SAAS,CAAC;CACpD;AA+BD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CA0J1D"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjG,OAAO,EAAoC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACzG,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/F,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKtF,MAAM,WAAW,cACf,SAAQ,IAAI,CACV,oBAAoB,EAClB,kBAAkB,GAClB,UAAU,GACV,aAAa,GACb,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,gBAAgB,CACnB;IACD,iFAAiF;IACjF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,gBAAgB,KAAK,SAAS,CAAC;CAClF;AA8CD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAmK1D"}
package/dist/react.js CHANGED
@@ -51,10 +51,17 @@ const statusLineStyle = {
51
51
  opacity: 0.65,
52
52
  pointerEvents: "none",
53
53
  };
54
- function defaultStatus(phase, channel) {
54
+ function defaultStatus(phase, channel, diagnosis) {
55
55
  if (phase === "negotiating") {
56
56
  return _jsx("p", { style: statusLineStyle, children: "Negotiating with view\u2026" });
57
57
  }
58
+ // A CSP diagnosis (guuey#235) is the WHY behind a silent frame — on any
59
+ // channel: a blocked runtime bundle never gets to negotiate, so the
60
+ // tripwire's verdict outranks the channel heuristic below. Actionable
61
+ // over accurate-but-mute: name the blocked URI and the allowance.
62
+ if (phase === "no-handshake" && diagnosis !== undefined) {
63
+ return (_jsx("p", { role: "alert", style: { ...statusLineStyle, opacity: 1, pointerEvents: "auto" }, children: diagnosis.message }));
64
+ }
58
65
  if (phase === "no-handshake" && channel === "ggui") {
59
66
  // A ggui shell negotiates unconditionally before painting, so silence
60
67
  // here is a boot failure with no other author — label it (role=alert
@@ -71,6 +78,8 @@ export function GuueyView(props) {
71
78
  const { mount, sandboxPageUrl, autoResize, dangerouslyAddSandboxFlags, allow, title, className, style, onPhaseChange, renderStatus, ...hostConfig } = props;
72
79
  const frameRef = useRef(null);
73
80
  const [phase, setPhase] = useState("negotiating");
81
+ // The CSP tripwire's verdict for THIS document, if any (guuey#235).
82
+ const [diagnosis, setDiagnosis] = useState(undefined);
74
83
  // The view's own size report, applied only under `autoResize`.
75
84
  const [reportedHeight, setReportedHeight] = useState(undefined);
76
85
  const html = viewDocumentHtml(mount.resource);
@@ -107,6 +116,7 @@ export function GuueyView(props) {
107
116
  // document boots fresh, and the previous negotiation's phase must not
108
117
  // paper over it — nor must the previous document's reported size.
109
118
  setPhase("negotiating");
119
+ setDiagnosis(undefined);
110
120
  setReportedHeight(undefined);
111
121
  const frame = frameRef.current;
112
122
  if (frame === null || html === undefined)
@@ -121,6 +131,10 @@ export function GuueyView(props) {
121
131
  setPhase(next);
122
132
  latest.current.onPhaseChange?.(next);
123
133
  },
134
+ onCspDiagnosis: (found) => {
135
+ setDiagnosis(found);
136
+ latest.current.hostConfig.onCspDiagnosis?.(found);
137
+ },
124
138
  onSizeChanged: (size) => {
125
139
  if (latest.current.autoResize === true && size.height !== undefined) {
126
140
  setReportedHeight(size.height);
@@ -180,5 +194,7 @@ export function GuueyView(props) {
180
194
  width: "100%",
181
195
  height: autoResize === true && reportedHeight !== undefined ? reportedHeight : "100%",
182
196
  border: 0,
183
- } }, `${page?.href ?? "srcdoc"}::${mount.resource.uri}`), renderStatus !== undefined ? renderStatus(phase) : defaultStatus(phase, mount.channel)] }));
197
+ } }, `${page?.href ?? "srcdoc"}::${mount.resource.uri}`), renderStatus !== undefined
198
+ ? renderStatus(phase, diagnosis)
199
+ : defaultStatus(phase, mount.channel, diagnosis)] }));
184
200
  }
@@ -28,10 +28,10 @@
28
28
  * SEP-1865 surface itself, someone else's frozen contract. Zero ggui
29
29
  * imports, deliberately (guuey#123): this host answers ANY spec-following
30
30
  * view. (The ggui vendor arm that used to live beside it in
31
- * `ggui-render.ts` retired 2026-08-16 — guuey#209; that module is now
32
- * deprecated re-exports only.)
31
+ * `ggui-render.ts` retired 2026-08-16 — guuey#209; the module shipped one
32
+ * minor as deprecated re-exports and was deleted at 0.8.0.)
33
33
  */
34
- import { type McpUiHostCapabilities, type McpUiHostContext, type McpUiInitializeResult } from "@modelcontextprotocol/ext-apps";
34
+ import { type McpUiHostCapabilities, type McpUiHostContext, type McpUiInitializeResult, type McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
35
35
  import type { McpToolStructuredContent } from "./action.js";
36
36
  import type { McpResourceReadResult } from "./reader.js";
37
37
  /**
@@ -209,4 +209,52 @@ export declare function viewHostReceive(state: ViewHostState, behavior: ViewHost
209
209
  * one stays lapsed. The caller owns the clock — this machine has none.
210
210
  */
211
211
  export declare function viewHostElapsed(state: ViewHostState): ViewHostState;
212
+ /**
213
+ * The origins a view needs the EMBEDDER's page to allow — the spec's own
214
+ * per-resource CSP declaration (`McpUiResourceCsp`: `connectDomains` →
215
+ * `connect-src`, `resourceDomains` → `script-src`/`style-src`/…,
216
+ * `frameDomains` → `frame-src`). This is the honest filter for the CSP
217
+ * tripwire: a `securitypolicyviolation` whose `blockedURI` lands on one of
218
+ * these hosts is a violation ABOUT the view, not about anything else the
219
+ * page loads. Empty/absent → nothing to match, tripwire inert.
220
+ */
221
+ export type ViewCspOrigins = McpUiResourceCsp;
222
+ /**
223
+ * What the embedder can act on when its own CSP blocked the view: the
224
+ * URI the browser refused, the directive that refused it, and the entry
225
+ * that would allow it (the blocked URI's origin — the smallest allowance
226
+ * that fixes exactly this). Rides beside `"no-handshake"` — the phase
227
+ * stays honest ("it never negotiated"); this is WHY.
228
+ */
229
+ export interface ViewCspDiagnosis {
230
+ blockedUri: string;
231
+ violatedDirective: string;
232
+ /** The origin to add under `violatedDirective` — e.g. `https://assets.mcp.example`. */
233
+ suggestedEntry: string;
234
+ /** Operator-facing sentence, ready to label. */
235
+ message: string;
236
+ }
237
+ /**
238
+ * The slice of a `SecurityPolicyViolationEvent` the tripwire reads —
239
+ * structural, so Node tests hand in plain objects (lib.dom's class is not
240
+ * constructible outside a browser).
241
+ */
242
+ export interface CspViolationLike {
243
+ blockedURI: string;
244
+ /** e.g. `script-src-elem`, `connect-src`. */
245
+ violatedDirective: string;
246
+ /** The directive as the policy spelled it (`script-src` may govern `script-src-elem`). */
247
+ effectiveDirective?: string;
248
+ }
249
+ /**
250
+ * Pure: is this violation ABOUT the view (its blocked URI lands on a
251
+ * declared origin), and if so, what should the embedder add?
252
+ *
253
+ * `blockedURI` is a full URL for network/script blocks; the browser sends
254
+ * bare tokens (`eval`, `inline`, `data`) for policy-class blocks — those
255
+ * carry no host and never match a declared origin, which is right: a
256
+ * `script-src eval` report is not the view's (see guuey#236 for the zod
257
+ * probe that produces exactly one such report at boot).
258
+ */
259
+ export declare function diagnoseCspViolation(violation: CspViolationLike, origins: ViewCspOrigins | undefined): ViewCspDiagnosis | undefined;
212
260
  //# sourceMappingURL=view-host-protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-host-protocol.d.ts","sourceRoot":"","sources":["../src/view-host-protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view-host-protocol.d.ts","sourceRoot":"","sources":["../src/view-host-protocol.ts"],"names":[],"mappings":"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"}
@@ -28,8 +28,8 @@
28
28
  * SEP-1865 surface itself, someone else's frozen contract. Zero ggui
29
29
  * imports, deliberately (guuey#123): this host answers ANY spec-following
30
30
  * view. (The ggui vendor arm that used to live beside it in
31
- * `ggui-render.ts` retired 2026-08-16 — guuey#209; that module is now
32
- * deprecated re-exports only.)
31
+ * `ggui-render.ts` retired 2026-08-16 — guuey#209; the module shipped one
32
+ * minor as deprecated re-exports and was deleted at 0.8.0.)
33
33
  */
34
34
  import { INITIALIZE_METHOD, LATEST_PROTOCOL_VERSION, RESOURCE_TEARDOWN_METHOD, SIZE_CHANGED_METHOD, } from "@modelcontextprotocol/ext-apps";
35
35
  /** JSON-RPC `method not found` — the spec's code, not an invented one. */
@@ -229,3 +229,59 @@ export function viewHostReceive(state, behavior, data) {
229
229
  export function viewHostElapsed(state) {
230
230
  return state.phase === "negotiating" ? { ...state, phase: "no-handshake" } : state;
231
231
  }
232
+ /** Every declared origin's HOST, wildcards (`https://*.x`) reduced to their suffix. */
233
+ function declaredHosts(origins) {
234
+ const out = [];
235
+ for (const list of [origins.connectDomains, origins.resourceDomains, origins.frameDomains]) {
236
+ for (const entry of list ?? []) {
237
+ // `https://*.example.com` → wildcard suffix `.example.com`
238
+ const wildcard = /^[a-z]+:\/\/\*\./i.exec(entry);
239
+ if (wildcard) {
240
+ out.push({ host: entry.slice(wildcard[0].length - 1).toLowerCase(), wildcard: true });
241
+ continue;
242
+ }
243
+ try {
244
+ out.push({ host: new URL(entry).hostname.toLowerCase(), wildcard: false });
245
+ }
246
+ catch {
247
+ // A malformed declaration is producer-side wire data; it simply
248
+ // never matches. The tripwire only ever ADDS a diagnosis, never
249
+ // blocks, so there is nothing to guard.
250
+ }
251
+ }
252
+ }
253
+ return out;
254
+ }
255
+ /**
256
+ * Pure: is this violation ABOUT the view (its blocked URI lands on a
257
+ * declared origin), and if so, what should the embedder add?
258
+ *
259
+ * `blockedURI` is a full URL for network/script blocks; the browser sends
260
+ * bare tokens (`eval`, `inline`, `data`) for policy-class blocks — those
261
+ * carry no host and never match a declared origin, which is right: a
262
+ * `script-src eval` report is not the view's (see guuey#236 for the zod
263
+ * probe that produces exactly one such report at boot).
264
+ */
265
+ export function diagnoseCspViolation(violation, origins) {
266
+ if (origins === undefined)
267
+ return undefined;
268
+ let blocked;
269
+ try {
270
+ blocked = new URL(violation.blockedURI);
271
+ }
272
+ catch {
273
+ return undefined; // bare token (eval/inline/data/…) — not a host, not the view's
274
+ }
275
+ const host = blocked.hostname.toLowerCase();
276
+ const hit = declaredHosts(origins).some((d) => d.wildcard ? host.endsWith(d.host) : host === d.host);
277
+ if (!hit)
278
+ return undefined;
279
+ const directive = violation.effectiveDirective || violation.violatedDirective;
280
+ const suggestedEntry = blocked.origin;
281
+ return {
282
+ blockedUri: violation.blockedURI,
283
+ violatedDirective: directive,
284
+ suggestedEntry,
285
+ message: `This page's Content-Security-Policy blocks ${violation.blockedURI} (${directive}) — the view cannot start. Add \`${directive} ${suggestedEntry}\` to the page's policy.`,
286
+ };
287
+ }
@@ -27,7 +27,7 @@
27
27
  * Seeded from ggui's console `surface-host.ts` (donated, guuey#186 audit);
28
28
  * re-derived here against the pure machine + our own tests.
29
29
  */
30
- import { type ViewHostPhase } from "./view-host-protocol.js";
30
+ import { type CspViolationLike, type ViewCspDiagnosis, type ViewCspOrigins, type ViewHostPhase } from "./view-host-protocol.js";
31
31
  import type { McpResourceReadResult } from "./reader.js";
32
32
  import { type McpToolCallResult, type UiActionRequest } from "./action.js";
33
33
  import type { McpUiResourcePayload } from "./block-ui.js";
@@ -57,6 +57,15 @@ export interface ViewHostEvents {
57
57
  source: unknown;
58
58
  }) => void): void;
59
59
  }
60
+ /**
61
+ * Where CSP violations are observed — the EMBEDDING document (the frame's
62
+ * blocked loads report there, not inside the opaque frame). Structural like
63
+ * {@link ViewHostEvents}, injectable for tests. Default: `document`.
64
+ */
65
+ export interface ViewCspEvents {
66
+ addEventListener(type: "securitypolicyviolation", listener: (event: CspViolationLike) => void): void;
67
+ removeEventListener(type: "securitypolicyviolation", listener: (event: CspViolationLike) => void): void;
68
+ }
60
69
  export interface AttachViewHostConfig {
61
70
  /**
62
71
  * Capabilities to advertise in the initialize result. Default: `{}` —
@@ -116,6 +125,41 @@ export interface AttachViewHostConfig {
116
125
  }) => void;
117
126
  /** Observe phase transitions (see {@link ViewHostPhase}). */
118
127
  onPhaseChange?: (phase: ViewHostPhase) => void;
128
+ /**
129
+ * The origins this view needs the embedding PAGE's CSP to allow — the
130
+ * spec's per-resource declaration (`McpUiResourceCsp`). Given, the host
131
+ * arms a CSP tripwire for the attachment's lifetime: a
132
+ * `securitypolicyviolation` on the EMBEDDING document whose blocked URI
133
+ * lands on one of these hosts is upgraded from a silent "never
134
+ * negotiated" into an actionable {@link ViewCspDiagnosis} (guuey#235).
135
+ * The phase itself stays `"no-handshake"` — honest about WHAT happened;
136
+ * the diagnosis says WHY. Absent → tripwire not installed, zero behavior
137
+ * change (the default for every existing caller).
138
+ *
139
+ * REACH (pinned by the browser leg, `e2e/tests/sdk/view-host.spec.ts`):
140
+ * the listener lives on the embedding document, so it sees violations
141
+ * the PAGE incurs on the view's origins — a runtime bundle the page
142
+ * loads at page level, a live channel the page opens on the view's
143
+ * behalf (the shape ggui's landing tripwire exercised). It does NOT see
144
+ * a `srcdoc` view's own blocked loads: the frame inherits the page's
145
+ * policy, but the browser enforces that copy in — and dispatches the
146
+ * violation on — the FRAME's document, which is opaque-origin and cannot
147
+ * be listened to from outside. A frame-side reporter would need host
148
+ * script injected into untrusted view HTML, a trust-boundary change this
149
+ * primitive deliberately does not make; the `sandboxPageUrl` mount owns
150
+ * its own policy and reports nothing here by construction.
151
+ */
152
+ cspOrigins?: ViewCspOrigins;
153
+ /**
154
+ * A CSP violation ABOUT this view was observed (see {@link cspOrigins}).
155
+ * Fires at most once per attachment, as soon as the violation lands —
156
+ * typically BEFORE the negotiation window lapses, since a blocked runtime
157
+ * never gets to negotiate. `<GuueyView>` folds it into the no-handshake
158
+ * label; a custom renderer shows/logs it as it likes.
159
+ */
160
+ onCspDiagnosis?: (diagnosis: ViewCspDiagnosis) => void;
161
+ /** CSP-violation event source, injectable for tests. Default: `document`. */
162
+ cspEvents?: ViewCspEvents;
119
163
  /**
120
164
  * How long to wait for `ui/initialize` before declaring
121
165
  * `"no-handshake"` (ms). `0` disables the timer. Default 8000 — a view
@@ -1 +1 @@
1
- {"version":3,"file":"view-host.d.ts","sourceRoot":"","sources":["../src/view-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EASL,KAAK,aAAa,EAEnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAEL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAC9F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,aAAa,EAAE;QAAE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CACd,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC5D,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC5D,IAAI,CAAC;CACT;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACtE;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;IAC7E;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpE,6DAA6D;IAC7D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAoDD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,GAAE,oBAAyB,GAAG,MAAM,IAAI,CAkFlG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAcnF"}
1
+ {"version":3,"file":"view-host.d.ts","sourceRoot":"","sources":["../src/view-host.ts"],"names":[],"mappings":"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"}
package/dist/view-host.js CHANGED
@@ -27,7 +27,7 @@
27
27
  * Seeded from ggui's console `surface-host.ts` (donated, guuey#186 audit);
28
28
  * re-derived here against the pure machine + our own tests.
29
29
  */
30
- import { initialViewHostState, resourceReadResponse, teardownMessage, toolCallResponse, viewHostElapsed, viewHostReceive, } from "./view-host-protocol.js";
30
+ import { diagnoseCspViolation, initialViewHostState, resourceReadResponse, teardownMessage, toolCallResponse, viewHostElapsed, viewHostReceive, } from "./view-host-protocol.js";
31
31
  import { unavailableToolCallResult, } from "./action.js";
32
32
  const DEFAULT_HOST_INFO = { name: "guuey-view-host", version: "1" };
33
33
  const DEFAULT_NEGOTIATION_TIMEOUT_MS = 8000;
@@ -151,12 +151,43 @@ export function attachViewHost(frame, config = {}) {
151
151
  return () => window.removeEventListener("message", domListener);
152
152
  };
153
153
  const unsubscribe = subscribe();
154
+ // The CSP tripwire (guuey#235): armed only when the caller declared the
155
+ // view's origins — with none, there is nothing to match and nothing is
156
+ // installed. It only ever ADDS a diagnosis; it never changes what the
157
+ // machine does. Once per attachment: the first violation about the view
158
+ // is the diagnosis (later ones are the same failure repeating).
159
+ const unsubscribeCsp = (() => {
160
+ const { cspOrigins, onCspDiagnosis } = config;
161
+ if (cspOrigins === undefined)
162
+ return () => { };
163
+ let reported = false;
164
+ const onViolation = (event) => {
165
+ if (reported)
166
+ return;
167
+ const diagnosis = diagnoseCspViolation(event, cspOrigins);
168
+ if (diagnosis === undefined)
169
+ return;
170
+ reported = true;
171
+ onCspDiagnosis?.(diagnosis);
172
+ };
173
+ const { cspEvents } = config;
174
+ if (cspEvents !== undefined) {
175
+ cspEvents.addEventListener("securitypolicyviolation", onViolation);
176
+ return () => cspEvents.removeEventListener("securitypolicyviolation", onViolation);
177
+ }
178
+ if (typeof document === "undefined")
179
+ return () => { }; // no embedding document — nothing reports there
180
+ const domListener = (event) => onViolation(event);
181
+ document.addEventListener("securitypolicyviolation", domListener);
182
+ return () => document.removeEventListener("securitypolicyviolation", domListener);
183
+ })();
154
184
  const timeoutMs = config.negotiationTimeoutMs ?? DEFAULT_NEGOTIATION_TIMEOUT_MS;
155
185
  const timer = timeoutMs > 0 ? setTimeout(() => setState(viewHostElapsed(state)), timeoutMs) : undefined;
156
186
  return () => {
157
187
  if (timer !== undefined)
158
188
  clearTimeout(timer);
159
189
  unsubscribe();
190
+ unsubscribeCsp();
160
191
  cachedWindow?.postMessage(teardownMessage(), "*");
161
192
  };
162
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/mcp-apps-host",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "The MCP Apps (SEP-1865) Host role for guuey's chat surfaces — view-mount narrowing across UI channels, ui:// locator rehydration by resources/read, and the sandbox-trust channel contract. Vendor-neutral: any spec-following MCP App mounts through it.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/card-mount.ts CHANGED
@@ -25,8 +25,8 @@
25
25
  * door answers rehydration, and ggui's `resources/read` mints the live-
26
26
  * channel material FRESH at read time — strictly fresher than any inlined
27
27
  * bootstrap. A ggui render is therefore just another locator producer; live
28
- * == rehydrated == spec. The arm's narrowing helpers stay exported one
29
- * minor as `@deprecated` (`ggui-render.ts`); nothing here consumes them.
28
+ * == rehydrated == spec. The arm's narrowing helpers were exported one
29
+ * minor as `@deprecated` and removed at 0.8.0 (`ggui-render.ts` deleted).
30
30
  *
31
31
  * ## Why the CHANNEL is returned alongside the resource
32
32
  *
package/src/index.ts CHANGED
@@ -16,23 +16,6 @@ export {
16
16
  uiLocator,
17
17
  type McpUiResourcePayload,
18
18
  } from "./block-ui.js";
19
- // The retired ggui vendor arm (guuey#209, 2026-08-16). Every symbol below
20
- // is `@deprecated` and stays exported for ONE MINOR under the post-launch
21
- // compat rule — removed in the minor after the one shipping this notice.
22
- // Nothing in this package calls them; `toolResultViewMount` hands a ggui
23
- // render back as a locator, and the reader assigns the "ggui" channel.
24
- export {
25
- asGguiRender,
26
- asGguiRenderBootstrap,
27
- blockGguiRender,
28
- gguiRenderResource,
29
- gguiShellHtml,
30
- toolResultGguiRender,
31
- GGUI_RENDER_META_KEY,
32
- type GguiRenderBootstrap,
33
- type GguiRenderDescriptor,
34
- type GguiShellHtmlOptions,
35
- } from "./ggui-render.js";
36
19
  export {
37
20
  resolveViewMount,
38
21
  snapshotViewMount,
@@ -72,6 +55,10 @@ export {
72
55
  TOOLS_CALL_METHOD,
73
56
  viewHostElapsed,
74
57
  viewHostReceive,
58
+ diagnoseCspViolation,
59
+ type CspViolationLike,
60
+ type ViewCspDiagnosis,
61
+ type ViewCspOrigins,
75
62
  type ViewHostBehavior,
76
63
  type ViewHostEffect,
77
64
  type ViewHostOutbound,
@@ -85,6 +72,7 @@ export {
85
72
  attachViewHost,
86
73
  viewDocumentHtml,
87
74
  type AttachViewHostConfig,
75
+ type ViewCspEvents,
88
76
  type ViewFrameLike,
89
77
  type ViewHostEvents,
90
78
  } from "./view-host.js";
package/src/react.tsx CHANGED
@@ -35,11 +35,11 @@
35
35
  import { useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
36
36
  import { attachViewHost, viewDocumentHtml, type AttachViewHostConfig } from "./view-host.js";
37
37
  import { attachSandboxPageDelivery } from "./sandbox-page.js";
38
- import type { ViewHostPhase } from "./view-host-protocol.js";
38
+ import type { ViewCspDiagnosis, ViewHostPhase } from "./view-host-protocol.js";
39
39
  import type { ResolvedViewMount } from "./card-mount.js";
40
40
 
41
41
  export { attachViewHost, viewDocumentHtml } from "./view-host.js";
42
- export type { AttachViewHostConfig, ViewFrameLike, ViewHostEvents } from "./view-host.js";
42
+ export type { AttachViewHostConfig, ViewCspEvents, ViewFrameLike, ViewHostEvents } from "./view-host.js";
43
43
  export {
44
44
  attachSandboxPageDelivery,
45
45
  isSandboxProxyReady,
@@ -47,7 +47,7 @@ export {
47
47
  SANDBOX_RESOURCE_READY_METHOD,
48
48
  type SandboxPageDeliveryConfig,
49
49
  } from "./sandbox-page.js";
50
- export type { ViewHostPhase } from "./view-host-protocol.js";
50
+ export type { ViewCspDiagnosis, ViewCspOrigins, ViewHostPhase } from "./view-host-protocol.js";
51
51
  export type { ResolvedViewMount, ViewMount, ViewMountChannel } from "./card-mount.js";
52
52
 
53
53
  /** Accessible name for a mounted view when the caller has nothing better. */
@@ -63,6 +63,8 @@ export interface GuueyViewProps
63
63
  | "onReadResource"
64
64
  | "onSizeChanged"
65
65
  | "negotiationTimeoutMs"
66
+ | "cspOrigins"
67
+ | "onCspDiagnosis"
66
68
  > {
67
69
  /** The resolved card to mount (see `toolResultViewMount`/`resolveViewMount`). */
68
70
  mount: ResolvedViewMount;
@@ -119,9 +121,12 @@ export interface GuueyViewProps
119
121
  * "render nothing". The default: a quiet "Negotiating with view…" line
120
122
  * while `"negotiating"`; a labeled failure for `"no-handshake"` on the
121
123
  * `"ggui"` channel; nothing once `"connected"` (the view owns its
122
- * pixels) and nothing for a silent `"inline"` card.
124
+ * pixels) and nothing for a silent `"inline"` card. The second argument
125
+ * is the CSP diagnosis when the tripwire caught one (see
126
+ * {@link AttachViewHostConfig.cspOrigins}) — the default label folds it
127
+ * in; a custom renderer decides how to show it.
123
128
  */
124
- renderStatus?: (phase: ViewHostPhase) => ReactNode;
129
+ renderStatus?: (phase: ViewHostPhase, diagnosis?: ViewCspDiagnosis) => ReactNode;
125
130
  }
126
131
 
127
132
  const statusLineStyle: CSSProperties = {
@@ -136,10 +141,25 @@ const statusLineStyle: CSSProperties = {
136
141
  pointerEvents: "none",
137
142
  };
138
143
 
139
- function defaultStatus(phase: ViewHostPhase, channel: ResolvedViewMount["channel"]): ReactNode {
144
+ function defaultStatus(
145
+ phase: ViewHostPhase,
146
+ channel: ResolvedViewMount["channel"],
147
+ diagnosis: ViewCspDiagnosis | undefined,
148
+ ): ReactNode {
140
149
  if (phase === "negotiating") {
141
150
  return <p style={statusLineStyle}>Negotiating with view…</p>;
142
151
  }
152
+ // A CSP diagnosis (guuey#235) is the WHY behind a silent frame — on any
153
+ // channel: a blocked runtime bundle never gets to negotiate, so the
154
+ // tripwire's verdict outranks the channel heuristic below. Actionable
155
+ // over accurate-but-mute: name the blocked URI and the allowance.
156
+ if (phase === "no-handshake" && diagnosis !== undefined) {
157
+ return (
158
+ <p role="alert" style={{ ...statusLineStyle, opacity: 1, pointerEvents: "auto" }}>
159
+ {diagnosis.message}
160
+ </p>
161
+ );
162
+ }
143
163
  if (phase === "no-handshake" && channel === "ggui") {
144
164
  // A ggui shell negotiates unconditionally before painting, so silence
145
165
  // here is a boot failure with no other author — label it (role=alert
@@ -173,6 +193,8 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
173
193
  } = props;
174
194
  const frameRef = useRef<HTMLIFrameElement>(null);
175
195
  const [phase, setPhase] = useState<ViewHostPhase>("negotiating");
196
+ // The CSP tripwire's verdict for THIS document, if any (guuey#235).
197
+ const [diagnosis, setDiagnosis] = useState<ViewCspDiagnosis | undefined>(undefined);
176
198
  // The view's own size report, applied only under `autoResize`.
177
199
  const [reportedHeight, setReportedHeight] = useState<number | undefined>(undefined);
178
200
  const html = viewDocumentHtml(mount.resource);
@@ -208,6 +230,7 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
208
230
  // document boots fresh, and the previous negotiation's phase must not
209
231
  // paper over it — nor must the previous document's reported size.
210
232
  setPhase("negotiating");
233
+ setDiagnosis(undefined);
211
234
  setReportedHeight(undefined);
212
235
  const frame = frameRef.current;
213
236
  if (frame === null || html === undefined) return;
@@ -220,6 +243,10 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
220
243
  setPhase(next);
221
244
  latest.current.onPhaseChange?.(next);
222
245
  },
246
+ onCspDiagnosis: (found) => {
247
+ setDiagnosis(found);
248
+ latest.current.hostConfig.onCspDiagnosis?.(found);
249
+ },
223
250
  onSizeChanged: (size) => {
224
251
  if (latest.current.autoResize === true && size.height !== undefined) {
225
252
  setReportedHeight(size.height);
@@ -308,7 +335,9 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
308
335
  border: 0,
309
336
  }}
310
337
  />
311
- {renderStatus !== undefined ? renderStatus(phase) : defaultStatus(phase, mount.channel)}
338
+ {renderStatus !== undefined
339
+ ? renderStatus(phase, diagnosis)
340
+ : defaultStatus(phase, mount.channel, diagnosis)}
312
341
  </div>
313
342
  );
314
343
  }