sunpeak 0.20.22 → 0.20.28

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 (66) hide show
  1. package/README.md +29 -4
  2. package/bin/commands/inspect.mjs +18 -0
  3. package/dist/chatgpt/index.cjs +1 -1
  4. package/dist/chatgpt/index.js +1 -1
  5. package/dist/claude/index.cjs +1 -1
  6. package/dist/claude/index.js +1 -1
  7. package/dist/embed.css +2 -0
  8. package/dist/host/chatgpt/index.cjs +1 -1
  9. package/dist/host/chatgpt/index.js +1 -1
  10. package/dist/index.cjs +3 -3
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.js +3 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/inspector/app-flatten.d.ts +7 -0
  15. package/dist/inspector/app-types.d.ts +83 -0
  16. package/dist/inspector/iframe-resource.d.ts +19 -1
  17. package/dist/inspector/index.cjs +3 -1
  18. package/dist/inspector/index.cjs.map +1 -1
  19. package/dist/inspector/index.d.ts +2 -0
  20. package/dist/inspector/index.js +3 -2
  21. package/dist/inspector/index.js.map +1 -1
  22. package/dist/inspector/inline-helper-script.d.ts +22 -0
  23. package/dist/inspector/inspector.d.ts +14 -1
  24. package/dist/inspector/simple-sidebar.d.ts +13 -1
  25. package/dist/inspector/use-inspector-state.d.ts +1 -0
  26. package/dist/{inspector-DtEighD9.cjs → inspector-DSX76Z-W.cjs} +340 -28
  27. package/dist/inspector-DSX76Z-W.cjs.map +1 -0
  28. package/dist/{inspector-CJNvLoHo.js → inspector-tIphAHtK.js} +336 -30
  29. package/dist/inspector-tIphAHtK.js.map +1 -0
  30. package/dist/mcp/index.cjs +1 -1
  31. package/dist/mcp/index.cjs.map +1 -1
  32. package/dist/mcp/index.js +1 -1
  33. package/dist/mcp/index.js.map +1 -1
  34. package/dist/sandbox-proxy.html +173 -0
  35. package/dist/style.css +9 -1
  36. package/dist/types/simulation.d.ts +3 -8
  37. package/dist/{use-app-cSBm5Pjl.js → use-app-C2pGHlnF.js} +2 -2
  38. package/dist/{use-app-cSBm5Pjl.js.map → use-app-C2pGHlnF.js.map} +1 -1
  39. package/dist/{use-app-CxtSfkSF.cjs → use-app-DIWh7-3f.cjs} +2 -2
  40. package/dist/{use-app-CxtSfkSF.cjs.map → use-app-DIWh7-3f.cjs.map} +1 -1
  41. package/package.json +14 -11
  42. package/template/dist/albums/albums.html +1 -1
  43. package/template/dist/albums/albums.json +1 -1
  44. package/template/dist/carousel/carousel.html +1 -1
  45. package/template/dist/carousel/carousel.json +1 -1
  46. package/template/dist/map/map.html +1 -1
  47. package/template/dist/map/map.json +1 -1
  48. package/template/dist/review/review.html +1 -1
  49. package/template/dist/review/review.json +1 -1
  50. package/template/node_modules/.bin/playwright +2 -2
  51. package/template/node_modules/.bin/vite +2 -2
  52. package/template/node_modules/.bin/vitest +2 -2
  53. package/template/node_modules/.vite/deps/_metadata.json +4 -4
  54. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +1 -1
  55. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -1
  56. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +1 -1
  57. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -1
  58. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +1 -1
  59. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
  60. package/template/node_modules/.vite-mcp/deps/_metadata.json +24 -24
  61. package/template/node_modules/.vite-mcp/deps/vitest.js +8 -8
  62. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  63. package/template/package.json +4 -4
  64. package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-claude-linux.png +0 -0
  65. package/dist/inspector-CJNvLoHo.js.map +0 -1
  66. package/dist/inspector-DtEighD9.cjs.map +0 -1
@@ -2,7 +2,7 @@ import { L as __require, _ as record, a as array, b as unknown, f as never, i as
2
2
  import { B as ResourceLinkSchema, C as ListPromptsRequestSchema, D as ListResourcesRequestSchema, E as ListResourceTemplatesResultSchema, F as PingRequestSchema, G as ToolSchema, I as PromptListChangedNotificationSchema, L as ReadResourceRequestSchema, N as LoggingMessageNotificationSchema, O as ListResourcesResultSchema, R as ReadResourceResultSchema, T as ListResourceTemplatesRequestSchema, V as ResourceListChangedNotificationSchema, W as ToolListChangedNotificationSchema, a as CallToolResultSchema, c as CreateMessageRequestSchema, i as CallToolRequestSchema, j as ListToolsResultSchema, m as EmbeddedResourceSchema, s as ContentBlockSchema, t as Protocol, v as ImplementationSchema, w as ListPromptsResultSchema, x as JSONRPCMessageSchema, z as RequestIdSchema } from "./protocol-kqfS1G0V.js";
3
3
  import * as React from "react";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
- import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/lib/default-style-variables.ts
7
7
  var DEFAULT_STYLE_VARIABLES = {
8
8
  "--color-background-primary": "light-dark(#ffffff, #1a1a1a)",
@@ -4223,7 +4223,7 @@ registerHostShell({
4223
4223
  }`
4224
4224
  });
4225
4225
  //#endregion
4226
- //#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.1_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__react-_3f5c1a6a4033ae62a19a2bb834f0e050/node_modules/@modelcontextprotocol/ext-apps/dist/src/app-bridge.js
4226
+ //#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.2_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__react-_f5b843da9146ebea748e10ad8dfce46a/node_modules/@modelcontextprotocol/ext-apps/dist/src/app-bridge.js
4227
4227
  ((X) => typeof __require < "u" ? __require : typeof Proxy < "u" ? new Proxy(X, { get: (Y, Z) => (typeof __require < "u" ? __require : Y)[Z] }) : X)(function(X) {
4228
4228
  if (typeof __require < "u") return __require.apply(this, arguments);
4229
4229
  throw Error("Dynamic require of \"" + X + "\" is not supported");
@@ -5545,6 +5545,145 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
5545
5545
  </body>
5546
5546
  </html>`;
5547
5547
  }
5548
+ var SUNPEAK_INLINE_HELPER_SCRIPT = `
5549
+ (function() {
5550
+ if (window.sunpeak) return; // already installed (e.g. parent hot-reload)
5551
+ // Opt-out: resource HTML using the real MCP Apps SDK can suppress the
5552
+ // helper to avoid sending two ui/initialize requests. Manual lowercase
5553
+ // comparison so content="off" / "OFF" / "Off" all match (the Selectors
5554
+ // L4 case-insensitive flag isn't supported in all test environments).
5555
+ try {
5556
+ var metas = document.querySelectorAll('meta[name="sunpeak-helper"]');
5557
+ for (var mi = 0; mi < metas.length; mi++) {
5558
+ var contentAttr = metas[mi].getAttribute('content');
5559
+ if (contentAttr && contentAttr.toLowerCase() === 'off') return;
5560
+ }
5561
+ } catch (e) { /* document not ready or querySelector missing */ }
5562
+
5563
+ var listeners = {
5564
+ toolInput: [],
5565
+ toolInputPartial: [],
5566
+ toolResult: [],
5567
+ toolCancelled: [],
5568
+ hostContext: []
5569
+ };
5570
+ var last = {
5571
+ toolInput: undefined,
5572
+ toolInputPartial: undefined,
5573
+ toolResult: undefined,
5574
+ toolCancelled: undefined,
5575
+ hostContext: undefined
5576
+ };
5577
+ function dispatch(channel, value) {
5578
+ last[channel] = value;
5579
+ // Iterate over a SNAPSHOT — callbacks that unsubscribe themselves
5580
+ // mutate the live list, which would skip subsequent callbacks under a
5581
+ // for-by-index loop on the original array.
5582
+ var list = listeners[channel].slice();
5583
+ for (var i = 0; i < list.length; i++) {
5584
+ try { list[i](value); } catch (e) { console.error('[sunpeak] callback error:', e); }
5585
+ }
5586
+ }
5587
+ // Map channel names to the public method name for error messages.
5588
+ var channelMethodName = {
5589
+ toolInput: 'onToolInput',
5590
+ toolInputPartial: 'onToolInputPartial',
5591
+ toolResult: 'onToolResult',
5592
+ toolCancelled: 'onToolCancelled',
5593
+ hostContext: 'onHostContextChange'
5594
+ };
5595
+ function subscribe(channel) {
5596
+ return function(cb) {
5597
+ if (typeof cb !== 'function') {
5598
+ throw new TypeError('window.sunpeak.' + channelMethodName[channel] + ' expects a function');
5599
+ }
5600
+ listeners[channel].push(cb);
5601
+ if (last[channel] !== undefined) {
5602
+ try { cb(last[channel]); } catch (e) { console.error('[sunpeak] callback error:', e); }
5603
+ }
5604
+ return function unsubscribe() {
5605
+ var idx = listeners[channel].indexOf(cb);
5606
+ if (idx >= 0) listeners[channel].splice(idx, 1);
5607
+ };
5608
+ };
5609
+ }
5610
+
5611
+ window.sunpeak = {
5612
+ onToolInput: subscribe('toolInput'),
5613
+ onToolInputPartial: subscribe('toolInputPartial'),
5614
+ onToolResult: subscribe('toolResult'),
5615
+ onToolCancelled: subscribe('toolCancelled'),
5616
+ onHostContextChange: subscribe('hostContext')
5617
+ };
5618
+
5619
+ var nextId = 1;
5620
+ var pending = {};
5621
+
5622
+ function sendRequest(method, params) {
5623
+ var id = nextId++;
5624
+ return new Promise(function(resolve, reject) {
5625
+ pending[id] = { resolve: resolve, reject: reject };
5626
+ try {
5627
+ window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, '*');
5628
+ } catch (e) {
5629
+ delete pending[id];
5630
+ reject(e);
5631
+ }
5632
+ });
5633
+ }
5634
+ function sendNotification(method, params) {
5635
+ try {
5636
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params || {} }, '*');
5637
+ } catch (e) { /* parent detached */ }
5638
+ }
5639
+
5640
+ window.addEventListener('message', function(ev) {
5641
+ // Only trust messages from the actual parent (the sandbox proxy).
5642
+ // Without this guard, a sibling iframe in the same browsing context or
5643
+ // a browser extension content script could forge JSON-RPC notifications
5644
+ // and drive the embedder's onToolResult/onToolInput callbacks with
5645
+ // attacker-controlled data.
5646
+ if (ev.source !== window.parent) return;
5647
+ var msg = ev.data;
5648
+ if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;
5649
+ if (typeof msg.id !== 'undefined' && pending[msg.id]) {
5650
+ var p = pending[msg.id];
5651
+ delete pending[msg.id];
5652
+ if (msg.error) p.reject(new Error((msg.error && msg.error.message) || 'request error'));
5653
+ else p.resolve(msg.result);
5654
+ return;
5655
+ }
5656
+ if (msg.method === 'ui/notifications/tool-input') {
5657
+ dispatch('toolInput', (msg.params && msg.params.arguments) || {});
5658
+ } else if (msg.method === 'ui/notifications/tool-input-partial') {
5659
+ dispatch('toolInputPartial', (msg.params && msg.params.arguments) || {});
5660
+ } else if (msg.method === 'ui/notifications/tool-result') {
5661
+ // tool-result params are the CallToolResult directly per the spec.
5662
+ dispatch('toolResult', msg.params || {});
5663
+ } else if (msg.method === 'ui/notifications/tool-cancelled') {
5664
+ // Clear cached input / partial / result so a late onToolResult
5665
+ // subscriber doesn't get the cancelled-then-stale value replayed.
5666
+ // hostContext stays — the environment hasn't changed.
5667
+ last.toolInput = undefined;
5668
+ last.toolInputPartial = undefined;
5669
+ last.toolResult = undefined;
5670
+ dispatch('toolCancelled', msg.params || {});
5671
+ } else if (msg.method === 'ui/notifications/host-context-changed') {
5672
+ dispatch('hostContext', msg.params || {});
5673
+ }
5674
+ });
5675
+
5676
+ sendRequest('ui/initialize', {
5677
+ appInfo: { name: 'sunpeak-inline-helper', version: '1.0.0' },
5678
+ appCapabilities: {},
5679
+ protocolVersion: '2026-01-26'
5680
+ }).then(function() {
5681
+ sendNotification('ui/notifications/initialized');
5682
+ }).catch(function(err) {
5683
+ console.warn('[sunpeak] ui/initialize failed:', err && err.message);
5684
+ });
5685
+ })();
5686
+ `.trim();
5548
5687
  //#endregion
5549
5688
  //#region src/inspector/iframe-resource.tsx
5550
5689
  /**
@@ -5683,6 +5822,23 @@ requestAnimationFrame(function(){
5683
5822
  e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");
5684
5823
  });}});`;
5685
5824
  /**
5825
+ * Inject the paint-fence responder and (optionally) a platform runtime script
5826
+ * into a user-provided HTML document. Used for the `html` prop mode where the
5827
+ * embedder hands us a complete document from `mcpClient.readResource(...)` and
5828
+ * we need to splice in the same infrastructure that the `scriptSrc` wrapper
5829
+ * provides. The injection is placed before `</head>` when present, falling
5830
+ * back to the start of `<body>` or the document start.
5831
+ */
5832
+ function injectInfraScripts(html, platformScript) {
5833
+ const fenceTag = `<script data-sunpeak-fence>${PAINT_FENCE_SCRIPT}<\/script>`;
5834
+ const injection = `${platformScript ? `<script>${platformScript}<\/script>` : ""}${fenceTag}${`<script data-sunpeak-helper>${SUNPEAK_INLINE_HELPER_SCRIPT}<\/script>`}`;
5835
+ const headMatch = html.match(/<\/head\s*>/i);
5836
+ if (headMatch) return html.replace(headMatch[0], `${injection}${headMatch[0]}`);
5837
+ const bodyMatch = html.match(/<body([^>]*)>/i);
5838
+ if (bodyMatch) return html.replace(bodyMatch[0], `${bodyMatch[0]}${injection}`);
5839
+ return injection + html;
5840
+ }
5841
+ /**
5686
5842
  * Generates HTML wrapper for a script URL.
5687
5843
  * The MCP Apps SDK in the loaded script handles communication via PostMessageTransport.
5688
5844
  */
@@ -5746,7 +5902,7 @@ function buildIframeAllow(permissions) {
5746
5902
  * connects via PostMessageTransport to window.parent. The parent side uses
5747
5903
  * McpAppHost (wrapping AppBridge) to communicate.
5748
5904
  */
5749
- function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputPartial, toolResult, hostOptions, csp, permissions, prefersBorder, className, style, onDisplayModeReady, debugInjectState, injectOpenAIRuntime, sandboxUrl }) {
5905
+ function IframeResource({ src, scriptSrc, html, hostContext, toolInput, toolInputPartial, toolResult, hostOptions, csp, permissions, prefersBorder, className, style, onDisplayModeReady, debugInjectState, injectOpenAIRuntime, sandboxUrl }) {
5750
5906
  const iframeRef = useRef(null);
5751
5907
  const hostRef = useRef(null);
5752
5908
  const resourceUrl = src ?? scriptSrc;
@@ -5754,6 +5910,8 @@ function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputParti
5754
5910
  srcRef.current = src;
5755
5911
  const scriptSrcRef = useRef(scriptSrc);
5756
5912
  scriptSrcRef.current = scriptSrc;
5913
+ const htmlRef = useRef(html);
5914
+ htmlRef.current = html;
5757
5915
  const cspRef = useRef(csp);
5758
5916
  cspRef.current = csp;
5759
5917
  const hostContextRef = useRef(hostContext);
@@ -5803,9 +5961,20 @@ function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputParti
5803
5961
  onSandboxReady: () => {
5804
5962
  const currentSrc = srcRef.current;
5805
5963
  const currentScriptSrc = scriptSrcRef.current;
5964
+ const currentHtml = htmlRef.current;
5806
5965
  const currentHost = hostRef.current;
5807
5966
  if (!currentHost) return;
5808
- if (currentScriptSrc) {
5967
+ if (currentHtml) {
5968
+ const finalHtml = injectInfraScripts(currentHtml, injectOpenAIRuntimeRef.current ? MOCK_OPENAI_RUNTIME_SCRIPT : void 0);
5969
+ currentHost.sendSandboxResourceReady({
5970
+ html: finalHtml,
5971
+ sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
5972
+ });
5973
+ if (iframeRef.current) {
5974
+ iframeRef.current.style.opacity = "1";
5975
+ iframeRef.current.style.transition = "opacity 100ms";
5976
+ }
5977
+ } else if (currentScriptSrc) {
5809
5978
  const absoluteScriptSrc = currentScriptSrc.startsWith("/") ? `${window.location.origin}${currentScriptSrc}` : currentScriptSrc;
5810
5979
  const cspPolicy = generateCSP(cspRef.current, absoluteScriptSrc);
5811
5980
  const appHtml = generateScriptHtml(absoluteScriptSrc, hostContextRef.current?.theme ?? "dark", cspPolicy, injectOpenAIRuntimeRef.current ? MOCK_OPENAI_RUNTIME_SCRIPT : void 0);
@@ -5876,7 +6045,17 @@ function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputParti
5876
6045
  const borderStyle = prefersBorder ? { border: "1px solid var(--color-border-primary, #e5e7eb)" } : { border: "none" };
5877
6046
  const sandboxSrc = useMemo(() => {
5878
6047
  if (!sandboxUrl) return void 0;
5879
- const url = new URL("/proxy", sandboxUrl);
6048
+ let url;
6049
+ try {
6050
+ const parsed = new URL(sandboxUrl);
6051
+ url = parsed.pathname && parsed.pathname !== "/" ? parsed : new URL("/proxy", sandboxUrl);
6052
+ } catch {
6053
+ return;
6054
+ }
6055
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
6056
+ console.warn("[IframeResource] Ignoring non-http(s) sandboxUrl:", sandboxUrl);
6057
+ return;
6058
+ }
5880
6059
  if (injectOpenAIRuntime) url.searchParams.set("platform", "chatgpt");
5881
6060
  return url.toString();
5882
6061
  }, [sandboxUrl, injectOpenAIRuntime]);
@@ -6316,6 +6495,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
6316
6495
  }, [toolResult, modelContext]);
6317
6496
  const resourceUrl = selectedSim?.resourceUrl;
6318
6497
  const resourceScript = selectedSim?.resourceScript;
6498
+ const resourceHtml = selectedSim?.resourceHtml;
6319
6499
  const csp = selectedSim?.resource ? extractResourceCSP(selectedSim.resource) : void 0;
6320
6500
  const resourceMeta = (selectedSim?.resource?._meta)?.ui;
6321
6501
  return {
@@ -6382,6 +6562,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
6382
6562
  handleUpdateModelContext,
6383
6563
  resourceUrl,
6384
6564
  resourceScript,
6565
+ resourceHtml,
6385
6566
  csp,
6386
6567
  permissions: resourceMeta?.permissions,
6387
6568
  prefersBorder: resourceMeta?.prefersBorder ?? false,
@@ -6484,7 +6665,14 @@ function useMcpConnection(initialServerUrl) {
6484
6665
  //#endregion
6485
6666
  //#region src/inspector/theme-provider.tsx
6486
6667
  var ThemeProviderContext = React.createContext(void 0);
6487
- /** Default theme applier: sets data-theme attribute on document.documentElement */
6668
+ /**
6669
+ * Default theme applier: sets `data-theme` on `document.documentElement`.
6670
+ * Kept for callers using `ThemeProvider` outside the bundled `<Inspector />`
6671
+ * (e.g. custom-inspector builds composed from this package's primitives).
6672
+ * The bundled Inspector overrides this with a no-op applier and applies
6673
+ * theme to its own root element, so embedding it inside a host React app
6674
+ * leaves the host's document attributes untouched.
6675
+ */
6488
6676
  function defaultApplyTheme(theme) {
6489
6677
  if (typeof document !== "undefined") document.documentElement.setAttribute("data-theme", theme);
6490
6678
  }
@@ -6527,7 +6715,7 @@ function ChevronRightIcon() {
6527
6715
  })
6528
6716
  });
6529
6717
  }
6530
- function SimpleSidebar({ children, controls, headerRight }) {
6718
+ function SimpleSidebar({ children, controls, headerRight, rootRef, fillParent = false }) {
6531
6719
  const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
6532
6720
  const [sidebarWidth, setSidebarWidth] = React.useState(DEFAULT_SIDEBAR_WIDTH);
6533
6721
  const [isResizing, setIsResizing] = React.useState(false);
@@ -6552,7 +6740,8 @@ function SimpleSidebar({ children, controls, headerRight }) {
6552
6740
  };
6553
6741
  }, [isResizing]);
6554
6742
  return /* @__PURE__ */ jsxs("div", {
6555
- className: "sunpeak-inspector-root flex h-screen w-full overflow-hidden relative",
6743
+ ref: rootRef,
6744
+ className: `sunpeak-inspector-root flex ${fillParent ? "h-full w-full" : "h-screen w-full"} overflow-hidden relative`,
6556
6745
  children: [
6557
6746
  isResizing && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 cursor-col-resize" }),
6558
6747
  isDrawerOpen && /* @__PURE__ */ jsx("div", {
@@ -6882,13 +7071,85 @@ function resolveServerToolResult(mock, args) {
6882
7071
  for (const entry of mock) if (Object.entries(entry.when).every(([key, value]) => args != null && args[key] === value)) return entry.result;
6883
7072
  }
6884
7073
  //#endregion
7074
+ //#region src/inspector/app-flatten.ts
7075
+ /** Pull the output-template URI off a tool's _meta, if present. */
7076
+ function getOutputTemplate(toolMeta) {
7077
+ if (!toolMeta || typeof toolMeta !== "object") return void 0;
7078
+ const openai = toolMeta.openai;
7079
+ if (!openai || typeof openai !== "object") return void 0;
7080
+ const template = openai.outputTemplate;
7081
+ return typeof template === "string" ? template : void 0;
7082
+ }
7083
+ /** Build an MCP-shaped `Resource` from the embedder's input. Used purely for
7084
+ * sidebar metadata (CSP, permissions, prefersBorder); the actual HTML render
7085
+ * goes through `resourceHtml` on the resulting Simulation. */
7086
+ function toMcpResource(r) {
7087
+ return {
7088
+ uri: r.uri,
7089
+ mimeType: r.mimeType ?? "text/html",
7090
+ name: r.uri,
7091
+ ...r._meta ? { _meta: r._meta } : {}
7092
+ };
7093
+ }
7094
+ /**
7095
+ * Flatten an `InspectorApp` to the `Record<string, Simulation>` shape the
7096
+ * Inspector consumes internally. Returns an empty map if `app` is missing.
7097
+ */
7098
+ function flattenAppToSimulations(app) {
7099
+ if (!app) return {};
7100
+ const result = {};
7101
+ const resourcesByUri = /* @__PURE__ */ new Map();
7102
+ for (const r of app.resources) {
7103
+ if (resourcesByUri.has(r.uri)) console.warn(`[Inspector] Duplicate resource URI '${r.uri}' in app.resources — the second entry replaces the first.`);
7104
+ resourcesByUri.set(r.uri, r);
7105
+ }
7106
+ for (const appTool of app.tools) {
7107
+ const uri = getOutputTemplate(appTool.tool._meta);
7108
+ if (!uri) continue;
7109
+ const resource = resourcesByUri.get(uri);
7110
+ if (!resource) continue;
7111
+ const mcpResource = toMcpResource(resource);
7112
+ const sims = appTool.simulations && appTool.simulations.length > 0 ? appTool.simulations : [{ name: appTool.tool.name }];
7113
+ for (const sim of sims) {
7114
+ const key = `${appTool.tool.name}__${sim.name}`;
7115
+ if (key in result) console.warn(`[Inspector] Duplicate simulation name '${sim.name}' under tool '${appTool.tool.name}' — the second entry replaces the first.`);
7116
+ result[key] = {
7117
+ name: key,
7118
+ displayName: sim.name,
7119
+ resourceHtml: resource.html,
7120
+ userMessage: sim.userMessage,
7121
+ tool: appTool.tool,
7122
+ resource: mcpResource,
7123
+ toolInput: sim.toolInput,
7124
+ toolResult: sim.toolResult,
7125
+ serverTools: sim.serverTools
7126
+ };
7127
+ }
7128
+ }
7129
+ return result;
7130
+ }
7131
+ //#endregion
6885
7132
  //#region src/inspector/inspector.tsx
6886
7133
  var DOCS_BASE_URL = "https://sunpeak.ai/docs";
6887
7134
  /** Check whether a simulation has user-authored fixture data. */
6888
7135
  function hasFixtureData(sim) {
6889
7136
  return sim.toolResult != null || sim.toolInput != null || sim.serverTools != null;
6890
7137
  }
6891
- function Inspector({ children, simulations: initialSimulations = {}, appName = "Sunpeak", appIcon, defaultHost = "chatgpt", onCallTool, onCallToolDirect, defaultProdResources = false, hideInspectorModes = false, demoMode = false, sandboxUrl, mcpServerUrl }) {
7138
+ var EMPTY_SIMULATIONS = Object.freeze({});
7139
+ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_SIMULATIONS, appName: appNameProp, appIcon: appIconProp, defaultHost = "chatgpt", onCallTool, onCallToolDirect, defaultProdResources = false, hideInspectorModes = false, demoMode = false, sandboxUrl, mcpServerUrl }) {
7140
+ const initialSimulations = React.useMemo(() => app ? flattenAppToSimulations(app) : initialSimulationsProp, [app, initialSimulationsProp]);
7141
+ const appName = app?.name ?? appNameProp ?? "Sunpeak";
7142
+ const appIcon = app?.icon ?? appIconProp;
7143
+ const isEmbedded = !!app;
7144
+ const conflictWarnedRef = React.useRef(false);
7145
+ React.useEffect(() => {
7146
+ if (conflictWarnedRef.current) return;
7147
+ if (app && initialSimulationsProp && Object.keys(initialSimulationsProp).length > 0) {
7148
+ conflictWarnedRef.current = true;
7149
+ console.warn("[Inspector] Both `app` and `simulations` were provided. `app` takes precedence; `simulations` is ignored.");
7150
+ }
7151
+ }, [app, initialSimulationsProp]);
7152
+ const rootRef = React.useRef(null);
6892
7153
  const [simulations, setSimulations] = React.useState(initialSimulations);
6893
7154
  React.useEffect(() => {
6894
7155
  setSimulations(initialSimulations);
@@ -6971,7 +7232,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
6971
7232
  const [oauthClientSecret, setOauthClientSecret] = React.useState("");
6972
7233
  const [oauthStatus, setOauthStatus] = React.useState("none");
6973
7234
  const [oauthError, setOauthError] = React.useState();
6974
- const connection = useMcpConnection(mcpServerUrl || void 0);
7235
+ const connection = useMcpConnection(isEmbedded ? void 0 : mcpServerUrl || void 0);
6975
7236
  const [prodResources, setProdResources] = React.useState(state.urlProdResources ?? defaultProdResources);
6976
7237
  const showSidebar = state.urlSidebar !== false;
6977
7238
  const showDevOverlay = state.urlDevOverlay !== false;
@@ -7045,12 +7306,22 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7045
7306
  return;
7046
7307
  }
7047
7308
  if (data.status === "redirect" && data.authUrl) {
7309
+ let parsedAuthUrl = null;
7310
+ try {
7311
+ parsedAuthUrl = new URL(data.authUrl);
7312
+ } catch {}
7313
+ if (!parsedAuthUrl || parsedAuthUrl.protocol !== "http:" && parsedAuthUrl.protocol !== "https:") {
7314
+ popup?.close();
7315
+ setOauthError("OAuth authorization URL is not a valid http(s) URL.");
7316
+ setOauthStatus("error");
7317
+ return;
7318
+ }
7048
7319
  if (!popup || popup.closed) {
7049
7320
  setOauthError("Popup was blocked. Allow popups for this site and try again.");
7050
7321
  setOauthStatus("error");
7051
7322
  return;
7052
7323
  }
7053
- popup.location.href = data.authUrl;
7324
+ popup.location.href = parsedAuthUrl.toString();
7054
7325
  let checkClosed;
7055
7326
  let bc;
7056
7327
  const cleanup = () => {
@@ -7239,15 +7510,23 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7239
7510
  displayMode,
7240
7511
  setDisplayMode
7241
7512
  ]);
7242
- React.useLayoutEffect(() => {
7243
- const vars = activeShell?.styleVariables;
7244
- if (!vars) return;
7245
- const root = document.documentElement;
7246
- for (const [key, value] of Object.entries(vars)) if (value) root.style.setProperty(key, value);
7247
- }, [activeShell]);
7513
+ const prevStyleVarKeysRef = React.useRef([]);
7248
7514
  const prevPageStyleKeysRef = React.useRef([]);
7249
7515
  React.useLayoutEffect(() => {
7250
- const root = document.documentElement;
7516
+ const root = rootRef.current;
7517
+ if (!root) return;
7518
+ root.setAttribute("data-theme", state.theme);
7519
+ root.style.colorScheme = state.theme;
7520
+ for (const key of prevStyleVarKeysRef.current) root.style.removeProperty(key);
7521
+ const vars = activeShell?.styleVariables;
7522
+ if (vars) {
7523
+ const keys = [];
7524
+ for (const [key, value] of Object.entries(vars)) if (value) {
7525
+ root.style.setProperty(key, value);
7526
+ keys.push(key);
7527
+ }
7528
+ prevStyleVarKeysRef.current = keys;
7529
+ } else prevStyleVarKeysRef.current = [];
7251
7530
  for (const key of prevPageStyleKeysRef.current) root.style.removeProperty(key);
7252
7531
  const pageStyles = activeShell?.pageStyles;
7253
7532
  if (pageStyles) {
@@ -7258,7 +7537,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7258
7537
  }
7259
7538
  prevPageStyleKeysRef.current = keys;
7260
7539
  } else prevPageStyleKeysRef.current = [];
7261
- }, [activeShell]);
7540
+ }, [activeShell, state.theme]);
7262
7541
  React.useLayoutEffect(() => {
7263
7542
  const fontCss = activeShell?.fontCss;
7264
7543
  const id = "sunpeak-host-fonts";
@@ -7349,7 +7628,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7349
7628
  children: /* @__PURE__ */ jsx("span", {
7350
7629
  className: "text-sm text-center max-w-xs",
7351
7630
  style: { color: "var(--color-text-secondary)" },
7352
- children: isError ? "Could not connect to MCP server" : isConnected ? "No tools with UI resources found on this server" : serverUrl ? "Connecting…" : "Enter an MCP server URL to get started"
7631
+ children: isEmbedded ? "No tools with UI resources in this app" : isError ? "Could not connect to MCP server" : isConnected ? "No tools with UI resources found on this server" : serverUrl ? "Connecting…" : "Enter an MCP server URL to get started"
7353
7632
  })
7354
7633
  });
7355
7634
  } else if (showEmptyState) content = /* @__PURE__ */ jsx("div", {
@@ -7374,6 +7653,30 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7374
7653
  children: "Building…"
7375
7654
  })
7376
7655
  });
7656
+ else if (state.resourceHtml) content = /* @__PURE__ */ jsx("div", {
7657
+ className: "h-full w-full",
7658
+ style: { background: iframeBg },
7659
+ children: /* @__PURE__ */ jsx(IframeResource, {
7660
+ html: state.resourceHtml,
7661
+ hostContext,
7662
+ toolInput: state.toolInput,
7663
+ toolResult: state.effectiveToolResult,
7664
+ hostOptions: {
7665
+ hostInfo: activeShell?.hostInfo,
7666
+ hostCapabilities: activeShell?.hostCapabilities,
7667
+ onDisplayModeChange: state.handleDisplayModeChange,
7668
+ onUpdateModelContext: state.handleUpdateModelContext,
7669
+ onCallTool: handleCallTool
7670
+ },
7671
+ permissions: state.permissions,
7672
+ prefersBorder: state.prefersBorder,
7673
+ onDisplayModeReady: state.handleDisplayModeReady,
7674
+ debugInjectState: state.modelContext,
7675
+ injectOpenAIRuntime: state.activeHost === "chatgpt",
7676
+ sandboxUrl,
7677
+ className: "h-full w-full"
7678
+ }, `${state.activeHost}-${state.selectedSimulationName}-html`)
7679
+ });
7377
7680
  else if (effectiveResourceUrl) content = /* @__PURE__ */ jsx("div", {
7378
7681
  className: "h-full w-full",
7379
7682
  style: { background: iframeBg },
@@ -7424,7 +7727,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7424
7727
  }, `${state.activeHost}-${state.selectedSimulationName}-${state.resourceScript}`)
7425
7728
  });
7426
7729
  else content = children;
7427
- const applyTheme = activeShell?.applyTheme;
7730
+ const applyTheme = React.useCallback((_theme) => {}, []);
7428
7731
  const runButton = !demoMode && onCallTool && currentSim && activeSimulationName === null ? /* @__PURE__ */ jsxs("button", {
7429
7732
  type: "button",
7430
7733
  onClick: handleRun,
@@ -7464,11 +7767,13 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7464
7767
  headerAction: runButton,
7465
7768
  children: content
7466
7769
  }) : content;
7770
+ const rootSizing = isEmbedded ? "h-full w-full" : "h-screen w-screen";
7467
7771
  if (!showSidebar) return /* @__PURE__ */ jsx(ThemeProvider, {
7468
7772
  theme: state.theme,
7469
7773
  applyTheme,
7470
7774
  children: /* @__PURE__ */ jsx("div", {
7471
- className: "flex h-screen w-screen",
7775
+ ref: rootRef,
7776
+ className: `sunpeak-inspector-root flex ${rootSizing}`,
7472
7777
  children: conversationContent
7473
7778
  })
7474
7779
  });
@@ -7476,10 +7781,12 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7476
7781
  theme: state.theme,
7477
7782
  applyTheme,
7478
7783
  children: [/* @__PURE__ */ jsx(SimpleSidebar, {
7784
+ rootRef,
7785
+ fillParent: isEmbedded,
7479
7786
  controls: /* @__PURE__ */ jsxs("div", {
7480
7787
  className: "space-y-1",
7481
7788
  children: [
7482
- /* @__PURE__ */ jsx(SidebarControl, {
7789
+ !isEmbedded && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarControl, {
7483
7790
  label: /* @__PURE__ */ jsxs("span", {
7484
7791
  className: "flex items-center gap-1.5",
7485
7792
  children: ["MCP Server", serverUrl && !demoMode && /* @__PURE__ */ jsx("span", {
@@ -7498,8 +7805,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7498
7805
  placeholder: "http://localhost:8000/mcp",
7499
7806
  disabled: demoMode
7500
7807
  })
7501
- }),
7502
- !demoMode && /* @__PURE__ */ jsx(SidebarCollapsibleControl, {
7808
+ }), !demoMode && /* @__PURE__ */ jsx(SidebarCollapsibleControl, {
7503
7809
  label: "Authentication",
7504
7810
  defaultCollapsed: authType === "none",
7505
7811
  children: /* @__PURE__ */ jsxs("div", {
@@ -7584,8 +7890,8 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7584
7890
  })
7585
7891
  ]
7586
7892
  })
7587
- }, `auth-${authType === "none" ? "none" : "active"}`),
7588
- !hideInspectorModes && !demoMode && /* @__PURE__ */ jsx(SidebarCheckbox, {
7893
+ }, `auth-${authType === "none" ? "none" : "active"}`)] }),
7894
+ !hideInspectorModes && !demoMode && !isEmbedded && /* @__PURE__ */ jsx(SidebarCheckbox, {
7589
7895
  checked: prodResources,
7590
7896
  onChange: setProdResources,
7591
7897
  label: "Prod Resources",
@@ -7636,7 +7942,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7636
7942
  label: selectedToolInfo && selectedToolInfo.fixtureSimNames.length > 0 ? "None (call server)" : "None"
7637
7943
  }], ...(selectedToolInfo?.fixtureSimNames ?? []).map((simName) => ({
7638
7944
  value: simName,
7639
- label: simName
7945
+ label: simulations[simName]?.displayName ?? simName
7640
7946
  }))]
7641
7947
  })
7642
7948
  })]
@@ -8016,6 +8322,6 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
8016
8322
  });
8017
8323
  }
8018
8324
  //#endregion
8019
- export { DEFAULT_STYLE_VARIABLES as C, cn as S, McpAppHost as _, SidebarControl as a, getRegisteredHosts as b, SidebarTextarea as c, ThemeProvider as d, useThemeContext as f, extractResourceCSP as g, IframeResource as h, SidebarCollapsibleControl as i, SidebarToggle as l, useInspectorState as m, resolveServerToolResult as n, SidebarInput as o, useMcpConnection as p, SidebarCheckbox as r, SidebarSelect as s, Inspector as t, SimpleSidebar as u, SCREEN_WIDTHS as v, registerHostShell as x, getHostShell as y };
8325
+ export { cn as C, registerHostShell as S, extractResourceCSP as _, SidebarCollapsibleControl as a, getHostShell as b, SidebarSelect as c, SimpleSidebar as d, ThemeProvider as f, IframeResource as g, useInspectorState as h, SidebarCheckbox as i, SidebarTextarea as l, useMcpConnection as m, flattenAppToSimulations as n, SidebarControl as o, useThemeContext as p, resolveServerToolResult as r, SidebarInput as s, Inspector as t, SidebarToggle as u, McpAppHost as v, DEFAULT_STYLE_VARIABLES as w, getRegisteredHosts as x, SCREEN_WIDTHS as y };
8020
8326
 
8021
- //# sourceMappingURL=inspector-CJNvLoHo.js.map
8327
+ //# sourceMappingURL=inspector-tIphAHtK.js.map