@superblocksteam/gateway 2.0.167-next.0 → 2.0.168-next.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/README.md +163 -28
- package/dist/agents/resolve-orchestrator-url.js +1 -2
- package/dist/agents/resolve-orchestrator-url.js.map +1 -1
- package/dist/capabilities/lifecycle.d.ts +3 -0
- package/dist/capabilities/lifecycle.js +330 -128
- package/dist/capabilities/lifecycle.js.map +1 -1
- package/dist/capabilities/live-state-attaches.d.ts +26 -0
- package/dist/capabilities/live-state-attaches.js +41 -0
- package/dist/capabilities/live-state-attaches.js.map +1 -0
- package/dist/capabilities/tool-permission-reply.d.ts +28 -0
- package/dist/capabilities/tool-permission-reply.js +55 -0
- package/dist/capabilities/tool-permission-reply.js.map +1 -0
- package/dist/capabilities/types.d.ts +19 -1
- package/dist/capabilities/types.js.map +1 -1
- package/dist/deadline.d.ts +6 -0
- package/dist/deadline.js +23 -0
- package/dist/deadline.js.map +1 -0
- package/dist/deps.d.ts +7 -0
- package/dist/health/readiness.js +1 -17
- package/dist/health/readiness.js.map +1 -1
- package/dist/integrations/filters.js +2 -2
- package/dist/integrations/filters.js.map +1 -1
- package/dist/orchestrator/client.d.ts +2 -1
- package/dist/orchestrator/client.js +4 -2
- package/dist/orchestrator/client.js.map +1 -1
- package/dist/sabs/app-state.d.ts +1 -0
- package/dist/sabs/app-state.js +9 -0
- package/dist/sabs/app-state.js.map +1 -1
- package/dist/sabs/awaited-decision.d.ts +24 -1
- package/dist/sabs/awaited-decision.js +40 -9
- package/dist/sabs/awaited-decision.js.map +1 -1
- package/dist/sabs/editor-client-methods.d.ts +21 -10
- package/dist/sabs/editor-client-methods.js +48 -24
- package/dist/sabs/editor-client-methods.js.map +1 -1
- package/dist/sabs/session-peer.d.ts +47 -14
- package/dist/sabs/session-peer.js.map +1 -1
- package/dist/sabs/turn-collector.d.ts +27 -1
- package/dist/sabs/turn-collector.js +38 -2
- package/dist/sabs/turn-collector.js.map +1 -1
- package/dist/sabs/websocket-session-peer.d.ts +27 -3
- package/dist/sabs/websocket-session-peer.js +466 -66
- package/dist/sabs/websocket-session-peer.js.map +1 -1
- package/dist/server/client.d.ts +3 -0
- package/dist/server/client.js +7 -3
- package/dist/server/client.js.map +1 -1
- package/dist/start.d.ts +13 -2
- package/dist/start.js +112 -12
- package/dist/start.js.map +1 -1
- package/dist/telemetry/metrics.js +8 -21
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/runtime.d.ts +5 -0
- package/dist/telemetry/runtime.js +22 -8
- package/dist/telemetry/runtime.js.map +1 -1
- package/dist/transports/mcp/admin-tools.d.ts +2 -1
- package/dist/transports/mcp/admin-tools.js +19 -2
- package/dist/transports/mcp/admin-tools.js.map +1 -1
- package/dist/transports/mcp/client-presentation.d.ts +9 -1
- package/dist/transports/mcp/client-presentation.js +10 -0
- package/dist/transports/mcp/client-presentation.js.map +1 -1
- package/dist/transports/mcp/decision-card-html.d.ts +4 -2
- package/dist/transports/mcp/decision-card-html.js +107 -25
- package/dist/transports/mcp/decision-card-html.js.map +1 -1
- package/dist/transports/mcp/decision-elicitation.d.ts +3 -2
- package/dist/transports/mcp/decision-elicitation.js +67 -2
- package/dist/transports/mcp/decision-elicitation.js.map +1 -1
- package/dist/transports/mcp/format-tool-content.d.ts +7 -0
- package/dist/transports/mcp/format-tool-content.js +3 -3
- package/dist/transports/mcp/format-tool-content.js.map +1 -1
- package/dist/transports/mcp/instructions/result.js +1 -0
- package/dist/transports/mcp/instructions/result.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/ask-user.d.ts +2 -1
- package/dist/transports/mcp/instructions/tools/ask-user.js +9 -2
- package/dist/transports/mcp/instructions/tools/ask-user.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/copy.d.ts +5 -2
- package/dist/transports/mcp/instructions/tools/copy.js +20 -5
- package/dist/transports/mcp/instructions/tools/copy.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/edit-app.d.ts +1 -1
- package/dist/transports/mcp/instructions/tools/edit-app.js +11 -1
- package/dist/transports/mcp/instructions/tools/edit-app.js.map +1 -1
- package/dist/transports/mcp/mount.d.ts +2 -3
- package/dist/transports/mcp/mount.js +395 -112
- package/dist/transports/mcp/mount.js.map +1 -1
- package/dist/transports/mcp/plan-approval.d.ts +23 -13
- package/dist/transports/mcp/plan-approval.js +39 -26
- package/dist/transports/mcp/plan-approval.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-tools.js","sourceRoot":"","sources":["../../../src/transports/mcp/admin-tools.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,GAEhB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-tools.js","sourceRoot":"","sources":["../../../src/transports/mcp/admin-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,10 +7,18 @@ export type ClientPresentationCapabilities = {
|
|
|
7
7
|
hasNativeAskForm: boolean;
|
|
8
8
|
hasNativeBrowser: boolean;
|
|
9
9
|
};
|
|
10
|
-
/** Presentation surfaces plus the
|
|
10
|
+
/** Presentation surfaces plus the capabilities MCP does advertise. */
|
|
11
11
|
export type ClientProfile = ClientPresentationCapabilities & {
|
|
12
|
+
/** Renders the MCP Apps decision card, whose confirm step attests an approval. */
|
|
13
|
+
supportsDecisionCard: boolean;
|
|
12
14
|
supportsFormElicitation: boolean;
|
|
13
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Whether the user can answer a decision through a surface the Gateway trusts
|
|
18
|
+
* as theirs: Cowork's native form, the decision card, or form elicitation. An
|
|
19
|
+
* approval the agent sends on its own is never accepted.
|
|
20
|
+
*/
|
|
21
|
+
export declare function collectsUserApproval(profile: ClientProfile): boolean;
|
|
14
22
|
export declare function clientPresentationCapabilities(client: {
|
|
15
23
|
name: string;
|
|
16
24
|
} | undefined): ClientPresentationCapabilities;
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
* What surfaces a host has. MCP advertises no capability for a host's own
|
|
3
3
|
* browser or ask/choice UI, so `clientInfo.name` is the only signal.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Whether the user can answer a decision through a surface the Gateway trusts
|
|
7
|
+
* as theirs: Cowork's native form, the decision card, or form elicitation. An
|
|
8
|
+
* approval the agent sends on its own is never accepted.
|
|
9
|
+
*/
|
|
10
|
+
export function collectsUserApproval(profile) {
|
|
11
|
+
return (profile.hasNativeAskForm ||
|
|
12
|
+
profile.supportsDecisionCard ||
|
|
13
|
+
profile.supportsFormElicitation);
|
|
14
|
+
}
|
|
5
15
|
export function clientPresentationCapabilities(client) {
|
|
6
16
|
const isCowork = client?.name === "local-agent-mode-superblocks" ||
|
|
7
17
|
client?.name.startsWith("local-agent-mode-plugin:") === true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-presentation.js","sourceRoot":"","sources":["../../../src/transports/mcp/client-presentation.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"client-presentation.js","sourceRoot":"","sources":["../../../src/transports/mcp/client-presentation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAeH;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAsB;IACzD,OAAO,CACL,OAAO,CAAC,gBAAgB;QACxB,OAAO,CAAC,oBAAoB;QAC5B,OAAO,CAAC,uBAAuB,CAChC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,MAAoC;IAEpC,MAAM,QAAQ,GACZ,MAAM,EAAE,IAAI,KAAK,8BAA8B;QAC/C,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,0BAA0B,CAAC,KAAK,IAAI,CAAC;IAC/D,OAAO;QACL,gBAAgB,EAAE,QAAQ;QAC1B,gBAAgB,EAAE,QAAQ;KAC3B,CAAC;AACJ,CAAC"}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
* resource URI when the HTML changes so Claude Desktop refreshes its cache.
|
|
22
22
|
*/
|
|
23
23
|
export declare const PLAN_APPROVAL_TOKEN_META_KEY = "superblocks/plan-approval-token";
|
|
24
|
+
/** The plan with its `@Name[id=...]` mentions, kept out of model-visible fields. */
|
|
25
|
+
export declare const PLAN_MARKUP_META_KEY = "superblocks/plan-markup";
|
|
24
26
|
export declare const CONFIRM_PLAN_APPROVAL_TOOL = "confirm_plan_approval";
|
|
25
|
-
export declare const APP_DECISION_RESOURCE_URI = "ui://gateway/decision-
|
|
26
|
-
export declare const APP_DECISION_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Superblocks needs an answer</title>\n <style>\n h1 {\n margin: 0;\n font-size: var(--font-text-md-size, 14px);\n font-weight: var(--font-weight-semibold, 600);\n }\n .meta {\n color: var(--sb-app-text-tertiary);\n font-size: var(--font-text-xs-size, 12px);\n }\n .reply {\n display: none;\n color: var(--sb-app-text-secondary);\n }\n .reply.shown {\n display: block;\n }\n .plan {\n display: none;\n max-height: 340px;\n margin: 0;\n overflow: auto;\n padding: 10px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background-subtle);\n font-size: var(--font-text-xs-size, 12px);\n word-break: break-word;\n }\n .plan.shown {\n display: block;\n }\n .plan > :first-child,\n .question > :first-child {\n margin-top: 0;\n }\n .plan > :last-child,\n .question > :last-child {\n margin-bottom: 0;\n }\n .plan h1,\n .plan h2,\n .plan h3,\n .plan p,\n .question h1,\n .question h2,\n .question h3,\n .question p {\n margin: 10px 0;\n }\n .plan h1,\n .plan h2,\n .plan h3,\n .question h1,\n .question h2,\n .question h3 {\n font-size: inherit;\n font-weight: var(--font-weight-semibold, 600);\n }\n .plan ul,\n .plan ol,\n .question ul,\n .question ol {\n margin: 10px 0;\n padding-left: 20px;\n }\n .plan blockquote,\n .question blockquote {\n margin: 10px 0;\n padding-left: 10px;\n border-left: 3px solid var(--sb-app-border);\n color: var(--sb-app-text-secondary);\n }\n .plan pre,\n .question pre {\n overflow: auto;\n padding: 8px;\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background);\n }\n .plan a,\n .question a {\n color: var(--sb-app-info-text);\n }\n .entity-tag {\n display: inline-flex;\n align-items: center;\n padding: 0 4px;\n max-width: 100%;\n overflow-wrap: anywhere;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: 4px;\n background: var(--sb-app-background);\n color: var(--sb-app-info-text);\n font-weight: 400;\n line-height: 18px;\n vertical-align: middle;\n white-space: normal;\n }\n .task {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .task input {\n margin: 0;\n }\n .plan code,\n .question code,\n button code {\n font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);\n }\n .question {\n display: none;\n overflow-wrap: anywhere;\n font-weight: var(--font-weight-medium, 500);\n }\n .question.shown {\n display: block;\n }\n .actions {\n display: none;\n flex-wrap: wrap;\n gap: 8px;\n }\n .actions.shown {\n display: flex;\n }\n #options {\n flex-direction: column;\n flex-wrap: nowrap;\n }\n #options > .option-button {\n width: 100%;\n overflow-wrap: anywhere;\n text-align: left;\n }\n .refine {\n display: none;\n flex-direction: column;\n gap: 8px;\n }\n .refine.shown {\n display: flex;\n }\n textarea {\n width: 100%;\n box-sizing: border-box;\n min-height: 64px;\n padding: 8px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background);\n color: var(--sb-app-text);\n font: inherit;\n resize: vertical;\n }\n .note {\n color: var(--sb-app-text-secondary);\n font-size: var(--font-text-xs-size, 12px);\n }\n\n :root {\n color-scheme: light dark;\n --sb-app-background: var(\n --color-background-primary,\n light-dark(#ffffff, #1b1c1f)\n );\n --sb-app-background-subtle: var(\n --color-background-secondary,\n light-dark(#f9fafb, #24272b)\n );\n --sb-app-background-hover: var(\n --color-background-tertiary,\n light-dark(#f3f4f6, #2f3437)\n );\n --sb-app-background-inverse: var(\n --color-background-inverse,\n light-dark(#121517, #f9fafb)\n );\n --sb-app-text: var(\n --color-text-primary,\n light-dark(#121517, #f9fafb)\n );\n --sb-app-text-secondary: var(\n --color-text-secondary,\n light-dark(#5a6272, #c6cad2)\n );\n --sb-app-text-tertiary: var(\n --color-text-tertiary,\n light-dark(#6b7280, #a4aab7)\n );\n --sb-app-text-inverse: var(\n --color-text-inverse,\n light-dark(#ffffff, #121517)\n );\n --sb-app-border: var(\n --color-border-primary,\n light-dark(#e8eaed, #454d5f)\n );\n --sb-app-ring: var(\n --color-ring-primary,\n light-dark(#0077b6, #27bbff)\n );\n --sb-app-info-background: var(\n --color-background-info,\n light-dark(#e6f5ff, #083a50)\n );\n --sb-app-info-text: var(\n --color-text-info,\n light-dark(#00679e, #7dd3fc)\n );\n --sb-app-info-border: var(\n --color-border-info,\n light-dark(#c2e7ff, #075985)\n );\n --sb-app-success-background: var(\n --color-background-success,\n light-dark(#e8f8ef, #0a3926)\n );\n --sb-app-success-text: var(\n --color-text-success,\n light-dark(#087a43, #6ee7b7)\n );\n --sb-app-success-border: var(\n --color-border-success,\n light-dark(#b8e8cf, #087a43)\n );\n --sb-app-warning-background: var(\n --color-background-warning,\n light-dark(#fff7e6, #49350a)\n );\n --sb-app-warning-text: var(\n --color-text-warning,\n light-dark(#946200, #fcd34d)\n );\n --sb-app-warning-border: var(\n --color-border-warning,\n light-dark(#f5d996, #946200)\n );\n --sb-app-danger-background: var(\n --color-background-danger,\n light-dark(#fff1f1, #4a1d1d)\n );\n --sb-app-danger-text: var(\n --color-text-danger,\n light-dark(#c54141, #fca5a5)\n );\n --sb-app-danger-border: var(\n --color-border-danger,\n light-dark(#ffcaca, #c54141)\n );\n }\n * {\n box-sizing: border-box;\n }\n body {\n min-width: 0;\n margin: 0;\n overflow-x: hidden;\n background: transparent;\n color: var(--sb-app-text);\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n font-size: var(--font-text-sm-size, 13px);\n line-height: var(--font-text-sm-line-height, 1.45);\n }\n .card {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-lg, 12px);\n background: var(--sb-app-background);\n box-shadow: var(--shadow-sm, 0 1px 2px rgba(18, 21, 23, 0.08));\n }\n h1,\n .meta,\n .note {\n min-width: 0;\n overflow-wrap: anywhere;\n }\n .note:empty {\n position: absolute;\n }\n button,\n textarea {\n font: inherit;\n }\n button {\n appearance: none;\n min-width: 0;\n padding: 8px 12px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background-subtle);\n color: var(--sb-app-text);\n font-weight: var(--font-weight-medium, 500);\n line-height: var(--font-text-sm-line-height, 1.45);\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background: var(--sb-app-background-hover);\n }\n .button-primary {\n border-color: var(--sb-app-background-inverse);\n background: var(--sb-app-background-inverse);\n color: var(--sb-app-text-inverse);\n }\n .button-primary:hover:not(:disabled) {\n border-color: var(--sb-app-background-inverse);\n background: var(--sb-app-background-inverse);\n }\n button:focus-visible,\n textarea:focus-visible,\n a:focus-visible {\n outline: 2px solid var(--sb-app-ring);\n outline-offset: 2px;\n }\n button:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n }\n .actions {\n min-width: 0;\n }\n .actions > button {\n overflow-wrap: anywhere;\n }\n @media (max-width: 360px) {\n .actions {\n flex-direction: column;\n flex-wrap: nowrap;\n }\n .actions > button {\n width: 100%;\n }\n }\n\n </style>\n</head>\n<body>\n <div class=\"card\">\n <h1 id=\"heading\">Superblocks</h1>\n <div id=\"appId\" class=\"meta\"></div>\n <div id=\"reply\" class=\"reply\"></div>\n <div id=\"plan\" class=\"plan\"></div>\n <div id=\"question\" class=\"question\"></div>\n <div id=\"options\" class=\"actions\"></div>\n <div id=\"planActions\" class=\"actions\">\n <button id=\"approve\" class=\"button-primary\" type=\"button\">Build it</button>\n <button id=\"refine\" class=\"button-secondary\" type=\"button\">Change something</button>\n </div>\n <div id=\"refineBox\" class=\"refine\">\n <textarea id=\"feedback\" placeholder=\"What should change?\"></textarea>\n <button id=\"sendRefine\" class=\"button-primary\" type=\"button\">Send</button>\n </div>\n <div id=\"note\" class=\"note\" aria-live=\"polite\"></div>\n </div>\n <script>\n (() => {\n const PROTOCOL_VERSION = \"2026-01-26\";\n let nextId = 1;\n const pending = new Map();\n let applicationId = \"\";\n let appName = \"\";\n let reply = \"\";\n let decision = null;\n let status = \"\";\n let refining = false;\n let sending = false;\n let answered = false;\n let approvalToken = \"\";\n let optionButtons = [];\n let lastHeight = 0;\n\n function post(message) {\n window.parent.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n post({ jsonrpc: \"2.0\", method, params });\n }\n\n function request(method, params) {\n const id = nextId++;\n return new Promise((resolve, reject) => {\n pending.set(id, { resolve, reject });\n post({ jsonrpc: \"2.0\", id, method, params });\n });\n }\n\n function element(id) {\n return document.getElementById(id);\n }\n\n function show(id, on) {\n const node = element(id);\n if (node && node.classList) {\n node.classList.toggle(\"shown\", on);\n }\n }\n\n // Hosts size the card from this notification alone; without it the card\n // is laid out at zero height and never appears.\n function reportSize() {\n const height = Math.ceil(document.documentElement.scrollHeight || 0);\n if (height <= 0 || height === lastHeight) {\n return;\n }\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n height,\n width: Math.ceil(window.innerWidth || 0),\n });\n }\n\n function note(text) {\n element(\"note\").textContent = text;\n show(\"note\", Boolean(text));\n reportSize();\n }\n\n function applyHostContext(context) {\n if (!context || typeof context !== \"object\") {\n return;\n }\n const root = document.documentElement;\n if (context.theme === \"light\" || context.theme === \"dark\") {\n root.setAttribute(\"data-theme\", context.theme);\n root.style.colorScheme = context.theme;\n }\n const variables = context.styles && context.styles.variables;\n if (variables && root.style) {\n for (const name of Object.keys(variables)) {\n const value = variables[name];\n if (typeof value === \"string\") {\n root.style.setProperty(name, value);\n }\n }\n }\n const fonts = context.styles && context.styles.css?.fonts;\n if (typeof fonts === \"string\") {\n let fontStyle = document.getElementById(\"__mcp-host-fonts\");\n if (!fontStyle) {\n fontStyle = document.createElement(\"style\");\n fontStyle.id = \"__mcp-host-fonts\";\n document.head.appendChild(fontStyle);\n }\n fontStyle.textContent = fonts;\n }\n reportSize();\n }\n\n function setBusy(on) {\n element(\"approve\").disabled = on;\n element(\"refine\").disabled = on;\n element(\"sendRefine\").disabled = on;\n for (const button of optionButtons) {\n button.disabled = on;\n }\n }\n\n /**\n * Posts the answer as the user's own turn.\n *\n * Not tools/call: calling edit_app from in here starts a build the host\n * agent never learns about, so nothing polls it and nothing is relayed.\n */\n function answer(text) {\n if (!text || sending || answered) {\n return;\n }\n sending = true;\n setBusy(true);\n note(\"Sending your answer\u2026\");\n request(\"ui/message\", {\n content: [{ text: text, type: \"text\" }],\n role: \"user\",\n })\n .then((result) => {\n sending = false;\n if (result && result.isError) {\n refused();\n return;\n }\n answered = true;\n setBusy(true);\n // The host taking the message is not the same as the user's turn\n // going anywhere: Claude Desktop leaves it in the composer as a\n // draft. Claiming it was sent strands the user on a build that is\n // waiting for their enter key.\n note(\"Your answer is in the chat \u2014 press enter there to send it.\");\n })\n .catch(refused);\n }\n\n function refused() {\n sending = false;\n setBusy(false);\n note(\n \"This host would not take your answer from the card \u2014 reply in the chat instead.\",\n );\n }\n\n function approvalRefused() {\n sending = false;\n setBusy(false);\n note(\n \"This host could not verify your approval - approve this plan in the Superblocks editor instead.\",\n );\n }\n\n function approve() {\n if (sending || answered) {\n return;\n }\n if (!approvalToken) {\n approvalRefused();\n return;\n }\n sending = true;\n setBusy(true);\n note(\"Confirming your approval...\");\n request(\"tools/call\", {\n arguments: { approvalToken: approvalToken },\n name: \"confirm_plan_approval\",\n })\n .then((result) => {\n sending = false;\n if (result && result.isError) {\n approvalRefused();\n return;\n }\n answer(\"Build it \u2014 approve the Superblocks plan as written.\");\n })\n .catch(approvalRefused);\n }\n\n function renderOptions(options) {\n const host = element(\"options\");\n host.replaceChildren();\n optionButtons = [];\n options.forEach((option, index) => {\n const button = document.createElement(\"button\");\n button.id = \"option-\" + index;\n button.className = \"button-secondary option-button\";\n button.type = \"button\";\n button.innerHTML = markdownInline(option, false);\n // Verbatim: an option Superblocks did not offer reaches it as a\n // prompt answering a question it never asked.\n button.addEventListener(\"click\", () => answer(option));\n host.appendChild(button);\n optionButtons.push(button);\n });\n show(\"options\", options.length > 0);\n }\n\n function waitingNote() {\n if (status === \"building\") {\n return \"Superblocks is still building \u2014 nothing to answer yet.\";\n }\n // Something opened this card, so \"nothing is waiting on you\" is a dead\n // end: say where the answer to that actually is.\n return \"Nothing to answer here \u2014 the chat has the latest.\";\n }\n\n function escapeHtml(text) {\n return String(text).replace(/[&<>\"']/g, (character) => {\n return {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n }[character];\n });\n }\n\n function markdownInline(text, allowLinks = true) {\n const codeDelimiter = String.fromCharCode(96);\n const emphasis = (text) =>\n text\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/__([^_]+)__/g, \"<strong>$1</strong>\")\n .replace(/(^|[^*])\\*([^*]+)\\*(?!\\*)/g, \"$1<em>$2</em>\")\n .replace(\n /(^|[^A-Za-z0-9_])_([^_]+)_(?![A-Za-z0-9_])/g,\n \"$1<em>$2</em>\",\n );\n const safeLink = (url) => {\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"https:\" && Boolean(parsed.hostname);\n } catch {\n return false;\n }\n };\n const entities = /@((?:(?!\\[id=)[^@\\r\\n])+?(?<!\\s))\\[id=[^\\]\\r\\n]+\\]/gu;\n const links = /\\[([^\\]]+)\\]\\(([^\\s)]+)\\)/g;\n const renderLinks = (value, allowLinks) => {\n const output = [];\n let offset = 0;\n const text = String(value);\n for (const match of text.matchAll(links)) {\n const index = match.index ?? 0;\n if (text[index - 1] === \"!\") {\n continue;\n }\n output.push(emphasis(escapeHtml(text.slice(offset, index))));\n const label = match[1];\n const url = match[2];\n if (safeLink(url)) {\n output.push(\n allowLinks\n ? '<a href=\"' +\n escapeHtml(url) +\n '\" target=\"_blank\" rel=\"noopener noreferrer\">' +\n markdownInline(label, false) +\n \"</a>\"\n : markdownInline(label, false),\n );\n } else {\n output.push(emphasis(escapeHtml(match[0])));\n }\n offset = index + match[0].length;\n }\n output.push(emphasis(escapeHtml(text.slice(offset))));\n return output.join(\"\");\n };\n const renderText = (value, allowLinks) => {\n const text = String(value);\n let placeholderDelimiter = String.fromCharCode(0);\n while (text.includes(placeholderDelimiter)) {\n placeholderDelimiter += String.fromCharCode(0);\n }\n const linkDestinations = Array.from(text.matchAll(links), (match) => {\n const start = (match.index ?? 0) + match[1].length + 3;\n return { start, end: start + match[2].length };\n });\n const entityTags = [];\n const tokenized = text.replace(entities, (match, label, index) => {\n if (\n linkDestinations.some(\n ({ start, end }) => index >= start && index < end,\n )\n ) {\n return match;\n }\n const placeholder =\n placeholderDelimiter + entityTags.length + placeholderDelimiter;\n entityTags.push(\n '<span class=\"entity-tag\">' + escapeHtml(label) + \"</span>\",\n );\n return placeholder;\n });\n return entityTags.reduce(\n (html, tag, index) =>\n html.replace(\n placeholderDelimiter + index + placeholderDelimiter,\n () => tag,\n ),\n renderLinks(tokenized, allowLinks),\n );\n };\n return String(text)\n .split(/(\\x60[^\\x60]+\\x60)/)\n .map((part) =>\n part.startsWith(codeDelimiter) && part.endsWith(codeDelimiter)\n ? \"<code>\" + escapeHtml(part.slice(1, -1)) + \"</code>\"\n : renderText(part, allowLinks),\n )\n .join(\"\");\n }\n\n function markdownHtml(markdown) {\n const lines = String(markdown).replace(/\\r/g, \"\").split(\"\\n\");\n const output = [];\n const lists = [];\n let paragraph = [];\n const closeParagraph = () => {\n if (paragraph.length > 0) {\n output.push(\"<p>\" + markdownInline(paragraph.join(\" \")) + \"</p>\");\n paragraph = [];\n }\n };\n const closeList = () => {\n const list = lists.pop();\n if (!list) {\n return;\n }\n if (list.open) {\n output.push(\"</li>\");\n }\n output.push(\"</\" + list.tag + \">\");\n };\n const closeLists = () => {\n while (lists.length > 0) {\n closeList();\n }\n };\n const listItem = (line) => {\n const match = /^(\\s*)([-*+]|\\d+\\.)\\s+(.+)$/.exec(line);\n if (!match) {\n return undefined;\n }\n return {\n content: match[3],\n indent: match[1].replace(/\\t/g, \" \").length,\n tag: /\\d+\\./.test(match[2]) ? \"ol\" : \"ul\",\n };\n };\n const renderListItem = (item) => {\n while (\n lists.length > 0 &&\n item.indent < lists[lists.length - 1].indent\n ) {\n closeList();\n }\n let list = lists[lists.length - 1];\n if (\n !list ||\n item.indent > list.indent ||\n (item.indent === list.indent && item.tag !== list.tag)\n ) {\n if (list && item.indent === list.indent) {\n closeList();\n }\n output.push(\"<\" + item.tag + \">\");\n list = { indent: item.indent, open: false, tag: item.tag };\n lists.push(list);\n }\n if (list.open) {\n output.push(\"</li>\");\n }\n const task = /^\\[([ xX])\\]\\s+(.+)$/.exec(item.content);\n output.push(\n \"<li>\" +\n (task\n ? '<label class=\"task\"><input type=\"checkbox\" disabled' +\n (task[1].toLowerCase() === \"x\" ? \" checked\" : \"\") +\n \">\" +\n markdownInline(task[2]) +\n \"</label>\"\n : markdownInline(item.content)),\n );\n list.open = true;\n };\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index];\n const fence = /^\\x60{3}[^\\x60]*\\s*$/.exec(line);\n if (fence) {\n closeParagraph();\n closeLists();\n const code = [];\n while (\n index + 1 < lines.length &&\n !/^\\x60{3}\\s*$/.test(lines[index + 1])\n ) {\n index += 1;\n code.push(lines[index]);\n }\n if (index + 1 < lines.length) {\n index += 1;\n }\n output.push(\n \"<pre><code>\" + escapeHtml(code.join(\"\\n\")) + \"</code></pre>\",\n );\n continue;\n }\n const heading = /^(#{1,3})\\s+(.+?)\\s*$/.exec(line);\n const quote = /^>\\s?(.*)$/.exec(line);\n const item = listItem(line);\n const rule = /^(---+|\\*\\*\\*+|___+)$/.test(line.trim());\n if (heading) {\n closeParagraph();\n closeLists();\n const level = heading[1].length;\n output.push(\"<h\" + level + \">\" + markdownInline(heading[2]) + \"</h\" + level + \">\");\n } else if (quote) {\n closeParagraph();\n closeLists();\n const quoted = [];\n while (index < lines.length) {\n const next = /^>\\s?(.*)$/.exec(lines[index]);\n if (!next) {\n break;\n }\n quoted.push(next[1]);\n index += 1;\n }\n index -= 1;\n output.push(\"<blockquote>\" + markdownHtml(quoted.join(\"\\n\")) + \"</blockquote>\");\n } else if (item) {\n closeParagraph();\n renderListItem(item);\n } else if (rule) {\n closeParagraph();\n closeLists();\n output.push(\"<hr>\");\n } else if (line.trim()) {\n closeLists();\n paragraph.push(line.trim());\n } else {\n closeParagraph();\n closeLists();\n }\n }\n closeParagraph();\n closeLists();\n return output.join(\"\");\n }\n\n function render() {\n const waiting = status === \"needs_decision\";\n const plan =\n waiting && decision && decision.kind === \"plan\" ? decision : null;\n const choice =\n waiting && decision && decision.kind === \"multi_choice\"\n ? decision\n : null;\n\n element(\"heading\").textContent = plan\n ? \"Superblocks proposed a plan\"\n : choice\n ? \"Superblocks has a question\"\n : waiting\n ? \"Superblocks is waiting on you\"\n : \"Superblocks\";\n element(\"appId\").textContent = appName || applicationId;\n // Superblocks narrates its whole working session into the reply. Once\n // there is a plan or a question, that narration is preamble in front\n // of the thing being asked, so it is dropped: the ask is the ask. It\n // survives only when there is nothing structured, where the reply is\n // itself the whole question and hiding it leaves a blank card.\n const narrationOnly = Boolean(plan || choice);\n element(\"reply\").textContent = narrationOnly ? \"\" : reply;\n show(\"reply\", Boolean(reply) && !narrationOnly);\n element(\"plan\").innerHTML = plan ? markdownHtml(plan.plan) : \"\";\n show(\"plan\", Boolean(plan));\n element(\"question\").innerHTML = choice\n ? markdownHtml(choice.question)\n : \"\";\n show(\"question\", Boolean(choice));\n show(\"planActions\", Boolean(plan));\n renderOptions(choice ? choice.options : []);\n // A turn can stop waiting on the user having streamed its plan or\n // question as chat text, leaving nothing structured to lay out. The\n // reply is then the whole ask, so free text is the only honest form.\n show(\"refineBox\", plan ? refining : waiting && !choice);\n setBusy(answered || sending);\n if (!answered && !sending) {\n note(waiting ? \"\" : waitingNote());\n }\n reportSize();\n }\n\n function applyPayload(payload) {\n let value = payload;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch (error) {\n return;\n }\n }\n if (!value || typeof value !== \"object\") {\n return;\n }\n if (Array.isArray(value.content)) {\n applyContent(value.content);\n if (value.structuredContent) {\n applyPayload(value.structuredContent);\n }\n return;\n }\n if (value.result && typeof value.result === \"object\") {\n applyPayload(value.result);\n return;\n }\n if (value.kind === \"ok\" && value.value) {\n value = value.value;\n }\n if (typeof value.applicationId === \"string\") {\n applicationId = value.applicationId;\n }\n if (typeof value.name === \"string\") {\n appName = value.name;\n }\n if (typeof value.reply === \"string\") {\n reply = value.reply;\n }\n if (typeof value.status === \"string\") {\n status = value.status;\n }\n if (\"decision\" in value) {\n decision =\n value.decision && typeof value.decision === \"object\"\n ? value.decision\n : null;\n }\n render();\n }\n\n function applyContent(content) {\n for (const part of content || []) {\n if (part && part.type === \"text\" && typeof part.text === \"string\") {\n applyPayload(part.text);\n }\n }\n }\n\n function applyApprovalMetadata(value) {\n const metadata = value && typeof value === \"object\" ? value._meta : null;\n if (\n metadata &&\n typeof metadata[\"superblocks/plan-approval-token\"] === \"string\"\n ) {\n approvalToken = metadata[\"superblocks/plan-approval-token\"];\n }\n }\n\n function handleHostMessage(event) {\n // App documents are opaque-origin, so the host window is the only\n // identity available to authenticate a message against.\n if (event.source !== window.parent) {\n return;\n }\n const data = event.data;\n if (!data || typeof data !== \"object\") {\n return;\n }\n if (data.method === \"ping\") {\n post({ jsonrpc: \"2.0\", id: data.id, result: {} });\n return;\n }\n if (data.id != null && pending.has(data.id)) {\n const waiter = pending.get(data.id);\n pending.delete(data.id);\n if (data.error) {\n waiter.reject(data.error);\n } else {\n waiter.resolve(data.result);\n }\n return;\n }\n const method = data.method;\n const params = data.params || {};\n if (\n method === \"ui/notifications/tool-result\" ||\n method === \"ui/tool-result\" ||\n method === \"tools/call\"\n ) {\n approvalToken = \"\";\n applyApprovalMetadata(params);\n applyApprovalMetadata(params.result);\n applyContent(params.content || (params.result || {}).content || []);\n if (params.structuredContent) {\n applyPayload(params.structuredContent);\n }\n }\n if (method === \"ui/notifications/tool-input\" && params.arguments) {\n applyPayload(params.arguments);\n }\n if (method === \"ui/notifications/host-context-changed\") {\n applyHostContext(params);\n }\n }\n\n window.addEventListener(\"message\", handleHostMessage);\n\n element(\"approve\").addEventListener(\"click\", approve);\n element(\"refine\").addEventListener(\"click\", () => {\n refining = true;\n element(\"approve\").className = \"button-secondary\";\n show(\"refineBox\", true);\n element(\"feedback\").focus();\n reportSize();\n });\n element(\"sendRefine\").addEventListener(\"click\", () => {\n const feedback = (element(\"feedback\").value || \"\").trim();\n if (!feedback) {\n return;\n }\n answer(\n decision && decision.kind === \"plan\"\n ? \"Change the Superblocks plan: \" + feedback\n : feedback,\n );\n });\n\n if (typeof ResizeObserver === \"function\") {\n new ResizeObserver(reportSize).observe(document.documentElement);\n }\n\n void request(\"ui/initialize\", {\n appCapabilities: { availableDisplayModes: [\"inline\"] },\n appInfo: { name: \"gateway-decision\", version: \"0.0.1\" },\n protocolVersion: PROTOCOL_VERSION,\n })\n .then((result) => {\n // Hosts hold back tool results until the app confirms it is ready.\n notify(\"ui/notifications/initialized\", {});\n applyHostContext(result && result.hostContext);\n if (result && result.toolResult) {\n approvalToken = \"\";\n applyApprovalMetadata(result.toolResult);\n applyPayload(result.toolResult);\n }\n })\n .catch(() => {\n // Hosts that push tool results via notification still work.\n })\n .finally(reportSize);\n })();\n </script>\n</body>\n</html>\n";
|
|
27
|
+
export declare const APP_DECISION_RESOURCE_URI = "ui://gateway/decision-v26.html";
|
|
28
|
+
export declare const APP_DECISION_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Superblocks needs an answer</title>\n <style>\n h1 {\n margin: 0;\n font-size: var(--font-text-md-size, 14px);\n font-weight: var(--font-weight-semibold, 600);\n }\n .meta {\n color: var(--sb-app-text-tertiary);\n font-size: var(--font-text-xs-size, 12px);\n }\n .reply {\n display: none;\n color: var(--sb-app-text-secondary);\n }\n .reply.shown {\n display: block;\n }\n .plan {\n display: none;\n max-height: 340px;\n margin: 0;\n overflow: auto;\n padding: 10px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background-subtle);\n font-size: var(--font-text-xs-size, 12px);\n word-break: break-word;\n }\n .plan.shown {\n display: block;\n }\n .plan > :first-child,\n .question > :first-child {\n margin-top: 0;\n }\n .plan > :last-child,\n .question > :last-child {\n margin-bottom: 0;\n }\n .plan h1,\n .plan h2,\n .plan h3,\n .plan p,\n .question h1,\n .question h2,\n .question h3,\n .question p {\n margin: 10px 0;\n }\n .plan h1,\n .plan h2,\n .plan h3,\n .question h1,\n .question h2,\n .question h3 {\n font-size: inherit;\n font-weight: var(--font-weight-semibold, 600);\n }\n .plan ul,\n .plan ol,\n .question ul,\n .question ol {\n margin: 10px 0;\n padding-left: 20px;\n }\n .plan blockquote,\n .question blockquote {\n margin: 10px 0;\n padding-left: 10px;\n border-left: 3px solid var(--sb-app-border);\n color: var(--sb-app-text-secondary);\n }\n .plan pre,\n .question pre {\n overflow: auto;\n padding: 8px;\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background);\n }\n .plan a,\n .question a {\n color: var(--sb-app-info-text);\n }\n .entity-tag {\n display: inline-flex;\n align-items: center;\n padding: 0 4px;\n max-width: 100%;\n overflow-wrap: anywhere;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: 4px;\n background: var(--sb-app-background);\n color: var(--sb-app-info-text);\n font-weight: 400;\n line-height: 18px;\n vertical-align: middle;\n white-space: normal;\n }\n .task {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .task input {\n margin: 0;\n }\n .plan code,\n .question code,\n button code {\n font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);\n }\n .question {\n display: none;\n overflow-wrap: anywhere;\n font-weight: var(--font-weight-medium, 500);\n }\n .question.shown {\n display: block;\n }\n .actions {\n display: none;\n flex-wrap: wrap;\n gap: 8px;\n }\n .actions.shown {\n display: flex;\n }\n #options {\n flex-direction: column;\n flex-wrap: nowrap;\n }\n #options > .option-button {\n width: 100%;\n overflow-wrap: anywhere;\n text-align: left;\n }\n .refine {\n display: none;\n flex-direction: column;\n gap: 8px;\n }\n .refine.shown {\n display: flex;\n }\n textarea {\n width: 100%;\n box-sizing: border-box;\n min-height: 64px;\n padding: 8px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background);\n color: var(--sb-app-text);\n font: inherit;\n resize: vertical;\n }\n .note {\n color: var(--sb-app-text-secondary);\n font-size: var(--font-text-xs-size, 12px);\n }\n\n :root {\n color-scheme: light dark;\n --sb-app-background: var(\n --color-background-primary,\n light-dark(#ffffff, #1b1c1f)\n );\n --sb-app-background-subtle: var(\n --color-background-secondary,\n light-dark(#f9fafb, #24272b)\n );\n --sb-app-background-hover: var(\n --color-background-tertiary,\n light-dark(#f3f4f6, #2f3437)\n );\n --sb-app-background-inverse: var(\n --color-background-inverse,\n light-dark(#121517, #f9fafb)\n );\n --sb-app-text: var(\n --color-text-primary,\n light-dark(#121517, #f9fafb)\n );\n --sb-app-text-secondary: var(\n --color-text-secondary,\n light-dark(#5a6272, #c6cad2)\n );\n --sb-app-text-tertiary: var(\n --color-text-tertiary,\n light-dark(#6b7280, #a4aab7)\n );\n --sb-app-text-inverse: var(\n --color-text-inverse,\n light-dark(#ffffff, #121517)\n );\n --sb-app-border: var(\n --color-border-primary,\n light-dark(#e8eaed, #454d5f)\n );\n --sb-app-ring: var(\n --color-ring-primary,\n light-dark(#0077b6, #27bbff)\n );\n --sb-app-info-background: var(\n --color-background-info,\n light-dark(#e6f5ff, #083a50)\n );\n --sb-app-info-text: var(\n --color-text-info,\n light-dark(#00679e, #7dd3fc)\n );\n --sb-app-info-border: var(\n --color-border-info,\n light-dark(#c2e7ff, #075985)\n );\n --sb-app-success-background: var(\n --color-background-success,\n light-dark(#e8f8ef, #0a3926)\n );\n --sb-app-success-text: var(\n --color-text-success,\n light-dark(#087a43, #6ee7b7)\n );\n --sb-app-success-border: var(\n --color-border-success,\n light-dark(#b8e8cf, #087a43)\n );\n --sb-app-warning-background: var(\n --color-background-warning,\n light-dark(#fff7e6, #49350a)\n );\n --sb-app-warning-text: var(\n --color-text-warning,\n light-dark(#946200, #fcd34d)\n );\n --sb-app-warning-border: var(\n --color-border-warning,\n light-dark(#f5d996, #946200)\n );\n --sb-app-danger-background: var(\n --color-background-danger,\n light-dark(#fff1f1, #4a1d1d)\n );\n --sb-app-danger-text: var(\n --color-text-danger,\n light-dark(#c54141, #fca5a5)\n );\n --sb-app-danger-border: var(\n --color-border-danger,\n light-dark(#ffcaca, #c54141)\n );\n }\n * {\n box-sizing: border-box;\n }\n body {\n min-width: 0;\n margin: 0;\n overflow-x: hidden;\n background: transparent;\n color: var(--sb-app-text);\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n font-size: var(--font-text-sm-size, 13px);\n line-height: var(--font-text-sm-line-height, 1.45);\n }\n .card {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-lg, 12px);\n background: var(--sb-app-background);\n box-shadow: var(--shadow-sm, 0 1px 2px rgba(18, 21, 23, 0.08));\n }\n h1,\n .meta,\n .note {\n min-width: 0;\n overflow-wrap: anywhere;\n }\n .note:empty {\n position: absolute;\n }\n button,\n textarea {\n font: inherit;\n }\n button {\n appearance: none;\n min-width: 0;\n padding: 8px 12px;\n border: var(--border-width-regular, 1px) solid var(--sb-app-border);\n border-radius: var(--border-radius-md, 8px);\n background: var(--sb-app-background-subtle);\n color: var(--sb-app-text);\n font-weight: var(--font-weight-medium, 500);\n line-height: var(--font-text-sm-line-height, 1.45);\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background: var(--sb-app-background-hover);\n }\n .button-primary {\n border-color: var(--sb-app-background-inverse);\n background: var(--sb-app-background-inverse);\n color: var(--sb-app-text-inverse);\n }\n .button-primary:hover:not(:disabled) {\n border-color: var(--sb-app-background-inverse);\n background: var(--sb-app-background-inverse);\n }\n button:focus-visible,\n textarea:focus-visible,\n a:focus-visible {\n outline: 2px solid var(--sb-app-ring);\n outline-offset: 2px;\n }\n button:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n }\n .actions {\n min-width: 0;\n }\n .actions > button {\n overflow-wrap: anywhere;\n }\n @media (max-width: 360px) {\n .actions {\n flex-direction: column;\n flex-wrap: nowrap;\n }\n .actions > button {\n width: 100%;\n }\n }\n\n </style>\n</head>\n<body>\n <div class=\"card\">\n <h1 id=\"heading\">Superblocks</h1>\n <div id=\"appId\" class=\"meta\"></div>\n <div id=\"reply\" class=\"reply\"></div>\n <div id=\"plan\" class=\"plan\"></div>\n <div id=\"question\" class=\"question\"></div>\n <div id=\"options\" class=\"actions\"></div>\n <div id=\"planActions\" class=\"actions\">\n <button id=\"approve\" class=\"button-primary\" type=\"button\">Build it</button>\n <button id=\"refine\" class=\"button-secondary\" type=\"button\">Change something</button>\n </div>\n <div id=\"refineBox\" class=\"refine\">\n <textarea id=\"feedback\" placeholder=\"What should change?\"></textarea>\n <button id=\"sendRefine\" class=\"button-primary\" type=\"button\">Send</button>\n </div>\n <div id=\"note\" class=\"note\" aria-live=\"polite\"></div>\n </div>\n <script>\n (() => {\n const PROTOCOL_VERSION = \"2026-01-26\";\n let nextId = 1;\n const pending = new Map();\n let applicationId = \"\";\n let branch;\n let appName = \"\";\n let reply = \"\";\n let decision = null;\n let status = \"\";\n let refining = false;\n let sending = false;\n let answered = false;\n let approvalToken = \"\";\n let planMarkup = \"\";\n let confirmedApprovalToken = \"\";\n let optionButtons = [];\n let lastHeight = 0;\n\n function post(message) {\n window.parent.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n post({ jsonrpc: \"2.0\", method, params });\n }\n\n function request(method, params) {\n const id = nextId++;\n return new Promise((resolve, reject) => {\n pending.set(id, { resolve, reject });\n post({ jsonrpc: \"2.0\", id, method, params });\n });\n }\n\n function element(id) {\n return document.getElementById(id);\n }\n\n function show(id, on) {\n const node = element(id);\n if (node && node.classList) {\n node.classList.toggle(\"shown\", on);\n }\n }\n\n // Hosts size the card from this notification alone; without it the card\n // is laid out at zero height and never appears.\n function reportSize() {\n const height = Math.ceil(document.documentElement.scrollHeight || 0);\n if (height <= 0 || height === lastHeight) {\n return;\n }\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n height,\n width: Math.ceil(window.innerWidth || 0),\n });\n }\n\n function note(text) {\n element(\"note\").textContent = text;\n show(\"note\", Boolean(text));\n reportSize();\n }\n\n function applyHostContext(context) {\n if (!context || typeof context !== \"object\") {\n return;\n }\n const root = document.documentElement;\n if (context.theme === \"light\" || context.theme === \"dark\") {\n root.setAttribute(\"data-theme\", context.theme);\n root.style.colorScheme = context.theme;\n }\n const variables = context.styles && context.styles.variables;\n if (variables && root.style) {\n for (const name of Object.keys(variables)) {\n const value = variables[name];\n if (typeof value === \"string\") {\n root.style.setProperty(name, value);\n }\n }\n }\n const fonts = context.styles && context.styles.css?.fonts;\n if (typeof fonts === \"string\") {\n let fontStyle = document.getElementById(\"__mcp-host-fonts\");\n if (!fontStyle) {\n fontStyle = document.createElement(\"style\");\n fontStyle.id = \"__mcp-host-fonts\";\n document.head.appendChild(fontStyle);\n }\n fontStyle.textContent = fonts;\n }\n reportSize();\n }\n\n function setBusy(on) {\n element(\"approve\").disabled = on;\n element(\"refine\").disabled = on;\n element(\"sendRefine\").disabled = on;\n for (const button of optionButtons) {\n button.disabled = on;\n }\n }\n\n /**\n * Posts the answer as the user's own turn.\n *\n * The host must receive a user turn so it continues following the build.\n */\n function answer(text) {\n if (!text || sending || answered) {\n return;\n }\n sending = true;\n setBusy(true);\n note(\"Sending your answer\u2026\");\n request(\"ui/message\", {\n content: [{ text: text, type: \"text\" }],\n role: \"user\",\n })\n .then((result) => {\n sending = false;\n if (result && result.isError) {\n refused();\n return;\n }\n answered = true;\n setBusy(true);\n // The host taking the message is not the same as the user's turn\n // going anywhere: Claude Desktop leaves it in the composer as a\n // draft. Claiming it was sent strands the user on a build that is\n // waiting for their enter key.\n note(\"Your answer is in the chat \u2014 press enter there to send it.\");\n })\n .catch(refused);\n }\n\n function refused() {\n sending = false;\n setBusy(false);\n note(\n \"This host would not take your answer from the card \u2014 reply in the chat instead.\",\n );\n }\n\n function approvalRefused() {\n sending = false;\n setBusy(false);\n note(\n \"This host could not verify your approval - approve this plan in the Superblocks editor instead.\",\n );\n }\n\n /**\n * Confirms the user's approval of the current challenge, once per token:\n * a confirmed challenge stays confirmed while a send that never went out\n * is retried, and confirming it again is refused.\n */\n async function confirmApproval() {\n if (!approvalToken) {\n return false;\n }\n if (confirmedApprovalToken === approvalToken) {\n return true;\n }\n const token = approvalToken;\n const confirmation = await request(\"tools/call\", {\n arguments: { approvalToken: token },\n name: \"confirm_plan_approval\",\n });\n if (confirmation && confirmation.isError) {\n return false;\n }\n confirmedApprovalToken = token;\n return true;\n }\n\n async function approve() {\n if (sending || answered) {\n return;\n }\n if (!approvalToken) {\n approvalRefused();\n return;\n }\n sending = true;\n setBusy(true);\n note(\"Confirming your approval...\");\n try {\n const confirmed = await confirmApproval();\n sending = false;\n if (!confirmed) {\n approvalRefused();\n return;\n }\n answer(\"Build it \u2014 approve the Superblocks plan as written.\");\n } catch {\n approvalRefused();\n }\n }\n\n function renderOptions(options) {\n const host = element(\"options\");\n host.replaceChildren();\n optionButtons = [];\n const permissionMessageId = decision && decision.kind === \"tool_permission\"\n ? decision.messageId\n : undefined;\n options.forEach((option, index) => {\n const button = document.createElement(\"button\");\n button.id = \"option-\" + index;\n button.className = \"button-secondary option-button\";\n button.type = \"button\";\n button.innerHTML = markdownInline(option, false);\n // Verbatim: an option Superblocks did not offer reaches it as a\n // prompt answering a question it never asked.\n button.addEventListener(\"click\", () => permissionMessageId\n ? answerPermission(option, permissionMessageId)\n : answer(option));\n host.appendChild(button);\n optionButtons.push(button);\n });\n show(\"options\", options.length > 0);\n }\n\n async function answerPermission(text, permissionMessageId) {\n if (sending || answered) {\n return;\n }\n sending = true;\n setBusy(true);\n note(\"Submitting your response...\");\n const refused = () => {\n sending = false;\n setBusy(false);\n note(\"This request could not be submitted. Ask the chat to show the current approval request again.\");\n };\n try {\n if (text === \"Approve\" && !(await confirmApproval())) {\n refused();\n return;\n }\n const result = await request(\"tools/call\", {\n name: \"edit_app\",\n arguments: { applicationId, ...(branch ? { branch } : {}), permissionMessageId, prompt: text },\n });\n if (result?.isError || result?.structuredContent?.status !== \"building\") {\n refused();\n return;\n }\n sending = false;\n answer(\"My response to the approval request is queued. Please check the build's progress to confirm it was accepted.\");\n } catch {\n refused();\n }\n }\n\n function waitingNote() {\n if (status === \"building\") {\n return \"Superblocks is still building \u2014 nothing to answer yet.\";\n }\n // Something opened this card, so \"nothing is waiting on you\" is a dead\n // end: say where the answer to that actually is.\n return \"Nothing to answer here \u2014 the chat has the latest.\";\n }\n\n function escapeHtml(text) {\n return String(text).replace(/[&<>\"']/g, (character) => {\n return {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n }[character];\n });\n }\n\n function markdownInline(text, allowLinks = true) {\n const codeDelimiter = String.fromCharCode(96);\n const emphasis = (text) =>\n text\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/__([^_]+)__/g, \"<strong>$1</strong>\")\n .replace(/(^|[^*])\\*([^*]+)\\*(?!\\*)/g, \"$1<em>$2</em>\")\n .replace(\n /(^|[^A-Za-z0-9_])_([^_]+)_(?![A-Za-z0-9_])/g,\n \"$1<em>$2</em>\",\n );\n const safeLink = (url) => {\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"https:\" && Boolean(parsed.hostname);\n } catch {\n return false;\n }\n };\n const entities = /@((?:(?!\\[id=)[^@\\r\\n])+?(?<!\\s))\\[id=[^\\]\\r\\n]+\\]/gu;\n const links = /\\[([^\\]]+)\\]\\(([^\\s)]+)\\)/g;\n const renderLinks = (value, allowLinks) => {\n const output = [];\n let offset = 0;\n const text = String(value);\n for (const match of text.matchAll(links)) {\n const index = match.index ?? 0;\n if (text[index - 1] === \"!\") {\n continue;\n }\n output.push(emphasis(escapeHtml(text.slice(offset, index))));\n const label = match[1];\n const url = match[2];\n if (safeLink(url)) {\n output.push(\n allowLinks\n ? '<a href=\"' +\n escapeHtml(url) +\n '\" target=\"_blank\" rel=\"noopener noreferrer\">' +\n markdownInline(label, false) +\n \"</a>\"\n : markdownInline(label, false),\n );\n } else {\n output.push(emphasis(escapeHtml(match[0])));\n }\n offset = index + match[0].length;\n }\n output.push(emphasis(escapeHtml(text.slice(offset))));\n return output.join(\"\");\n };\n const renderText = (value, allowLinks) => {\n const text = String(value);\n let placeholderDelimiter = String.fromCharCode(0);\n while (text.includes(placeholderDelimiter)) {\n placeholderDelimiter += String.fromCharCode(0);\n }\n const linkDestinations = Array.from(text.matchAll(links), (match) => {\n const start = (match.index ?? 0) + match[1].length + 3;\n return { start, end: start + match[2].length };\n });\n const entityTags = [];\n const tokenized = text.replace(entities, (match, label, index) => {\n if (\n linkDestinations.some(\n ({ start, end }) => index >= start && index < end,\n )\n ) {\n return match;\n }\n const placeholder =\n placeholderDelimiter + entityTags.length + placeholderDelimiter;\n entityTags.push(\n '<span class=\"entity-tag\">' + escapeHtml(label) + \"</span>\",\n );\n return placeholder;\n });\n return entityTags.reduce(\n (html, tag, index) =>\n html.replace(\n placeholderDelimiter + index + placeholderDelimiter,\n () => tag,\n ),\n renderLinks(tokenized, allowLinks),\n );\n };\n return String(text)\n .split(/(\\x60[^\\x60]+\\x60)/)\n .map((part) =>\n part.startsWith(codeDelimiter) && part.endsWith(codeDelimiter)\n ? \"<code>\" + escapeHtml(part.slice(1, -1)) + \"</code>\"\n : renderText(part, allowLinks),\n )\n .join(\"\");\n }\n\n function markdownHtml(markdown) {\n const lines = String(markdown).replace(/\\r/g, \"\").split(\"\\n\");\n const output = [];\n const lists = [];\n let paragraph = [];\n const closeParagraph = () => {\n if (paragraph.length > 0) {\n output.push(\"<p>\" + markdownInline(paragraph.join(\" \")) + \"</p>\");\n paragraph = [];\n }\n };\n const closeList = () => {\n const list = lists.pop();\n if (!list) {\n return;\n }\n if (list.open) {\n output.push(\"</li>\");\n }\n output.push(\"</\" + list.tag + \">\");\n };\n const closeLists = () => {\n while (lists.length > 0) {\n closeList();\n }\n };\n const listItem = (line) => {\n const match = /^(\\s*)([-*+]|\\d+\\.)\\s+(.+)$/.exec(line);\n if (!match) {\n return undefined;\n }\n return {\n content: match[3],\n indent: match[1].replace(/\\t/g, \" \").length,\n tag: /\\d+\\./.test(match[2]) ? \"ol\" : \"ul\",\n };\n };\n const renderListItem = (item) => {\n while (\n lists.length > 0 &&\n item.indent < lists[lists.length - 1].indent\n ) {\n closeList();\n }\n let list = lists[lists.length - 1];\n if (\n !list ||\n item.indent > list.indent ||\n (item.indent === list.indent && item.tag !== list.tag)\n ) {\n if (list && item.indent === list.indent) {\n closeList();\n }\n output.push(\"<\" + item.tag + \">\");\n list = { indent: item.indent, open: false, tag: item.tag };\n lists.push(list);\n }\n if (list.open) {\n output.push(\"</li>\");\n }\n const task = /^\\[([ xX])\\]\\s+(.+)$/.exec(item.content);\n output.push(\n \"<li>\" +\n (task\n ? '<label class=\"task\"><input type=\"checkbox\" disabled' +\n (task[1].toLowerCase() === \"x\" ? \" checked\" : \"\") +\n \">\" +\n markdownInline(task[2]) +\n \"</label>\"\n : markdownInline(item.content)),\n );\n list.open = true;\n };\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index];\n const fence = /^\\x60{3}[^\\x60]*\\s*$/.exec(line);\n if (fence) {\n closeParagraph();\n closeLists();\n const code = [];\n while (\n index + 1 < lines.length &&\n !/^\\x60{3}\\s*$/.test(lines[index + 1])\n ) {\n index += 1;\n code.push(lines[index]);\n }\n if (index + 1 < lines.length) {\n index += 1;\n }\n output.push(\n \"<pre><code>\" + escapeHtml(code.join(\"\\n\")) + \"</code></pre>\",\n );\n continue;\n }\n const heading = /^(#{1,3})\\s+(.+?)\\s*$/.exec(line);\n const quote = /^>\\s?(.*)$/.exec(line);\n const item = listItem(line);\n const rule = /^(---+|\\*\\*\\*+|___+)$/.test(line.trim());\n if (heading) {\n closeParagraph();\n closeLists();\n const level = heading[1].length;\n output.push(\"<h\" + level + \">\" + markdownInline(heading[2]) + \"</h\" + level + \">\");\n } else if (quote) {\n closeParagraph();\n closeLists();\n const quoted = [];\n while (index < lines.length) {\n const next = /^>\\s?(.*)$/.exec(lines[index]);\n if (!next) {\n break;\n }\n quoted.push(next[1]);\n index += 1;\n }\n index -= 1;\n output.push(\"<blockquote>\" + markdownHtml(quoted.join(\"\\n\")) + \"</blockquote>\");\n } else if (item) {\n closeParagraph();\n renderListItem(item);\n } else if (rule) {\n closeParagraph();\n closeLists();\n output.push(\"<hr>\");\n } else if (line.trim()) {\n closeLists();\n paragraph.push(line.trim());\n } else {\n closeParagraph();\n closeLists();\n }\n }\n closeParagraph();\n closeLists();\n return output.join(\"\");\n }\n\n function render() {\n const waiting = status === \"needs_decision\";\n const plan =\n waiting && decision && decision.kind === \"plan\" ? decision : null;\n const permission =\n waiting && decision && decision.kind === \"tool_permission\"\n ? decision\n : null;\n const choice = waiting\n ? (decision &&\n decision.kind === \"multi_choice\" &&\n decision) ||\n (permission && {\n options: [\"Approve\", \"Decline\"],\n question: permission.question,\n })\n : null;\n\n element(\"heading\").textContent = plan\n ? \"Superblocks proposed a plan\"\n : permission\n ? \"Superblocks needs your approval\"\n : choice\n ? \"Superblocks has a question\"\n : waiting\n ? \"Superblocks is waiting on you\"\n : \"Superblocks\";\n element(\"appId\").textContent = appName || applicationId;\n // Superblocks narrates its whole working session into the reply. Once\n // there is a plan or a question, that narration is preamble in front\n // of the thing being asked, so it is dropped: the ask is the ask. It\n // survives only when there is nothing structured, where the reply is\n // itself the whole question and hiding it leaves a blank card.\n const narrationOnly = Boolean(plan || choice);\n element(\"reply\").textContent = narrationOnly ? \"\" : reply;\n show(\"reply\", Boolean(reply) && !narrationOnly);\n element(\"plan\").innerHTML = plan\n ? markdownHtml(planMarkup || plan.plan)\n : \"\";\n show(\"plan\", Boolean(plan));\n element(\"question\").innerHTML = choice\n ? markdownHtml(choice.question)\n : \"\";\n show(\"question\", Boolean(choice));\n show(\"planActions\", Boolean(plan));\n renderOptions(choice ? choice.options : []);\n // A turn can stop waiting on the user having streamed its plan or\n // question as chat text, leaving nothing structured to lay out. The\n // reply is then the whole ask, so free text is the only honest form.\n show(\"refineBox\", plan ? refining : waiting && !choice);\n setBusy(answered || sending);\n if (!answered && !sending) {\n note(waiting ? \"\" : waitingNote());\n }\n reportSize();\n }\n\n function applyPayload(payload) {\n let value = payload;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch (error) {\n return;\n }\n }\n if (!value || typeof value !== \"object\") {\n return;\n }\n if (Array.isArray(value.content)) {\n applyContent(value.content);\n if (value.structuredContent) {\n applyPayload(value.structuredContent);\n }\n return;\n }\n if (value.result && typeof value.result === \"object\") {\n applyPayload(value.result);\n return;\n }\n if (value.kind === \"ok\" && value.value) {\n value = value.value;\n }\n if (typeof value.applicationId === \"string\") {\n applicationId = value.applicationId;\n branch = typeof value.branch === \"string\" ? value.branch : undefined;\n }\n if (typeof value.name === \"string\") {\n appName = value.name;\n }\n if (typeof value.reply === \"string\") {\n reply = value.reply;\n }\n if (typeof value.status === \"string\") {\n status = value.status;\n }\n if (\"decision\" in value) {\n decision =\n value.decision && typeof value.decision === \"object\"\n ? value.decision\n : null;\n }\n render();\n }\n\n function applyContent(content) {\n for (const part of content || []) {\n if (part && part.type === \"text\" && typeof part.text === \"string\") {\n applyPayload(part.text);\n }\n }\n }\n\n function applyResultMetadata(value) {\n const metadata = value && typeof value === \"object\" ? value._meta : null;\n if (\n metadata &&\n typeof metadata[\"superblocks/plan-approval-token\"] === \"string\"\n ) {\n approvalToken = metadata[\"superblocks/plan-approval-token\"];\n }\n if (metadata && typeof metadata[\"superblocks/plan-markup\"] === \"string\") {\n planMarkup = metadata[\"superblocks/plan-markup\"];\n }\n }\n\n function handleHostMessage(event) {\n // App documents are opaque-origin, so the host window is the only\n // identity available to authenticate a message against.\n if (event.source !== window.parent) {\n return;\n }\n const data = event.data;\n if (!data || typeof data !== \"object\") {\n return;\n }\n if (data.method === \"ping\") {\n post({ jsonrpc: \"2.0\", id: data.id, result: {} });\n return;\n }\n if (data.id != null && pending.has(data.id)) {\n const waiter = pending.get(data.id);\n pending.delete(data.id);\n if (data.error) {\n waiter.reject(data.error);\n } else {\n waiter.resolve(data.result);\n }\n return;\n }\n const method = data.method;\n const params = data.params || {};\n if (\n method === \"ui/notifications/tool-result\" ||\n method === \"ui/tool-result\" ||\n method === \"tools/call\"\n ) {\n approvalToken = \"\";\n planMarkup = \"\";\n applyResultMetadata(params);\n applyResultMetadata(params.result);\n applyContent(params.content || (params.result || {}).content || []);\n if (params.structuredContent) {\n applyPayload(params.structuredContent);\n }\n }\n if (method === \"ui/notifications/tool-input\" && params.arguments) {\n applyPayload(params.arguments);\n }\n if (method === \"ui/notifications/host-context-changed\") {\n applyHostContext(params);\n }\n }\n\n window.addEventListener(\"message\", handleHostMessage);\n\n element(\"approve\").addEventListener(\"click\", approve);\n element(\"refine\").addEventListener(\"click\", () => {\n refining = true;\n element(\"approve\").className = \"button-secondary\";\n show(\"refineBox\", true);\n element(\"feedback\").focus();\n reportSize();\n });\n element(\"sendRefine\").addEventListener(\"click\", () => {\n const feedback = (element(\"feedback\").value || \"\").trim();\n if (!feedback) {\n return;\n }\n answer(\n decision && decision.kind === \"plan\"\n ? \"Change the Superblocks plan: \" + feedback\n : feedback,\n );\n });\n\n if (typeof ResizeObserver === \"function\") {\n new ResizeObserver(reportSize).observe(document.documentElement);\n }\n\n void request(\"ui/initialize\", {\n appCapabilities: { availableDisplayModes: [\"inline\"] },\n appInfo: { name: \"gateway-decision\", version: \"0.0.1\" },\n protocolVersion: PROTOCOL_VERSION,\n })\n .then((result) => {\n // Hosts hold back tool results until the app confirms it is ready.\n notify(\"ui/notifications/initialized\", {});\n applyHostContext(result && result.hostContext);\n if (result && result.toolResult) {\n approvalToken = \"\";\n planMarkup = \"\";\n applyResultMetadata(result.toolResult);\n applyPayload(result.toolResult);\n }\n })\n .catch(() => {\n // Hosts that push tool results via notification still work.\n })\n .finally(reportSize);\n })();\n </script>\n</body>\n</html>\n";
|
|
@@ -22,8 +22,10 @@ import { MCP_APP_BRAND_CSS } from "./mcp-app-brand-css.js";
|
|
|
22
22
|
* resource URI when the HTML changes so Claude Desktop refreshes its cache.
|
|
23
23
|
*/
|
|
24
24
|
export const PLAN_APPROVAL_TOKEN_META_KEY = "superblocks/plan-approval-token";
|
|
25
|
+
/** The plan with its `@Name[id=...]` mentions, kept out of model-visible fields. */
|
|
26
|
+
export const PLAN_MARKUP_META_KEY = "superblocks/plan-markup";
|
|
25
27
|
export const CONFIRM_PLAN_APPROVAL_TOOL = "confirm_plan_approval";
|
|
26
|
-
export const APP_DECISION_RESOURCE_URI = "ui://gateway/decision-
|
|
28
|
+
export const APP_DECISION_RESOURCE_URI = "ui://gateway/decision-v26.html";
|
|
27
29
|
export const APP_DECISION_HTML = `<!DOCTYPE html>
|
|
28
30
|
<html lang="en">
|
|
29
31
|
<head>
|
|
@@ -218,6 +220,7 @@ ${MCP_APP_BRAND_CSS}
|
|
|
218
220
|
let nextId = 1;
|
|
219
221
|
const pending = new Map();
|
|
220
222
|
let applicationId = "";
|
|
223
|
+
let branch;
|
|
221
224
|
let appName = "";
|
|
222
225
|
let reply = "";
|
|
223
226
|
let decision = null;
|
|
@@ -226,6 +229,8 @@ ${MCP_APP_BRAND_CSS}
|
|
|
226
229
|
let sending = false;
|
|
227
230
|
let answered = false;
|
|
228
231
|
let approvalToken = "";
|
|
232
|
+
let planMarkup = "";
|
|
233
|
+
let confirmedApprovalToken = "";
|
|
229
234
|
let optionButtons = [];
|
|
230
235
|
let lastHeight = 0;
|
|
231
236
|
|
|
@@ -319,8 +324,7 @@ ${MCP_APP_BRAND_CSS}
|
|
|
319
324
|
/**
|
|
320
325
|
* Posts the answer as the user's own turn.
|
|
321
326
|
*
|
|
322
|
-
*
|
|
323
|
-
* agent never learns about, so nothing polls it and nothing is relayed.
|
|
327
|
+
* The host must receive a user turn so it continues following the build.
|
|
324
328
|
*/
|
|
325
329
|
function answer(text) {
|
|
326
330
|
if (!text || sending || answered) {
|
|
@@ -366,7 +370,31 @@ ${MCP_APP_BRAND_CSS}
|
|
|
366
370
|
);
|
|
367
371
|
}
|
|
368
372
|
|
|
369
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Confirms the user's approval of the current challenge, once per token:
|
|
375
|
+
* a confirmed challenge stays confirmed while a send that never went out
|
|
376
|
+
* is retried, and confirming it again is refused.
|
|
377
|
+
*/
|
|
378
|
+
async function confirmApproval() {
|
|
379
|
+
if (!approvalToken) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
if (confirmedApprovalToken === approvalToken) {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
const token = approvalToken;
|
|
386
|
+
const confirmation = await request("tools/call", {
|
|
387
|
+
arguments: { approvalToken: token },
|
|
388
|
+
name: "${CONFIRM_PLAN_APPROVAL_TOOL}",
|
|
389
|
+
});
|
|
390
|
+
if (confirmation && confirmation.isError) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
confirmedApprovalToken = token;
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function approve() {
|
|
370
398
|
if (sending || answered) {
|
|
371
399
|
return;
|
|
372
400
|
}
|
|
@@ -377,25 +405,26 @@ ${MCP_APP_BRAND_CSS}
|
|
|
377
405
|
sending = true;
|
|
378
406
|
setBusy(true);
|
|
379
407
|
note("Confirming your approval...");
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
})
|
|
392
|
-
.catch(approvalRefused);
|
|
408
|
+
try {
|
|
409
|
+
const confirmed = await confirmApproval();
|
|
410
|
+
sending = false;
|
|
411
|
+
if (!confirmed) {
|
|
412
|
+
approvalRefused();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
answer("Build it — approve the Superblocks plan as written.");
|
|
416
|
+
} catch {
|
|
417
|
+
approvalRefused();
|
|
418
|
+
}
|
|
393
419
|
}
|
|
394
420
|
|
|
395
421
|
function renderOptions(options) {
|
|
396
422
|
const host = element("options");
|
|
397
423
|
host.replaceChildren();
|
|
398
424
|
optionButtons = [];
|
|
425
|
+
const permissionMessageId = decision && decision.kind === "tool_permission"
|
|
426
|
+
? decision.messageId
|
|
427
|
+
: undefined;
|
|
399
428
|
options.forEach((option, index) => {
|
|
400
429
|
const button = document.createElement("button");
|
|
401
430
|
button.id = "option-" + index;
|
|
@@ -404,13 +433,47 @@ ${MCP_APP_BRAND_CSS}
|
|
|
404
433
|
button.innerHTML = markdownInline(option, false);
|
|
405
434
|
// Verbatim: an option Superblocks did not offer reaches it as a
|
|
406
435
|
// prompt answering a question it never asked.
|
|
407
|
-
button.addEventListener("click", () =>
|
|
436
|
+
button.addEventListener("click", () => permissionMessageId
|
|
437
|
+
? answerPermission(option, permissionMessageId)
|
|
438
|
+
: answer(option));
|
|
408
439
|
host.appendChild(button);
|
|
409
440
|
optionButtons.push(button);
|
|
410
441
|
});
|
|
411
442
|
show("options", options.length > 0);
|
|
412
443
|
}
|
|
413
444
|
|
|
445
|
+
async function answerPermission(text, permissionMessageId) {
|
|
446
|
+
if (sending || answered) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
sending = true;
|
|
450
|
+
setBusy(true);
|
|
451
|
+
note("Submitting your response...");
|
|
452
|
+
const refused = () => {
|
|
453
|
+
sending = false;
|
|
454
|
+
setBusy(false);
|
|
455
|
+
note("This request could not be submitted. Ask the chat to show the current approval request again.");
|
|
456
|
+
};
|
|
457
|
+
try {
|
|
458
|
+
if (text === "Approve" && !(await confirmApproval())) {
|
|
459
|
+
refused();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const result = await request("tools/call", {
|
|
463
|
+
name: "edit_app",
|
|
464
|
+
arguments: { applicationId, ...(branch ? { branch } : {}), permissionMessageId, prompt: text },
|
|
465
|
+
});
|
|
466
|
+
if (result?.isError || result?.structuredContent?.status !== "building") {
|
|
467
|
+
refused();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
sending = false;
|
|
471
|
+
answer("My response to the approval request is queued. Please check the build's progress to confirm it was accepted.");
|
|
472
|
+
} catch {
|
|
473
|
+
refused();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
414
477
|
function waitingNote() {
|
|
415
478
|
if (status === "building") {
|
|
416
479
|
return "Superblocks is still building — nothing to answer yet.";
|
|
@@ -671,13 +734,24 @@ ${MCP_APP_BRAND_CSS}
|
|
|
671
734
|
const waiting = status === "needs_decision";
|
|
672
735
|
const plan =
|
|
673
736
|
waiting && decision && decision.kind === "plan" ? decision : null;
|
|
674
|
-
const
|
|
675
|
-
waiting && decision && decision.kind === "
|
|
737
|
+
const permission =
|
|
738
|
+
waiting && decision && decision.kind === "tool_permission"
|
|
676
739
|
? decision
|
|
677
740
|
: null;
|
|
741
|
+
const choice = waiting
|
|
742
|
+
? (decision &&
|
|
743
|
+
decision.kind === "multi_choice" &&
|
|
744
|
+
decision) ||
|
|
745
|
+
(permission && {
|
|
746
|
+
options: ["Approve", "Decline"],
|
|
747
|
+
question: permission.question,
|
|
748
|
+
})
|
|
749
|
+
: null;
|
|
678
750
|
|
|
679
751
|
element("heading").textContent = plan
|
|
680
752
|
? "Superblocks proposed a plan"
|
|
753
|
+
: permission
|
|
754
|
+
? "Superblocks needs your approval"
|
|
681
755
|
: choice
|
|
682
756
|
? "Superblocks has a question"
|
|
683
757
|
: waiting
|
|
@@ -692,7 +766,9 @@ ${MCP_APP_BRAND_CSS}
|
|
|
692
766
|
const narrationOnly = Boolean(plan || choice);
|
|
693
767
|
element("reply").textContent = narrationOnly ? "" : reply;
|
|
694
768
|
show("reply", Boolean(reply) && !narrationOnly);
|
|
695
|
-
element("plan").innerHTML = plan
|
|
769
|
+
element("plan").innerHTML = plan
|
|
770
|
+
? markdownHtml(planMarkup || plan.plan)
|
|
771
|
+
: "";
|
|
696
772
|
show("plan", Boolean(plan));
|
|
697
773
|
element("question").innerHTML = choice
|
|
698
774
|
? markdownHtml(choice.question)
|
|
@@ -739,6 +815,7 @@ ${MCP_APP_BRAND_CSS}
|
|
|
739
815
|
}
|
|
740
816
|
if (typeof value.applicationId === "string") {
|
|
741
817
|
applicationId = value.applicationId;
|
|
818
|
+
branch = typeof value.branch === "string" ? value.branch : undefined;
|
|
742
819
|
}
|
|
743
820
|
if (typeof value.name === "string") {
|
|
744
821
|
appName = value.name;
|
|
@@ -766,7 +843,7 @@ ${MCP_APP_BRAND_CSS}
|
|
|
766
843
|
}
|
|
767
844
|
}
|
|
768
845
|
|
|
769
|
-
function
|
|
846
|
+
function applyResultMetadata(value) {
|
|
770
847
|
const metadata = value && typeof value === "object" ? value._meta : null;
|
|
771
848
|
if (
|
|
772
849
|
metadata &&
|
|
@@ -774,6 +851,9 @@ ${MCP_APP_BRAND_CSS}
|
|
|
774
851
|
) {
|
|
775
852
|
approvalToken = metadata["${PLAN_APPROVAL_TOKEN_META_KEY}"];
|
|
776
853
|
}
|
|
854
|
+
if (metadata && typeof metadata["${PLAN_MARKUP_META_KEY}"] === "string") {
|
|
855
|
+
planMarkup = metadata["${PLAN_MARKUP_META_KEY}"];
|
|
856
|
+
}
|
|
777
857
|
}
|
|
778
858
|
|
|
779
859
|
function handleHostMessage(event) {
|
|
@@ -808,8 +888,9 @@ ${MCP_APP_BRAND_CSS}
|
|
|
808
888
|
method === "tools/call"
|
|
809
889
|
) {
|
|
810
890
|
approvalToken = "";
|
|
811
|
-
|
|
812
|
-
|
|
891
|
+
planMarkup = "";
|
|
892
|
+
applyResultMetadata(params);
|
|
893
|
+
applyResultMetadata(params.result);
|
|
813
894
|
applyContent(params.content || (params.result || {}).content || []);
|
|
814
895
|
if (params.structuredContent) {
|
|
815
896
|
applyPayload(params.structuredContent);
|
|
@@ -860,7 +941,8 @@ ${MCP_APP_BRAND_CSS}
|
|
|
860
941
|
applyHostContext(result && result.hostContext);
|
|
861
942
|
if (result && result.toolResult) {
|
|
862
943
|
approvalToken = "";
|
|
863
|
-
|
|
944
|
+
planMarkup = "";
|
|
945
|
+
applyResultMetadata(result.toolResult);
|
|
864
946
|
applyPayload(result.toolResult);
|
|
865
947
|
}
|
|
866
948
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-card-html.js","sourceRoot":"","sources":["../../../src/transports/mcp/decision-card-html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,iCAAiC,CAAC;AAC9E,MAAM,CAAC,MAAM,0BAA0B,GAAG,uBAAuB,CAAC;AAClE,MAAM,CAAC,MAAM,yBAAyB,GAAG,gCAAgC,CAAC;AAE1E,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuK/B,iBAAiB
|
|
1
|
+
{"version":3,"file":"decision-card-html.js","sourceRoot":"","sources":["../../../src/transports/mcp/decision-card-html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,iCAAiC,CAAC;AAC9E,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,yBAAyB,CAAC;AAC9D,MAAM,CAAC,MAAM,0BAA0B,GAAG,uBAAuB,CAAC;AAClE,MAAM,CAAC,MAAM,yBAAyB,GAAG,gCAAgC,CAAC;AAE1E,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuK/B,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAgMA,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA8chB,4BAA4B;;sCAEnB,4BAA4B;;2CAEvB,oBAAoB;mCAC5B,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsGtD,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP form elicitation for Superblocks decisions (plan approve/refine
|
|
3
|
-
* multi_choice questions).
|
|
2
|
+
* MCP form elicitation for Superblocks decisions (plan approve/refine,
|
|
3
|
+
* multi_choice questions, and tool-permission approvals).
|
|
4
4
|
*
|
|
5
5
|
* Chat is the baseline: every decision is also returned as needs_decision +
|
|
6
6
|
* decision so a host without elicitation can ask the user in its own words. A
|
|
@@ -12,6 +12,7 @@ import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
|
12
12
|
import type { CapabilityResult, DecisionPayload } from "../../capabilities/types.js";
|
|
13
13
|
export type DecisionAnswer = {
|
|
14
14
|
kind: "answer";
|
|
15
|
+
permissionMessageId?: string;
|
|
15
16
|
prompt: string;
|
|
16
17
|
} | {
|
|
17
18
|
kind: "approve";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP form elicitation for Superblocks decisions (plan approve/refine
|
|
3
|
-
* multi_choice questions).
|
|
2
|
+
* MCP form elicitation for Superblocks decisions (plan approve/refine,
|
|
3
|
+
* multi_choice questions, and tool-permission approvals).
|
|
4
4
|
*
|
|
5
5
|
* Chat is the baseline: every decision is also returned as needs_decision +
|
|
6
6
|
* decision so a host without elicitation can ask the user in its own words. A
|
|
@@ -128,6 +128,36 @@ export function decisionElicitationRequest(decision) {
|
|
|
128
128
|
},
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
+
if (decision.kind === "tool_permission") {
|
|
132
|
+
gatewayDebug("decision request created", {
|
|
133
|
+
"decision.kind": decision.kind,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
message: decision.question,
|
|
137
|
+
mode: "form",
|
|
138
|
+
requestedSchema: {
|
|
139
|
+
properties: {
|
|
140
|
+
action: {
|
|
141
|
+
description: "Allow this action, or decline it",
|
|
142
|
+
enum: ["approve", "decline"],
|
|
143
|
+
enumNames: [
|
|
144
|
+
"Approve: allow this action",
|
|
145
|
+
"Decline: do not allow this action",
|
|
146
|
+
],
|
|
147
|
+
title: "Approval",
|
|
148
|
+
type: "string",
|
|
149
|
+
},
|
|
150
|
+
feedback: {
|
|
151
|
+
description: "Optional explanation to pass back with a declined response.",
|
|
152
|
+
title: "Reason",
|
|
153
|
+
type: "string",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
required: ["action"],
|
|
157
|
+
type: "object",
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
131
161
|
if (decision.options.length === 0) {
|
|
132
162
|
gatewayDebug("decision request skipped", {
|
|
133
163
|
"decision.kind": decision.kind,
|
|
@@ -199,6 +229,41 @@ export function readDecisionAnswer(decision, outcome) {
|
|
|
199
229
|
});
|
|
200
230
|
return feedback ? { feedback, kind: "refine" } : undefined;
|
|
201
231
|
}
|
|
232
|
+
if (decision.kind === "tool_permission") {
|
|
233
|
+
const action = outcome.content.action;
|
|
234
|
+
if (action === "approve") {
|
|
235
|
+
gatewayDebug("decision answer read", {
|
|
236
|
+
"decision.answer_kind": "answer",
|
|
237
|
+
"decision.kind": decision.kind,
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
kind: "answer",
|
|
241
|
+
permissionMessageId: decision.messageId,
|
|
242
|
+
prompt: "Approve",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (action === "decline") {
|
|
246
|
+
const feedback = typeof outcome.content.feedback === "string"
|
|
247
|
+
? outcome.content.feedback.trim()
|
|
248
|
+
: "";
|
|
249
|
+
gatewayDebug("decision answer read", {
|
|
250
|
+
"decision.answer_kind": "answer",
|
|
251
|
+
"decision.kind": decision.kind,
|
|
252
|
+
...(feedback ? { "decision.feedback": true } : {}),
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
kind: "answer",
|
|
256
|
+
permissionMessageId: decision.messageId,
|
|
257
|
+
prompt: feedback ? `Decline: ${feedback}` : "Decline",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
gatewayDebug("decision answer read", {
|
|
261
|
+
"decision.answer_kind": "none",
|
|
262
|
+
"decision.kind": decision.kind,
|
|
263
|
+
"decision.reason": "invalid_tool_permission_action",
|
|
264
|
+
});
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
202
267
|
const choice = outcome.content.choice;
|
|
203
268
|
// Only an option Superblocks offered: a free-typed answer would reach Clark
|
|
204
269
|
// as a prompt for a question it did not ask.
|