@reqvire-org/ads 0.3.3 → 0.3.5
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 +12 -4
- package/dist/behaviors.mjs +157 -0
- package/dist/bootstrap-ds/ADS.md +7 -0
- package/dist/bootstrap-ds/design-system/controllers/.gitkeep +0 -0
- package/dist/check.mjs +7 -2
- package/dist/cli.mjs +17 -0
- package/dist/config.mjs +19 -3
- package/dist/css-values.mjs +26 -0
- package/dist/index.mjs +2 -0
- package/dist/init.mjs +1 -1
- package/dist/mcp/build-status.mjs +10 -3
- package/dist/mcp/catalog.mjs +11 -8
- package/dist/mcp/context.mjs +2 -2
- package/dist/mcp/mutations.mjs +10 -0
- package/dist/mcp/preview-owner-guard.mjs +29 -0
- package/dist/mcp/preview-watch.mjs +28 -4
- package/dist/mcp/preview-widget.bundle.js +2 -2
- package/dist/mcp/prompts.mjs +2 -2
- package/dist/mcp/server.mjs +56 -19
- package/dist/mcp/showcase-host.mjs +9 -5
- package/dist/public-api.mjs +39 -0
- package/dist/scenarios-compiler.mjs +17 -2
- package/dist/scenarios.mjs +27 -39
- package/dist/skills/README.md +3 -3
- package/dist/skills/ads-break-ui/SKILL.md +22 -0
- package/dist/skills/ads-designer/SKILL.md +23 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +7 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/controllers/.gitkeep +0 -0
- package/dist/skills/ads-designer/references/cli.md +1 -1
- package/dist/skills/ads-designer/references/design-craft.md +1 -1
- package/dist/skills/ads-import/SKILL.md +22 -0
- package/dist/skills/ads-import/references/cli.md +1 -1
- package/dist/skills/{ads-assimilate → ads-migrate}/SKILL.md +28 -6
- package/dist/skills/ads-migrate/agents/openai.yaml +4 -0
- package/dist/skills/{ads-assimilate → ads-migrate}/references/cli.md +1 -1
- package/dist/skills/{ads-assimilate → ads-migrate}/references/native-implementation.md +11 -0
- package/dist/skills/ads-motion/SKILL.md +22 -0
- package/dist/tokens.mjs +3 -2
- package/dist/units.mjs +20 -9
- package/dist/verification/browser.mjs +27 -0
- package/dist/verification/playwright-reporter.cjs +20 -0
- package/dist/verification.mjs +214 -0
- package/docs/ADS.md +27 -2
- package/docs/ARCHITECTURE.md +39 -1
- package/docs/BEHAVIORS.md +230 -0
- package/docs/CHANGELOG.md +36 -0
- package/docs/GUIDE.md +16 -1
- package/docs/MCP.md +46 -7
- package/package.json +4 -2
- package/dist/skills/ads-assimilate/agents/openai.yaml +0 -4
- /package/dist/skills/{ads-assimilate → ads-migrate}/references/lint-and-docs.md +0 -0
- /package/dist/skills/{ads-assimilate → ads-migrate}/references/testing.md +0 -0
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { statSync, mkdirSync, writeFileSync, renameSync, rmSync, realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { buildFailure, inspectBuildStatus } from "./build-status.mjs";
|
|
6
7
|
|
|
7
8
|
function stamp(file) {
|
|
@@ -16,6 +17,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
16
17
|
let session = randomUUID();
|
|
17
18
|
let state = command ? "idle" : "disabled";
|
|
18
19
|
let child;
|
|
20
|
+
let ownerGuard;
|
|
19
21
|
let error;
|
|
20
22
|
let closed;
|
|
21
23
|
let childClosed = false;
|
|
@@ -23,6 +25,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
23
25
|
let stopping;
|
|
24
26
|
let restarting;
|
|
25
27
|
let disposed = false;
|
|
28
|
+
let stalled = false;
|
|
26
29
|
const baselines = new Map();
|
|
27
30
|
const ready = new Set();
|
|
28
31
|
const requests = new Set();
|
|
@@ -69,6 +72,23 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
69
72
|
log.write(`ads mcp: ${error}\n`);
|
|
70
73
|
return;
|
|
71
74
|
}
|
|
75
|
+
if (child.pid) {
|
|
76
|
+
// A small independent guard owns a pipe from this MCP process. It can
|
|
77
|
+
// terminate the detached compiler group even if this process is SIGKILLed.
|
|
78
|
+
const guard = ownerGuard = spawn(process.execPath, [fileURLToPath(new URL("./preview-owner-guard.mjs", import.meta.url)), String(child.pid)], {
|
|
79
|
+
stdio: ["pipe", "ignore", "pipe"], detached: process.platform !== "win32", windowsHide: true,
|
|
80
|
+
});
|
|
81
|
+
guard.stdin.on("error", () => {}); // Owner shutdown can race guard exit.
|
|
82
|
+
guard.stderr.pipe(log, { end: false });
|
|
83
|
+
function guardFailed(failure) {
|
|
84
|
+
if (ownerGuard !== guard || state === "stopping" || childClosed) return;
|
|
85
|
+
error = `Preview ownership guard failed: ${failure.message ?? failure}. Invoke the preview again after fixing the launcher.`;
|
|
86
|
+
state = "failed";
|
|
87
|
+
kill("SIGKILL");
|
|
88
|
+
}
|
|
89
|
+
guard.once("error", guardFailed);
|
|
90
|
+
guard.once("exit", (code, signal) => guardFailed(signal ?? code));
|
|
91
|
+
}
|
|
72
92
|
child.stdout.pipe(log, { end: false });
|
|
73
93
|
child.stderr.pipe(log, { end: false });
|
|
74
94
|
closed = new Promise((resolve) => child.once("close", (code, signal) => {
|
|
@@ -76,6 +96,8 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
76
96
|
// Finish cleanup now, before discarding the owned process-group ID.
|
|
77
97
|
kill("SIGKILL");
|
|
78
98
|
childClosed = true;
|
|
99
|
+
const guard = ownerGuard; ownerGuard = undefined;
|
|
100
|
+
guard?.stdin.end("disarm\n");
|
|
79
101
|
clearTimeout(killTimer);
|
|
80
102
|
if (state !== "stopping") {
|
|
81
103
|
state = "failed";
|
|
@@ -102,14 +124,14 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
102
124
|
requestFile = path.join(root, ".ds", `preview-requests-${session}.json`);
|
|
103
125
|
reportFile = path.join(root, ".ds", `build-status-${session}.json`);
|
|
104
126
|
state = "idle"; error = undefined; child = closed = stopping = undefined; childClosed = false;
|
|
105
|
-
ready.clear(); baselines.clear(); active = undefined;
|
|
127
|
+
ready.clear(); baselines.clear(); active = undefined; stalled = false;
|
|
106
128
|
start();
|
|
107
129
|
})().finally(() => { restarting = undefined; });
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
async function ensure(file, signal) {
|
|
111
133
|
if (signal?.aborted) throw new Error("Preview request cancelled.");
|
|
112
|
-
if (state === "failed" || restarting) await restart();
|
|
134
|
+
if (state === "failed" || restarting || stalled) await restart();
|
|
113
135
|
if (disposed) throw new Error("Preview watcher is stopped.");
|
|
114
136
|
start();
|
|
115
137
|
request(file);
|
|
@@ -132,10 +154,12 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
132
154
|
if (build?.state === "unreported" && current !== null && current !== baseline) { ready.add(file); return build; }
|
|
133
155
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
134
156
|
}
|
|
135
|
-
|
|
157
|
+
stalled = true;
|
|
158
|
+
const report = buildStatus();
|
|
159
|
+
throw Object.assign(new Error(`Preview build is still pending${report.progress ? ` in ${report.progress.phase} for ${Math.round((Date.now() - Date.parse(report.progress.updatedAt)) / 1000)}s` : ""}. Inspect ads://project/build and builder logs. Invoke the preview again to restart the timed-out builder within this MCP session.`), { build: { ...report, watcher: status(), code: "ADS_PREVIEW_PENDING" } });
|
|
136
160
|
}
|
|
137
161
|
|
|
138
|
-
const status = () => ({ state, ...(child?.pid ? { pid: child.pid } : {}), ...(error ? { error } : {}) });
|
|
162
|
+
const status = () => ({ state, ...(stalled ? { stalled: true } : {}), ...(child?.pid ? { pid: child.pid } : {}), ...(ownerGuard?.pid ? { guardPid: ownerGuard.pid } : {}), ...(error ? { error } : {}) });
|
|
139
163
|
const buildStatus = () => inspectBuildStatus(root, config, command ? { session, file: reportFile, processState: status() } : {});
|
|
140
164
|
|
|
141
165
|
async function stopChild() {
|
|
@@ -100,9 +100,9 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
100
100
|
- omitted: host decides border`)}),Bx=d.object({method:d.literal("ui/request-display-mode"),params:d.object({mode:da.describe("The display mode being requested.")})}),Dv=d.object({mode:da.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),Vv=d.union([d.literal("model"),d.literal("app")]).describe("Tool visibility scope - who can access the tool."),Wx=d.object({resourceUri:d.string().optional(),visibility:d.array(Vv).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
101
101
|
- "model": Tool visible to and callable by the agent
|
|
102
102
|
- "app": Tool callable by the app from this server only`),csp:d.never().optional(),permissions:d.never().optional()}),Gx=d.object({mimeTypes:d.array(d.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),Kx=d.object({method:d.literal("ui/download-file"),params:d.object({contents:d.array(d.union([go,So])).describe("Resource contents to download \u2014 embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),Yx=d.object({method:d.literal("ui/message"),params:d.object({role:d.literal("user").describe('Message role, currently only "user" is supported.'),content:d.array(Fe).describe("Message content blocks (text, image, etc.).")})}),Qx=d.object({method:d.literal("ui/notifications/sandbox-resource-ready"),params:d.object({html:d.string().describe("HTML content to load into the inner iframe."),sandbox:d.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:_f.optional().describe("CSP configuration from resource metadata."),permissions:yf.optional().describe("Sandbox permissions from resource metadata.")})}),Hv=d.object({method:d.literal("ui/notifications/tool-result"),params:Ot.describe("Standard MCP tool execution result.")}),x_=d.object({toolInfo:d.object({id:Ne.optional().describe("JSON-RPC id of the tools/call request."),tool:It.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:Tv.optional().describe("Current color theme preference."),styles:Av.optional().describe("Style configuration for theming the app."),displayMode:da.optional().describe("How the UI is currently displayed."),availableDisplayModes:d.array(da).optional().describe("Display modes the host supports."),containerDimensions:d.union([d.object({height:d.number().describe("Fixed container height in pixels.")}),d.object({maxHeight:d.union([d.number(),d.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(d.union([d.object({width:d.number().describe("Fixed container width in pixels.")}),d.object({maxWidth:d.union([d.number(),d.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
103
|
-
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d.string().optional().describe("User's timezone in IANA format."),userAgent:d.string().optional().describe("Host application identifier."),platform:d.union([d.literal("web"),d.literal("desktop"),d.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:d.object({touch:d.boolean().optional().describe("Whether the device supports touch input."),hover:d.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:d.object({top:d.number().describe("Top safe area inset in pixels."),right:d.number().describe("Right safe area inset in pixels."),bottom:d.number().describe("Bottom safe area inset in pixels."),left:d.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Fv=d.object({method:d.literal("ui/notifications/host-context-changed"),params:x_.describe("Partial context update containing only changed fields.")}),Xx=d.object({method:d.literal("ui/update-model-context"),params:d.object({content:d.array(Fe).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:d.record(d.string(),d.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),eP=d.object({method:d.literal("ui/initialize"),params:d.object({appInfo:Ve.describe("App identification (name and version)."),appCapabilities:Zv.describe("Features and capabilities this app provides."),protocolVersion:d.string().describe("Protocol version this app supports.")})}),Jv=d.object({protocolVersion:d.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ve.describe("Host application identification and version."),hostCapabilities:Uv.describe("Features and capabilities provided by the host."),hostContext:x_.describe("Rich context about the host environment.")}).passthrough(),Bv={target:"draft-2020-12"};async function R_(e,t){let o=e["~standard"];if(o.jsonSchema)return o.jsonSchema[t](Bv);throw Error(`Schema (vendor: ${o.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function w_(e,t,o=""){let r=await e["~standard"].validate(t);if(r.issues){let n=r.issues.map(s=>{let i=s.path?.map(c=>typeof c=="object"?c.key:c).join(".");return i?`${i}: ${s.message}`:s.message}).join("; ");throw Error(o+n)}return r.value}function Wv(e,t){let o={...e,...t};return t.experimental!==void 0&&(o.experimental={...e.experimental,...t.experimental}),t.tools!==void 0&&(o.tools={...e.tools,...t.tools}),o}var Jl=class e extends mf{_appInfo;_appCapabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_registeredEvents=new Set;_events=new ff;_methods=new gf;setRequestHandler=(...t)=>{let[o,...r]=z_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=z_("notification",t)??t;this._methods.claim(o,"setNotificationHandler"),super.setNotificationHandler.call(this,o,...r)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...o)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...o)};_assertInitialized(t){if(this._initializedSent)return;let o=`[ext-apps] App.${t}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(o);console.warn(`${o}. This will throw in a future release.`)}eventSchemas={toolinput:Ov,toolinputpartial:Nv,toolresult:Hv,toolcancelled:Mv,hostcontextchanged:Fv};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(t){if(!e.ONE_SHOT_EVENTS.has(t)||this._everHadListener.has(t)||(this._everHadListener.add(t),!this._initializedSent))return;let o=`[ext-apps] "${String(t)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(o);console.warn(o)}setEventHandler(t,o){o&&this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.setHandler(t,o)}addEventListener(t,o){this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.addEventListener(t,o)}removeEventListener(t,o){this._events.removeEventListener(t,o)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let o=this.eventSchemas[t];if(!o)throw Error(`Unknown event: ${String(t)}`);this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},r=>{let n=r;t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...n}),this._events.dispatch(t,n)}),this._registeredEvents.add(t)}getEventHandler(t){return this._events.getHandler(t)}warnIfRequestHandlerReplaced(t,o,r){o&&r&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,o={},r={autoResize:!0}){super(r),this._appInfo=t,this._appCapabilities=o,this.options=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),!r.allowUnsafeEval&&d.config({jitless:!0}),this.replaceRequestHandler("ping",n=>(console.log("Received ping:",n.params),{})),this.setEventHandler("hostcontextchanged",void 0)}buildContext(t){return t}registerAppCapabilities(t){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._appCapabilities=Wv(this._appCapabilities,t)}registerTool(t,o,r){if(this._registeredTools[t])throw Error(`Tool ${t} is already registered`);let n=this,s=()=>{n._initializedSent&&n._appCapabilities.tools?.listChanged&&n.sendToolListChanged()},i=o.inputSchema!==void 0,c={title:o.title,description:o.description,inputSchema:o.inputSchema,outputSchema:o.outputSchema,annotations:o.annotations,_meta:o._meta,enabled:!0,enable(){this.enabled=!0,s()},disable(){this.enabled=!1,s()},update(l){Object.assign(this,l),s()},remove(){n._registeredTools[t]===c&&(delete n._registeredTools[t],s())},handler:async(l,u)=>{if(!c.enabled)throw Error(`Tool ${t} is disabled`);let m;if(i){let S=c.inputSchema,_=S?await w_(S,l??{},`Invalid input for tool ${t}: `):l??{};m=await r(_,u)}else m=await r(u);return c.outputSchema&&!m.isError&&(m.structuredContent=await w_(c.outputSchema,m.structuredContent,`Invalid output for tool ${t}: `)),m}};return this._registeredTools[t]=c,!this._appCapabilities.tools&&!this.transport&&this.registerAppCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),s(),c}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(t,o)=>{let r=this._registeredTools[t.name];if(!r)throw Error(`Tool ${t.name} not found`);return r.handler(t.arguments,o)},this.onlisttools=async(t,o)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([r,n])=>n.enabled).map(async([r,n])=>{let s={name:r,title:n.title,description:n.description,inputSchema:n.inputSchema?await R_(n.inputSchema,"input"):{type:"object",properties:{}}};return n.outputSchema&&(s.outputSchema=await R_(n.outputSchema,"output")),n.annotations&&(s.annotations=n.annotations),n._meta&&(s._meta=n._meta),s}))}))}async sendToolListChanged(t={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:t})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(t){this.setEventHandler("toolinput",t)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(t){this.setEventHandler("toolinputpartial",t)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(t){this.setEventHandler("toolresult",t)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(t){this.setEventHandler("toolcancelled",t)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(t){this.setEventHandler("hostcontextchanged",t)}_onteardown;get onteardown(){return this._onteardown}set onteardown(t){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,t),this._onteardown=t,this.replaceRequestHandler("ui/resource-teardown",{params:jv.shape.params,result:Lv},(o,r)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(t){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,t),this._onlisttools=t,this.replaceRequestHandler("tools/list",(o,r)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(o.params,r)})}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${t})`);break}}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._appCapabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;default:return}}assertNotificationCapability(t){}async callServerTool(t,o){if(this._assertInitialized("callServerTool"),typeof t=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${t}"). Did you mean: callServerTool({ name: "${t}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:t},{onprogress:()=>{},resetTimeoutOnProgress:!0,...o})}async readServerResource(t,o){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:t},o)}async listServerResources(t,o){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:t},o)}async createSamplingMessage(t,o){return this._assertInitialized("createSamplingMessage"),await this.request({method:"sampling/createMessage",params:t},o)}sendMessage(t,o){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:t},Iv,o)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,o){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:t},Ct,o)}openLink(t,o){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:t},$v,o)}sendOpenLink=this.openLink;downloadFile(t,o){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:t},Cv,o)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,o){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:t},Dv,o)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,o=0,r=0,n=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let i=document.documentElement,c=i.style.height;i.style.height="max-content";let l=Math.ceil(i.getBoundingClientRect().height);i.style.height=c;let u=Math.ceil(window.innerWidth);(u!==o||l!==r)&&(o=u,r=l,this.sendSizeChanged({width:u,height:l}))}))};n();let s=new ResizeObserver(n);return s.observe(document.documentElement),s.observe(document.body),()=>s.disconnect()}async connect(t=new Sf(window.parent,window.parent),o){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1;try{await super.connect(t);let r=await this.request({method:"ui/initialize",params:{appCapabilities:this._appCapabilities,appInfo:this._appInfo,protocolVersion:xv}},Jv,o);if(r===void 0)throw Error(`Server sent invalid initialize result: ${r}`);this._hostCapabilities=r.hostCapabilities,this._hostInfo=r.hostInfo,this._hostContext=r.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(r){throw this.close(),r}}};var Bl=class{constructor(t){this.read=t,this.records=new Map,this.bytes=new Map,this.urls=new Map,this.styles=new Map,this.styleReady=new Map,this.revisions=new Set,this.importing=Promise.resolve(),this.generation=0,this.closed=!1,globalThis.__dsPreviewImport=o=>this.importModule(o)}register(t){if(t.schemaVersion!==1||!Array.isArray(t.scenarios)||!t.assets||typeof t.revision!="string")throw new Error("Unsupported preview manifest. Rebuild previews with the installed ADS version.");if(this.revisions.add(t.revision),this.revisions.size>20)throw Object.assign(new Error("Reopen the preview to release modules retained across 20 build revisions."),{code:"ADS_PREVIEW_REOPEN"});for(let[o,r]of Object.entries(t.assets)){if(!/^ads:\/\/assets\/[a-f0-9]{64}\.[a-z0-9]+$/.test(o)||!/^sha256:[a-f0-9]{64}$/.test(r.digest)||!["imports","dynamicImports","styles","references"].every(n=>Array.isArray(r[n])&&r[n].every(s=>Object.hasOwn(t.assets,s))))throw new Error("Invalid preview dependency graph.");if(this.records.has(o)&&this.records.get(o).digest!==r.digest)throw new Error("An immutable preview resource changed its content.");this.records.set(o,r)}}async resource(t){if(!this.bytes.has(t)){let o=(async()=>{let r=this.records.get(t);if(!r)throw new Error(`Unknown preview dependency: ${t}`);let n=await this.read(t);if(this.closed)throw new Error("The preview App was closed.");let s=n.contents.find(u=>u.uri===t);if(!s)throw new Error(`Missing preview dependency: ${t}`);let i=typeof s.text=="string"?new TextEncoder().encode(s.text):Uint8Array.from(atob(s.blob),u=>u.charCodeAt(0)),c=await crypto.subtle.digest("SHA-256",i),l="sha256:"+[...new Uint8Array(c)].map(u=>u.toString(16).padStart(2,"0")).join("");if(i.length!==r.size||l!==r.digest)throw new Error("Preview resource integrity check failed. Rebuild and reopen the preview.");return i})();this.bytes.set(t,o),o.catch(()=>this.bytes.delete(t))}return this.bytes.get(t)}closure(t){let o=[],r=new Set,n=s=>{if(r.has(s))return;r.add(s);let i=this.records.get(s);if(!i)throw new Error(`Missing preview dependency metadata: ${s}`);for(let c of[...i.imports,...i.styles,...i.references])n(c);o.push(s)};return n(t),o}async materialize(t){let o=this.closure(t);await Promise.all(o.map(n=>this.resource(n)));let r={};for(let n of o){if(this.closed)throw new Error("The preview App was closed.");if(this.urls.has(n))continue;let s=this.records.get(n),i=await this.resource(n);if(s.kind==="module"||s.mimeType==="text/css"){let l=new TextDecoder().decode(i);for(let u of s.references){let m=this.urls.get(u);if(!m)throw new Error("Unsupported cyclic asset reference.");l=l.replaceAll(u,m)}i=l}let c=URL.createObjectURL(new Blob([i],{type:s.mimeType}));this.urls.set(n,c),s.kind==="module"&&(r[n]=c)}if(Object.keys(r).length){let n=document.createElement("script");n.type="importmap",n.textContent=JSON.stringify({imports:r}),document.head.append(n)}return o}async importModule(t){let o=this.importing.then(()=>this.materialize(t));this.importing=o.catch(()=>{});let r=await o;for(let n of r){if(this.records.get(n).mimeType!=="text/css")continue;if(this.styles.has(n)){await this.styleReady.get(n);continue}let i=document.createElement("link");i.rel="stylesheet",i.href=this.urls.get(n),this.styles.set(n,i);let c=new Promise((l,u)=>{i.onload=l,i.onerror=()=>u(new Error("The host CSP blocked a preview stylesheet."))});this.styleReady.set(n,c),document.head.append(i);try{await c}catch(l){throw i.remove(),this.styles.delete(n),this.styleReady.delete(n),l}}return import(t)}async mount(t,o,r){let n=++this.generation;this.register(t);let s=t.scenarios.find(l=>l.id===o);if(!s)throw new Error("The selected scenario is no longer registered.");let i=await this.importModule(s.module);if(n!==this.generation||(await this.unmount?.(),n!==this.generation||this.closed))return!1;r.replaceChildren();let c=new Set(this.closure(s.module));for(let[l,u]of this.styles)c.has(l)||(u.remove(),this.styles.delete(l),this.styleReady.delete(l));for(let l of this.closure(s.module))this.styles.has(l)&&document.head.append(this.styles.get(l));if(typeof i.mount!="function")throw new Error("The compiled scenario has no mount adapter.");if(this.unmount=i.mount(r),typeof this.unmount!="function")throw new Error("The scenario mount adapter must return an unmount callback.");return!0}async dispose(){if(!this.closed){this.closed=!0,++this.generation,await this.unmount?.();for(let t of this.styles.values())t.remove();for(let t of this.urls.values())URL.revokeObjectURL(t);this.styles.clear(),this.styleReady.clear(),this.urls.clear(),this.bytes.clear(),this.records.clear(),delete globalThis.__dsPreviewImport}}};function P_(e){return Array.isArray(e)&&e.length>0&&e.every(t=>t&&typeof t=="object"&&typeof t.token=="string"&&/^--[\w-]+$/.test(t.token)&&typeof t.property=="string"&&/^-?[a-z][a-z0-9-]*$/.test(t.property)&&typeof t.probe=="string"&&t.probe.trim().length>0)&&new Set(e.map(t=>`${t.token}:${t.property}`)).size===e.length}function T_(e,t){let o=t.getComputedStyle(e),r=e.getBoundingClientRect();if(o.display==="none"||o.visibility!=="visible"||!e.getClientRects().length||!(r.width||r.height))return!1;for(let n=e;n;n=n.parentElement)if(Number(t.getComputedStyle(n).opacity)===0)return!1;return!0}function k_(e,t){if(!P_(t))return{ok:!1,diagnostics:["Token preview needs explicit { token, property, probe } samples from the project fixture. Rebuild with the current compiler."]};let o=e.ownerDocument,r=o.defaultView,n=o.documentElement,s=[],i=[],c=[...e.querySelectorAll("[data-ads-token], [data-ads-property]")],l=new Set(t.map(u=>`${u.token}:${u.property}`));for(let u of c)l.has(`${u.dataset.adsToken}:${u.dataset.adsProperty}`)||s.push(`Undeclared rendered token sample ${u.dataset.adsToken}:${u.dataset.adsProperty}.`);for(let u of t){if(!r.CSS.supports(u.property,u.probe)){s.push(`${u.token}: the fixture probe is invalid for ${u.property}.`);continue}let m=r.getComputedStyle(n).getPropertyValue(u.token).trim();if(!m||!r.CSS.supports(u.property,m)){s.push(`${u.token}: its current value is invalid for the fixture's ${u.property} binding.`);continue}let S=c.filter(y=>y.dataset.adsToken===u.token&&y.dataset.adsProperty===u.property);if(!S.length){s.push(`${u.token} needs a rendered ${u.property} sample, not only a name/value label.`);continue}let _=!1;for(let y of S){let P=[];for(let q=y.parentElement;q&&q!==e;q=q.parentElement)q.tagName==="DETAILS"&&!q.open&&(q.open=!0,P.push(q));let E=n.getAttribute("style"),C=y.getAttribute("style");try{y.style.setProperty("transition","none","important");let q=r.getComputedStyle(y).getPropertyValue(u.property),J=T_(y,r);n.style.setProperty(u.token,u.probe,"important");let L=r.getComputedStyle(y).getPropertyValue(u.property);_=!!(L&&L!==q&&(J||T_(y,r)))}finally{E===null?n.removeAttribute("style"):n.setAttribute("style",E),C===null?y.removeAttribute("style"):y.setAttribute("style",C);for(let q of P)q.open=!1}if(_)break}_?i.push(u):s.push(`${u.token} has no visible ${u.property} sample that responds to its value. Hardcoded copies do not satisfy coverage.`)}return{ok:!s.length,verified:i,diagnostics:s}}var We=new Jl({name:"ADS resource preview",version:"1.0.0"},{},{autoResize:!0}),E_=document.getElementById("root"),bf=document.getElementById("ads-preview-status"),vf=async e=>{if(!We.getHostCapabilities()?.serverResources)throw new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase.");try{return await We.readServerResource({uri:e},{timeout:15e3})}catch(t){throw t.code===-32601||t.message==="Method not found"?new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase."):t}},Gl=new Bl(vf),Vr,ha,$_,Rf,zf=!1,vo=0,pa={},Be="";function Mt(e,t=!1){bf.textContent=e,bf.hidden=!e,bf.setAttribute("role",t?"alert":"status")}async function Wl(e,t={}){try{We.getHostCapabilities()?.logging&&await We.sendLog({level:e==="error"?"error":"info",logger:"ads-preview",data:{state:e,id:Vr,...t}})}catch{}}function C_(e){pa={...pa,...e,styles:{...pa.styles,...e.styles}},["light","dark"].includes(pa.theme)&&(document.documentElement.dataset.theme=pa.theme)}async function I_(){return JSON.parse((await vf("ads://preview/manifest")).contents.find(e=>typeof e.text=="string").text)}async function wf(e,t){if(e===Vr&&t&&t===ha){Mt(Be,!!Be),await Wl(Be?"error":"ready",Be?{message:Be}:{revision:ha});return}let o=++vo;++Gl.generation,Vr=e,Be="",Mt(""),await Wl("loading");try{let r=await I_();if(t&&r.revision!==t)throw new Error("Preview changed while opening. Select it again to use the current build.");if(o!==vo||!await Gl.mount(r,e,E_)||o!==vo||(ha=r.revision,$_=r.session,await new Promise(i=>requestAnimationFrame(()=>requestAnimationFrame(i))),await document.fonts.ready,o!==vo))return;let s=r.scenarios.find(i=>i.id===e);if(s?.kind==="tokens"){let i=k_(E_,s.samples);if(!i.ok)throw Be=`Token preview verification failed:
|
|
103
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d.string().optional().describe("User's timezone in IANA format."),userAgent:d.string().optional().describe("Host application identifier."),platform:d.union([d.literal("web"),d.literal("desktop"),d.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:d.object({touch:d.boolean().optional().describe("Whether the device supports touch input."),hover:d.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:d.object({top:d.number().describe("Top safe area inset in pixels."),right:d.number().describe("Right safe area inset in pixels."),bottom:d.number().describe("Bottom safe area inset in pixels."),left:d.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Fv=d.object({method:d.literal("ui/notifications/host-context-changed"),params:x_.describe("Partial context update containing only changed fields.")}),Xx=d.object({method:d.literal("ui/update-model-context"),params:d.object({content:d.array(Fe).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:d.record(d.string(),d.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),eP=d.object({method:d.literal("ui/initialize"),params:d.object({appInfo:Ve.describe("App identification (name and version)."),appCapabilities:Zv.describe("Features and capabilities this app provides."),protocolVersion:d.string().describe("Protocol version this app supports.")})}),Jv=d.object({protocolVersion:d.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ve.describe("Host application identification and version."),hostCapabilities:Uv.describe("Features and capabilities provided by the host."),hostContext:x_.describe("Rich context about the host environment.")}).passthrough(),Bv={target:"draft-2020-12"};async function R_(e,t){let o=e["~standard"];if(o.jsonSchema)return o.jsonSchema[t](Bv);throw Error(`Schema (vendor: ${o.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function w_(e,t,o=""){let r=await e["~standard"].validate(t);if(r.issues){let n=r.issues.map(s=>{let i=s.path?.map(c=>typeof c=="object"?c.key:c).join(".");return i?`${i}: ${s.message}`:s.message}).join("; ");throw Error(o+n)}return r.value}function Wv(e,t){let o={...e,...t};return t.experimental!==void 0&&(o.experimental={...e.experimental,...t.experimental}),t.tools!==void 0&&(o.tools={...e.tools,...t.tools}),o}var Jl=class e extends mf{_appInfo;_appCapabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_registeredEvents=new Set;_events=new ff;_methods=new gf;setRequestHandler=(...t)=>{let[o,...r]=z_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=z_("notification",t)??t;this._methods.claim(o,"setNotificationHandler"),super.setNotificationHandler.call(this,o,...r)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...o)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...o)};_assertInitialized(t){if(this._initializedSent)return;let o=`[ext-apps] App.${t}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(o);console.warn(`${o}. This will throw in a future release.`)}eventSchemas={toolinput:Ov,toolinputpartial:Nv,toolresult:Hv,toolcancelled:Mv,hostcontextchanged:Fv};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(t){if(!e.ONE_SHOT_EVENTS.has(t)||this._everHadListener.has(t)||(this._everHadListener.add(t),!this._initializedSent))return;let o=`[ext-apps] "${String(t)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(o);console.warn(o)}setEventHandler(t,o){o&&this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.setHandler(t,o)}addEventListener(t,o){this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.addEventListener(t,o)}removeEventListener(t,o){this._events.removeEventListener(t,o)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let o=this.eventSchemas[t];if(!o)throw Error(`Unknown event: ${String(t)}`);this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},r=>{let n=r;t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...n}),this._events.dispatch(t,n)}),this._registeredEvents.add(t)}getEventHandler(t){return this._events.getHandler(t)}warnIfRequestHandlerReplaced(t,o,r){o&&r&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,o={},r={autoResize:!0}){super(r),this._appInfo=t,this._appCapabilities=o,this.options=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),!r.allowUnsafeEval&&d.config({jitless:!0}),this.replaceRequestHandler("ping",n=>(console.log("Received ping:",n.params),{})),this.setEventHandler("hostcontextchanged",void 0)}buildContext(t){return t}registerAppCapabilities(t){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._appCapabilities=Wv(this._appCapabilities,t)}registerTool(t,o,r){if(this._registeredTools[t])throw Error(`Tool ${t} is already registered`);let n=this,s=()=>{n._initializedSent&&n._appCapabilities.tools?.listChanged&&n.sendToolListChanged()},i=o.inputSchema!==void 0,c={title:o.title,description:o.description,inputSchema:o.inputSchema,outputSchema:o.outputSchema,annotations:o.annotations,_meta:o._meta,enabled:!0,enable(){this.enabled=!0,s()},disable(){this.enabled=!1,s()},update(l){Object.assign(this,l),s()},remove(){n._registeredTools[t]===c&&(delete n._registeredTools[t],s())},handler:async(l,u)=>{if(!c.enabled)throw Error(`Tool ${t} is disabled`);let m;if(i){let S=c.inputSchema,_=S?await w_(S,l??{},`Invalid input for tool ${t}: `):l??{};m=await r(_,u)}else m=await r(u);return c.outputSchema&&!m.isError&&(m.structuredContent=await w_(c.outputSchema,m.structuredContent,`Invalid output for tool ${t}: `)),m}};return this._registeredTools[t]=c,!this._appCapabilities.tools&&!this.transport&&this.registerAppCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),s(),c}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(t,o)=>{let r=this._registeredTools[t.name];if(!r)throw Error(`Tool ${t.name} not found`);return r.handler(t.arguments,o)},this.onlisttools=async(t,o)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([r,n])=>n.enabled).map(async([r,n])=>{let s={name:r,title:n.title,description:n.description,inputSchema:n.inputSchema?await R_(n.inputSchema,"input"):{type:"object",properties:{}}};return n.outputSchema&&(s.outputSchema=await R_(n.outputSchema,"output")),n.annotations&&(s.annotations=n.annotations),n._meta&&(s._meta=n._meta),s}))}))}async sendToolListChanged(t={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:t})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(t){this.setEventHandler("toolinput",t)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(t){this.setEventHandler("toolinputpartial",t)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(t){this.setEventHandler("toolresult",t)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(t){this.setEventHandler("toolcancelled",t)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(t){this.setEventHandler("hostcontextchanged",t)}_onteardown;get onteardown(){return this._onteardown}set onteardown(t){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,t),this._onteardown=t,this.replaceRequestHandler("ui/resource-teardown",{params:jv.shape.params,result:Lv},(o,r)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(t){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,t),this._onlisttools=t,this.replaceRequestHandler("tools/list",(o,r)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(o.params,r)})}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${t})`);break}}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._appCapabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;default:return}}assertNotificationCapability(t){}async callServerTool(t,o){if(this._assertInitialized("callServerTool"),typeof t=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${t}"). Did you mean: callServerTool({ name: "${t}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:t},{onprogress:()=>{},resetTimeoutOnProgress:!0,...o})}async readServerResource(t,o){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:t},o)}async listServerResources(t,o){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:t},o)}async createSamplingMessage(t,o){return this._assertInitialized("createSamplingMessage"),await this.request({method:"sampling/createMessage",params:t},o)}sendMessage(t,o){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:t},Iv,o)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,o){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:t},Ct,o)}openLink(t,o){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:t},$v,o)}sendOpenLink=this.openLink;downloadFile(t,o){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:t},Cv,o)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,o){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:t},Dv,o)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,o=0,r=0,n=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let i=document.documentElement,c=i.style.height;i.style.height="max-content";let l=Math.ceil(i.getBoundingClientRect().height);i.style.height=c;let u=Math.ceil(window.innerWidth);(u!==o||l!==r)&&(o=u,r=l,this.sendSizeChanged({width:u,height:l}))}))};n();let s=new ResizeObserver(n);return s.observe(document.documentElement),s.observe(document.body),()=>s.disconnect()}async connect(t=new Sf(window.parent,window.parent),o){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1;try{await super.connect(t);let r=await this.request({method:"ui/initialize",params:{appCapabilities:this._appCapabilities,appInfo:this._appInfo,protocolVersion:xv}},Jv,o);if(r===void 0)throw Error(`Server sent invalid initialize result: ${r}`);this._hostCapabilities=r.hostCapabilities,this._hostInfo=r.hostInfo,this._hostContext=r.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(r){throw this.close(),r}}};var Bl=class{constructor(t){this.read=t,this.records=new Map,this.bytes=new Map,this.urls=new Map,this.styles=new Map,this.styleReady=new Map,this.revisions=new Set,this.importing=Promise.resolve(),this.generation=0,this.closed=!1,globalThis.__dsPreviewImport=o=>this.importModule(o)}register(t){if(t.schemaVersion!==1||!Array.isArray(t.scenarios)||!t.assets||typeof t.revision!="string")throw new Error("Unsupported preview manifest. Rebuild previews with the installed ADS version.");if(this.revisions.add(t.revision),this.revisions.size>20)throw Object.assign(new Error("Reopen the preview to release modules retained across 20 build revisions."),{code:"ADS_PREVIEW_REOPEN"});for(let[o,r]of Object.entries(t.assets)){if(!/^ads:\/\/assets\/[a-f0-9]{64}\.[a-z0-9]+$/.test(o)||!/^sha256:[a-f0-9]{64}$/.test(r.digest)||!["imports","dynamicImports","styles","references"].every(n=>Array.isArray(r[n])&&r[n].every(s=>Object.hasOwn(t.assets,s))))throw new Error("Invalid preview dependency graph.");if(this.records.has(o)&&this.records.get(o).digest!==r.digest)throw new Error("An immutable preview resource changed its content.");this.records.set(o,r)}}async resource(t){if(!this.bytes.has(t)){let o=(async()=>{let r=this.records.get(t);if(!r)throw new Error(`Unknown preview dependency: ${t}`);let n=await this.read(t);if(this.closed)throw new Error("The preview App was closed.");let s=n.contents.find(u=>u.uri===t);if(!s)throw new Error(`Missing preview dependency: ${t}`);let i=typeof s.text=="string"?new TextEncoder().encode(s.text):Uint8Array.from(atob(s.blob),u=>u.charCodeAt(0)),c=await crypto.subtle.digest("SHA-256",i),l="sha256:"+[...new Uint8Array(c)].map(u=>u.toString(16).padStart(2,"0")).join("");if(i.length!==r.size||l!==r.digest)throw new Error("Preview resource integrity check failed. Rebuild and reopen the preview.");return i})();this.bytes.set(t,o),o.catch(()=>this.bytes.delete(t))}return this.bytes.get(t)}closure(t){let o=[],r=new Set,n=s=>{if(r.has(s))return;r.add(s);let i=this.records.get(s);if(!i)throw new Error(`Missing preview dependency metadata: ${s}`);for(let c of[...i.imports,...i.styles,...i.references])n(c);o.push(s)};return n(t),o}async materialize(t){let o=this.closure(t);await Promise.all(o.map(n=>this.resource(n)));let r={};for(let n of o){if(this.closed)throw new Error("The preview App was closed.");if(this.urls.has(n))continue;let s=this.records.get(n),i=await this.resource(n);if(s.kind==="module"||s.mimeType==="text/css"){let l=new TextDecoder().decode(i);for(let u of s.references){let m=this.urls.get(u);if(!m)throw new Error("Unsupported cyclic asset reference.");l=l.replaceAll(u,m)}i=l}let c=URL.createObjectURL(new Blob([i],{type:s.mimeType}));this.urls.set(n,c),s.kind==="module"&&(r[n]=c)}if(Object.keys(r).length){let n=document.createElement("script");n.type="importmap",n.textContent=JSON.stringify({imports:r}),document.head.append(n)}return o}async importModule(t){let o=this.importing.then(()=>this.materialize(t));this.importing=o.catch(()=>{});let r=await o;for(let n of r){if(this.records.get(n).mimeType!=="text/css")continue;if(this.styles.has(n)){await this.styleReady.get(n);continue}let i=document.createElement("link");i.rel="stylesheet",i.href=this.urls.get(n),this.styles.set(n,i);let c=new Promise((l,u)=>{i.onload=l,i.onerror=()=>u(new Error("The host CSP blocked a preview stylesheet."))});this.styleReady.set(n,c),document.head.append(i);try{await c}catch(l){throw i.remove(),this.styles.delete(n),this.styleReady.delete(n),l}}return import(t)}async mount(t,o,r){let n=++this.generation;this.register(t);let s=t.scenarios.find(l=>l.id===o);if(!s)throw new Error("The selected scenario is no longer registered.");let i=await this.importModule(s.module);if(n!==this.generation||(await this.unmount?.(),n!==this.generation||this.closed))return!1;r.replaceChildren();let c=new Set(this.closure(s.module));for(let[l,u]of this.styles)c.has(l)||(u.remove(),this.styles.delete(l),this.styleReady.delete(l));for(let l of this.closure(s.module))this.styles.has(l)&&document.head.append(this.styles.get(l));if(typeof i.mount!="function")throw new Error("The compiled scenario has no mount adapter.");if(this.unmount=i.mount(r),typeof this.unmount!="function")throw new Error("The scenario mount adapter must return an unmount callback.");return!0}async dispose(){if(!this.closed){this.closed=!0,++this.generation,await this.unmount?.();for(let t of this.styles.values())t.remove();for(let t of this.urls.values())URL.revokeObjectURL(t);this.styles.clear(),this.styleReady.clear(),this.urls.clear(),this.bytes.clear(),this.records.clear(),delete globalThis.__dsPreviewImport}}};function P_(e){return Array.isArray(e)&&e.length>0&&e.every(t=>t&&typeof t=="object"&&typeof t.token=="string"&&/^--[\w-]+$/.test(t.token)&&typeof t.property=="string"&&/^-?[a-z][a-z0-9-]*$/.test(t.property)&&typeof t.probe=="string"&&t.probe.trim().length>0)&&new Set(e.map(t=>`${t.token}:${t.property}`)).size===e.length}function T_(e,t){let o=t.getComputedStyle(e),r=e.getBoundingClientRect();if(o.display==="none"||o.visibility!=="visible"||!e.getClientRects().length||!(r.width||r.height))return!1;for(let n=e;n;n=n.parentElement)if(Number(t.getComputedStyle(n).opacity)===0)return!1;return!0}function k_(e,t){if(!P_(t))return{ok:!1,diagnostics:["Token preview needs explicit { token, property, probe } samples from the project fixture. Rebuild with the current compiler."]};let o=e.ownerDocument,r=o.defaultView,n=o.documentElement,s=[],i=[],c=[...e.querySelectorAll("[data-ads-token], [data-ads-property]")],l=new Set(t.map(u=>`${u.token}:${u.property}`));for(let u of c)l.has(`${u.dataset.adsToken}:${u.dataset.adsProperty}`)||s.push(`Undeclared rendered token sample ${u.dataset.adsToken}:${u.dataset.adsProperty}.`);for(let u of t){if(!r.CSS.supports(u.property,u.probe)){s.push(`${u.token}: the fixture probe is invalid for ${u.property}.`);continue}let m=r.getComputedStyle(n).getPropertyValue(u.token).trim();if(!m||!r.CSS.supports(u.property,m)){s.push(`${u.token}: its current value is invalid for the fixture's ${u.property} binding.`);continue}let S=c.filter(y=>y.dataset.adsToken===u.token&&y.dataset.adsProperty===u.property);if(!S.length){s.push(`${u.token} needs a rendered ${u.property} sample, not only a name/value label.`);continue}let _=!1;for(let y of S){let P=[];for(let q=y.parentElement;q&&q!==e;q=q.parentElement)q.tagName==="DETAILS"&&!q.open&&(q.open=!0,P.push(q));let E=n.getAttribute("style"),C=y.getAttribute("style");try{y.style.setProperty("transition","none","important");let q=r.getComputedStyle(y).getPropertyValue(u.property),J=T_(y,r);n.style.setProperty(u.token,u.probe,"important");let L=r.getComputedStyle(y).getPropertyValue(u.property);_=!!(L&&L!==q&&(J||T_(y,r)))}finally{E===null?n.removeAttribute("style"):n.setAttribute("style",E),C===null?y.removeAttribute("style"):y.setAttribute("style",C);for(let q of P)q.open=!1}if(_)break}_?i.push(u):s.push(`${u.token} has no visible ${u.property} sample that responds to its value. Hardcoded copies do not satisfy coverage.`)}return{ok:!s.length,verified:i,diagnostics:s}}var We=new Jl({name:"ADS resource preview",version:"1.0.0"},{},{autoResize:!0}),E_=document.getElementById("root"),bf=document.getElementById("ads-preview-status"),vf=async e=>{if(!We.getHostCapabilities()?.serverResources)throw new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase.");try{return await We.readServerResource({uri:e},{timeout:15e3})}catch(t){throw t.code===-32601||t.message==="Method not found"?new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase."):t}},Gl=new Bl(vf),Vr,ha,$_,Rf,zf=!1,vo=0,pa={},Be="";function Mt(e,t=!1){bf.textContent=e,bf.hidden=!e,bf.setAttribute("role",t?"alert":"status")}async function Wl(e,t={}){try{We.getHostCapabilities()?.logging&&await We.sendLog({level:e==="error"?"error":"info",logger:"ads-preview",data:{state:e,id:Vr,...t}})}catch{}}function C_(e){pa={...pa,...e,styles:{...pa.styles,...e.styles}},["light","dark"].includes(pa.theme)&&(document.documentElement.dataset.theme=pa.theme)}async function I_(){return JSON.parse((await vf("ads://preview/manifest")).contents.find(e=>typeof e.text=="string").text)}async function wf(e,t){if(e===Vr&&t&&t===ha){Mt(Be,!!Be),await Wl(Be?"error":"ready",Be?{message:Be}:{revision:ha});return}let o=++vo;++Gl.generation,Vr=e,document.documentElement.dataset.adsScenario=e,document.documentElement.dataset.adsPreviewState="loading",Be="",Mt(""),await Wl("loading");try{let r=await I_();if(t&&r.revision!==t)throw new Error("Preview changed while opening. Select it again to use the current build.");if(o!==vo||!await Gl.mount(r,e,E_)||o!==vo||(ha=r.revision,$_=r.session,await new Promise(i=>requestAnimationFrame(()=>requestAnimationFrame(i))),await document.fonts.ready,o!==vo))return;let s=r.scenarios.find(i=>i.id===e);if(s?.kind==="tokens"){let i=k_(E_,s.samples);if(!i.ok)throw Be=`Token preview verification failed:
|
|
104
104
|
${i.diagnostics.slice(0,20).join(`
|
|
105
|
-
`)}`,new Error(Be)}o===vo&&(Mt(""),await Wl("ready",{revision:ha}))}catch(r){o===vo&&(Mt(r.message,!0),await Wl("error",{message:r.message,...r.code?{code:r.code}:{}}))}}async function Gv(){if(!(!Vr||zf||document.visibilityState==="hidden")){zf=!0;try{let t=JSON.parse((await vf("ads://project/build")).contents.find(o=>typeof o.text=="string").text).previews.find(o=>o.id===Vr);if(!t||!t.current){let o=t?.diagnostics?.[0]?.message;Mt(o?`Build failed. Showing the previous preview.
|
|
105
|
+
`)}`,new Error(Be)}o===vo&&(document.documentElement.dataset.adsPreviewState="ready",Mt(""),await Wl("ready",{revision:ha}))}catch(r){o===vo&&(document.documentElement.dataset.adsPreviewState="error",Mt(r.message,!0),await Wl("error",{message:r.message,...r.code?{code:r.code}:{}}))}}async function Gv(){if(!(!Vr||zf||document.visibilityState==="hidden")){zf=!0;try{let t=JSON.parse((await vf("ads://project/build")).contents.find(o=>typeof o.text=="string").text).previews.find(o=>o.id===Vr);if(!t||!t.current){let o=t?.diagnostics?.[0]?.message;Mt(o?`Build failed. Showing the previous preview.
|
|
106
106
|
${o}`:"Rebuilding. This is the previous preview.",!!o)}else{let o=await I_();o.revision!==ha||o.session!==$_?await wf(Vr):Mt(Be,!!Be)}}catch(e){Mt(`Preview freshness unavailable: ${e.message}`,!0)}finally{zf=!1}}}We.ontoolresult=e=>{let t=e.structuredContent;t?.scenario?.id&&wf(t.scenario.id,t.manifestRevision)};We.onhostcontextchanged=C_;We.onteardown=async()=>(++vo,clearInterval(Rf),await Gl.dispose(),{});try{if(await We.connect(),C_(We.getHostContext()??{}),!We.getHostCapabilities()?.serverResources)throw new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase.");Rf=setInterval(()=>{Gv()},2e3);let e=document.getElementById("ads-preview-selection");e&&wf(JSON.parse(e.textContent).id)}catch(e){Mt(e.message,!0)}window.addEventListener("pagehide",()=>{clearInterval(Rf),Gl.dispose()},{once:!0});
|
|
107
107
|
/*! Bundled license information:
|
|
108
108
|
|
package/dist/mcp/prompts.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const reasoning = `ADS is development tooling. The project owns its components, patterns, token values and branding.
|
|
2
|
-
Start with ads_status and use its projectRoot, workflow and project commands; the MCP workspace may be nested inside the client repository. If ADS tools are not exposed, read ads://project/guide then ads://project/status. Find existing public UI with ads_catalog and load one relevant ADS workflow via ads_skills/ads_read_skill. UI implementation/refinement uses ads-designer; library adaptation ads-
|
|
2
|
+
Start with ads_status and use its projectRoot, workflow and project commands; the MCP workspace may be nested inside the client repository. If ADS tools are not exposed, read ads://project/guide then ads://project/status. Find existing public UI with ads_catalog and load one relevant ADS workflow via ads_skills/ads_read_skill. UI implementation/refinement uses ads-designer; library adaptation ads-migrate; ADS transfer ads-import; motion ads-motion; stress testing ads-break-ui. Domain-model workflows are separate. Inspect the relevant public API, local guide, token definitions and real showcase fixture before proposing changes. Keep source/instruction discovery scoped to the target project, ancestry and selected external sources; broad scans of /mnt or home are not needed to discover this ADS.
|
|
3
3
|
When ads_status.mutations.enabled, discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff through ads_apply_patch({unit,patch,dryRun}). A unit includes its own scenarios/fixtures; imported peers remain separate. tokens is the token foundation unit. Only that owner's public re-exports may change in the shared entry; preserve unrelated statements including export {};. Token manifests list companionFiles: include required foundation import changes only for canonical token files created/deleted in the same patch, preserving other imports/comments and canonical order. Use dryRun to review validation. Rejected patches leave source unchanged; fix diagnostics rather than bypassing checks with direct writes. Shared globals use foundation; guides use documentation; assets use discovered assets/* units. Intentional configuration, registered project guards/builders and rule exceptions use configuration, which validates the proposed settings and refreshes its compiler. Keep mandatory ADS gates. For large/binary diffs, pass patchFile pointing to .ds/patches/<name>.patch rather than loading bytes into context; Git binary patches require installed Git. When editing is disabled, use local editing tools.
|
|
4
4
|
Reason through Data (design-token values, semantic roles and relationships), UX (structure, behavior, accessibility, states and layout), and Skin (token-backed identity and visual states). Business data is a separate application-owned input, supplied through typed props or injected callbacks/services. The project uses Linaria extraction for owned styles; MCP adds no production runtime.
|
|
5
5
|
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Put demo-only sizing and framing in a public review/layout pattern; add component props for deliberate runtime API needs and preserve required behavior. Do not create a widget-only replica or override component internals.
|
|
6
6
|
For token source inspection, call ads_tokens with a focused query. For token samples, components, patterns and composition, use ads_scenarios to select a path-derived id and invoke ads_show_scenario. Showcase and MCP mount that exact entry and its colocated fixture; no page initialization is required. The showcase is a minimal tool-owned shell plus an MCP client and Apps host. Its menus come from tools; it invokes the same entry tools and refreshes the active scenario through notifications. The reusable App loads framework runtime, foundation CSS, fonts and selected modules via MCP resources inside its sandbox. Keep project execution and styles out of the outer host; preserve mount/unmount cleanup and lazy dependencies. Use the existing ads_status showcase URL, or ads showcase when no MCP runtime is connected. Projects supply scenarios/fixtures and the compiler, not browser page files or another shell. Tool metadata selects an MCP App; a resources/read of HTML alone is not proof of rendered UI. If the host has no Apps view, give the configured showcase URL and use text/resources.
|
|
7
7
|
Check ads_status.previewWatch before launching a separate preview builder. A configured watcher is managed by ads mcp. Check ads_status.build or ads://project/build after changes: running does not mean ready, and unreported freshness is not evidence of current source. Reported preview failures include structured diagnostics; fix the owning source and wait for a successful rebuild, then invoke the preview tool again. Notifications may not reach the agent, so read status explicitly. Build revisions identify reported compilation attempts, not proof of runtime/visual correctness. Do not invent replacement UI to bypass a failed preview.
|
|
8
8
|
Skills: use ads_skills to select a workflow and read its SKILL.md with ads_read_skill. Resolve relative supporting files from that skill URI. Reading instructions does not prove native skill activation or authorize unrelated actions.
|
|
9
|
-
Ask for missing intent only when it changes the outcome. State the proposed behavior, states and difficult content. Preserve lazy loading, workers, caches, cancellation, reduced motion, focus and keyboard behavior. Run ads_check plus the project's registered lint, typecheck, build and relevant tests; inspect the actual browser/rendered app at narrow and wide sizes in both themes. Static success does not establish visual or interaction parity.`;
|
|
9
|
+
Ask for missing intent only when it changes the outcome. State the proposed behavior, states and difficult content. Preserve lazy loading, workers, caches, cancellation, reduced motion, focus and keyboard behavior. For controllers and hooks, read ads://guides/behaviors. Discover guarantees/checks with ads_behaviors and run ads_verify; read current execution evidence. Keep behavior.json and checks development-only. Use the configured framework and declared dependencies, not MCP host globals. Preserve covers for rendered UI; exercises links behavioral APIs to demonstrations, not passing guarantees. Run ads_check plus the project's registered lint, typecheck, build and relevant tests; inspect the actual browser/rendered app at narrow and wide sizes in both themes. Static success does not establish visual or interaction parity.`;
|
|
10
10
|
|
|
11
11
|
export function promptDefinitions(skills) {
|
|
12
12
|
const prompts = [
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { Server, ProtocolError, INVALID_PARAMS, ResourceNotFoundError } from "@modelcontextprotocol/server";
|
|
4
|
-
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
4
|
+
import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { checkProject } from "../check.mjs";
|
|
7
7
|
import { loadConfig, sourceFiles } from "../config.mjs";
|
|
8
|
+
import { behaviorStatus, createVerificationController, BEHAVIOR_URI } from "../verification.mjs";
|
|
8
9
|
import { inspectTokens } from "../tokens.mjs";
|
|
9
10
|
import { inspectScenarios } from "../scenarios.mjs";
|
|
10
11
|
import { startShowcaseHost } from "./showcase-host.mjs";
|
|
@@ -33,6 +34,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
33
34
|
const cache = inspection ?? {};
|
|
34
35
|
const skills = cache.skills ??= createSkillStore(skillDirectory);
|
|
35
36
|
const edits = cache.edits ??= createMutationController(root);
|
|
37
|
+
const verification = cache.verification ??= createVerificationController(root);
|
|
36
38
|
let options = loadConfig(root);
|
|
37
39
|
const signature = `${root}:${JSON.stringify(options.config)}`;
|
|
38
40
|
if (cache.signature !== signature) {
|
|
@@ -77,11 +79,13 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
77
79
|
const skillEntries = new Set(skills.skills.map((skill) => skill.uri));
|
|
78
80
|
return [
|
|
79
81
|
{ uri: GUIDE_URI, name: "Start here: design-system workflow", description: "Read first for UI/ADS tasks, especially when only resource access is exposed. Explains the exact workspace, tool sequence, skill selection, previews, project commands and fallback.", mimeType: "text/markdown" },
|
|
82
|
+
{ uri: "ads://guides/behaviors", name: "Controller and behavioral verification guide", mimeType: "text/markdown" },
|
|
80
83
|
{ uri: `${PROJECT_URI}/status`, name: "ADS project status", mimeType: "application/json" },
|
|
81
84
|
{ uri: BUILD_URI, name: "Preview build status and diagnostics", mimeType: "application/json" },
|
|
82
85
|
{ uri: `${PROJECT_URI}/tokens`, name: "Project token values and themes", mimeType: "application/json" },
|
|
83
86
|
{ uri: `${PROJECT_URI}/catalog`, name: "Public components and patterns", mimeType: "application/json" },
|
|
84
87
|
{ uri: `${PROJECT_URI}/scenarios`, name: "Independently mountable scenarios", mimeType: "application/json" },
|
|
88
|
+
{ uri: BEHAVIOR_URI, name: "Behavior contracts, demonstrations and current verification evidence", mimeType: "application/json" },
|
|
85
89
|
{ uri: `${PROJECT_URI}/checks`, name: "Current ADS architecture diagnostics", mimeType: "application/json" },
|
|
86
90
|
{ uri: TOKENS_APP_URI, name: "Experimental token widget", mimeType: APP_MIME },
|
|
87
91
|
{ uri: SCENARIO_APP_URI, name: "Experimental scenario widget", mimeType: APP_MIME },
|
|
@@ -98,18 +102,20 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
98
102
|
const context = project();
|
|
99
103
|
return { projectRoot: root, workflow: workflow([...previews(), ...scenarioApps()]), project: context,
|
|
100
104
|
package: pkg.name, version: pkg.version, dsDir: options.config.dsDir, publicAlias: options.config.publicAlias,
|
|
101
|
-
components: getCatalog().filter((item) => item.kind === "components").length,
|
|
102
|
-
patterns: getCatalog().filter((item) => item.kind === "patterns").length,
|
|
105
|
+
components: getCatalog().filter((item) => item.kind === "components" && item.apiKind === "ui").length,
|
|
106
|
+
patterns: getCatalog().filter((item) => item.kind === "patterns" && item.apiKind === "ui").length,
|
|
103
107
|
tokenCount: inspectTokens(root).tokens.length, showcaseUrl: getShowcaseUrl(),
|
|
104
108
|
previewWatch: previewWatch?.status() ?? { state: "disabled" },
|
|
105
109
|
mutations: mutations ? edits.status() : { enabled: false },
|
|
106
110
|
build: summarizeBuild(builds()),
|
|
107
111
|
previews: previews(), skills: skills.skills.map(({ uri, frontmatter }) => ({ uri, name: frontmatter.name, description: frontmatter.description })),
|
|
108
112
|
scenarios: { count: getRegistry().scenarios.length, diagnostics: getRegistry().diagnostics, resourceUri: `${PROJECT_URI}/scenarios`, tool: "ads_show_scenario" },
|
|
113
|
+
behaviors: { resourceUri: BEHAVIOR_URI, discoveryTool: "ads_behaviors", verificationTool: "ads_verify", ...verification.status() },
|
|
109
114
|
experimental: { apps: true, nativeSkills: era === "modern", nativeSkillActivation: "host-controlled" } };
|
|
110
115
|
}
|
|
111
116
|
async function read(uri, signal) {
|
|
112
117
|
if (uri === GUIDE_URI) return { contents: [{ uri, mimeType: "text/markdown", text: projectGuide(project(), [...previews(), ...scenarioApps()]) }] };
|
|
118
|
+
if (uri === "ads://guides/behaviors") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../../docs/BEHAVIORS.md", import.meta.url), "utf8") }] };
|
|
113
119
|
if (skills.files.has(uri)) return { contents: [skills.read(uri)] };
|
|
114
120
|
let value;
|
|
115
121
|
if (uri === `${PROJECT_URI}/status`) value = status();
|
|
@@ -118,6 +124,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
118
124
|
else if (uri === `${PROJECT_URI}/catalog`) value = { items: getCatalog() };
|
|
119
125
|
else if (uri === `${PROJECT_URI}/scenarios`) value = getRegistry();
|
|
120
126
|
else if (uri === `${PROJECT_URI}/checks`) value = checkProject(root);
|
|
127
|
+
else if (uri === BEHAVIOR_URI) value = behaviorStatus(options);
|
|
121
128
|
else if (uri === PREVIEW_MANIFEST_URI && options.config.mcp.previewManifest) {
|
|
122
129
|
value = readPreviewManifest(options);
|
|
123
130
|
if (!value) throw new Error("Preview resources are not built yet. Invoke ads_show_scenario to start the managed compiler.");
|
|
@@ -153,13 +160,13 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
153
160
|
function tools() {
|
|
154
161
|
return [
|
|
155
162
|
{ name: "ads_status", description: "Start here for UI/ADS work: components, patterns, tokens/themes/branding, layout, behavior, motion and showcase review. Returns the exact project root/public API, workflow, project commands, editing mode and build diagnostics. When mutations.enabled, use guarded ads_apply_patch for ADS edits; otherwise use local editing tools.", schema: empty, call: () => json(status()) },
|
|
156
|
-
{ name: "ads_catalog", description: "Before creating/changing UI, find existing public components/patterns with a small query/limit. Returns import alias and exact source/guide URIs so you can reuse or edit the owner. Read ads_status first for workspace/workflow. Metadata only; does not render UI.", schema: query.extend({ kind: z.enum(["components", "patterns"]).optional() }), call: ({ query: search = "", limit, kind }) => {
|
|
163
|
+
{ name: "ads_catalog", description: "Before creating/changing UI or behavior, find existing public components/patterns/controllers and hooks with a small query/limit. Returns import alias and exact source/guide URIs so you can reuse or edit the owner. Read ads_status first for workspace/workflow. Metadata only; does not render UI.", schema: query.extend({ kind: z.enum(["components", "patterns", "controllers"]).optional() }), call: ({ query: search = "", limit, kind }) => {
|
|
157
164
|
const matches = getCatalog().filter((item) => (!kind || item.kind === kind) && item.name.toLowerCase().includes(search.toLowerCase()));
|
|
158
165
|
const units = edits.units();
|
|
159
166
|
return json({ total: matches.length, items: matches.slice(0, limit).map(item => ({ ...item, unit: units.find(unit => unit.files.includes(item.file))?.id ?? null })), resourceUri: `${PROJECT_URI}/catalog` });
|
|
160
167
|
} },
|
|
161
|
-
{ name: "ads_scenarios", description: "Find an independently mountable token, component, pattern or composition preview. IDs derive from source filenames; covers lists public UI and tokens lists the real token inputs. Use ads_show_scenario with an exact returned id. Showcase and MCP use this same registry.", schema: query.extend({ kind: z.enum(["tokens", "components", "patterns", "composition"]).optional(), offset: z.number().int().min(0).default(0) }), call: ({ query: search = "", limit, kind, offset }) => {
|
|
162
|
-
const matches = getRegistry().scenarios.filter(item => (!kind || item.kind === kind) && `${item.id} ${item.title} ${item.covers.join(" ")} ${item.tokens.join(" ")}`.toLowerCase().includes(search.toLowerCase()));
|
|
168
|
+
{ name: "ads_scenarios", description: "Find an independently mountable token, component, pattern or composition preview. IDs derive from source filenames; covers lists rendered public UI, exercises lists behavioral APIs and tokens lists the real token inputs. Use ads_show_scenario with an exact returned id. Showcase and MCP use this same registry.", schema: query.extend({ kind: z.enum(["tokens", "components", "patterns", "composition"]).optional(), offset: z.number().int().min(0).default(0) }), call: ({ query: search = "", limit, kind, offset }) => {
|
|
169
|
+
const matches = getRegistry().scenarios.filter(item => (!kind || item.kind === kind) && `${item.id} ${item.title} ${item.covers.join(" ")} ${(item.exercises ?? []).join(" ")} ${item.tokens.join(" ")}`.toLowerCase().includes(search.toLowerCase()));
|
|
163
170
|
const units = edits.units();
|
|
164
171
|
return json({ total: matches.length, scenarios: matches.slice(offset, offset + limit).map(item => ({ ...item, unit: units.find(unit => unit.files.includes(item.file))?.id ?? null })), ...(offset + limit < matches.length ? { nextOffset: offset + limit } : {}), diagnostics: getRegistry().diagnostics, resourceUri: `${PROJECT_URI}/scenarios` });
|
|
165
172
|
} },
|
|
@@ -185,7 +192,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
185
192
|
const result = checkProject(root);
|
|
186
193
|
return json({ ok: result.ok, total: result.diagnostics.length, diagnostics: result.diagnostics.slice(0, limit), advisoryCount: result.advisories?.length ?? 0, advisories: (result.advisories ?? []).slice(0, limit), resourceUri: `${PROJECT_URI}/checks` });
|
|
187
194
|
} },
|
|
188
|
-
{ name: "ads_skills", description: "Select one ADS workflow after ads_status: ads-designer for building/refining/debugging UI; ads-
|
|
195
|
+
{ name: "ads_skills", description: "Select one ADS workflow after ads_status: ads-designer for building/refining/debugging UI; ads-migrate for a library reference; ads-import for another project's ADS; ads-motion for animation; ads-break-ui for stress testing. Then read that workflow with ads_read_skill. Listing does not activate a native skill.", schema: empty, call: () => json({ skills: skills.skills.map(({ uri, frontmatter }) => ({ uri, ...frontmatter })) }) },
|
|
189
196
|
{ name: "ads_read_skill", description: "Read a selected skill or relative supporting text file in bounded chunks. This is explicit instruction access, not automatic native skill activation/install.", schema: z.object({ name: z.string().max(64), file: z.string().max(300).default("SKILL.md"), offset: z.number().int().min(0).default(0), limit: z.number().int().min(1).max(8000).default(6000) }).strict(), call: ({ name, file, offset, limit }) => {
|
|
190
197
|
const skill = skills.skills.find((entry) => entry.frontmatter.name === name);
|
|
191
198
|
if (!skill) throw new Error("Unknown ADS skill name.");
|
|
@@ -214,7 +221,24 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
214
221
|
}
|
|
215
222
|
return json({ widget: { resourceUri: TOKENS_APP_URI }, category: null, tokenCount: inspectTokens(root).tokens.length, dataUri: `${PROJECT_URI}/tokens`, showcaseUrl: getShowcaseUrl() });
|
|
216
223
|
} },
|
|
217
|
-
{ name: "
|
|
224
|
+
{ name: "ads_behaviors", description: "Discover owner-local behavioral guarantees for public hooks/APIs or private controllers, their scenario demonstrations, registered checks/profiles and fresh or stale execution evidence. Optional exercises links behavioral APIs to scenarios; covers remains rendered UI. Metadata and reachable calls do not prove a guarantee passed. Use ads_verify for selected registered checks.", schema: query.extend({ unit: z.string().max(256).optional(), api: z.string().max(200).optional() }), call: ({ query: search = "", limit, unit, api }) => {
|
|
225
|
+
const inventory = behaviorStatus(options);
|
|
226
|
+
const matches = inventory.contracts.filter(item => (!unit || item.unit === unit) && (!api || Object.values(item.subjects).some(subject => subject.api === api)) && JSON.stringify(item).toLowerCase().includes(search.toLowerCase()));
|
|
227
|
+
return json({ total: matches.length, contracts: matches.slice(0, limit).map(item => {
|
|
228
|
+
if (unit) return item;
|
|
229
|
+
const apis = Object.values(item.subjects).filter(subject => subject.api).map(subject => subject.api);
|
|
230
|
+
const demonstrations = [...new Set(Object.values(item.demonstrations).flat())];
|
|
231
|
+
return { unit: item.unit, file: item.file, apis: apis.slice(0, 20), apiCount: apis.length,
|
|
232
|
+
guarantees: item.guarantees.slice(0, 5).map(guarantee => ({ id: guarantee.id, result: guarantee.result })), guaranteeCount: item.guarantees.length,
|
|
233
|
+
demonstrations: demonstrations.slice(0, 10), demonstrationCount: demonstrations.length, checkCount: Object.keys(item.checks).length };
|
|
234
|
+
}), diagnostics: inventory.diagnostics.slice(0, 50), resourceUri: BEHAVIOR_URI });
|
|
235
|
+
} },
|
|
236
|
+
{ name: "ads_verify", description: "Run one owner's registered development behavior checks using its configured runner/profile. Executes trusted project commands with bounded output, cancellation and process-tree cleanup; installs nothing. Browser profiles use an isolated managed showcase/MCP App. React StrictMode requires a project development harness, not a production widget. Missing/skipped cases, stale inputs and process exit alone cannot produce passing evidence. Discover exact unit/check IDs with ads_behaviors first.", schema: z.object({ unit: z.string().min(1).max(256), checks: z.array(z.string().min(1).max(100)).min(1).max(100).optional() }).strict(), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, call: async (args, signal) => {
|
|
237
|
+
const result = await verification.run(args, signal);
|
|
238
|
+
await refresh();
|
|
239
|
+
return { ...json(result), ...(!result.ok ? { isError: true } : {}) };
|
|
240
|
+
} },
|
|
241
|
+
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; shared controllers; composition; tokens; foundation shared files; documentation; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Manifests include companionFiles with permitted shared-entry or owner-only style-boundary bookkeeping; preserve peer permissions and unrelated configuration. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
218
242
|
const matches = edits.units().filter(item => unit ? item.id === unit : item.id.toLowerCase().includes(search.toLowerCase()));
|
|
219
243
|
return json({ total: matches.length, units: matches.slice(0, limit).map(item => ({ id: item.id, kind: item.kind, fileCount: item.files.length, ...(unit ? { files: item.files.slice(0, 200), filesTruncated: item.files.length > 200, ...(item.companionFiles ? { companionFiles: item.companionFiles } : {}) } : {}) })) });
|
|
220
244
|
} },
|
|
@@ -295,7 +319,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
295
319
|
try { const stat = statSync(file); return `${file}:${stat.mtimeMs}:${stat.size}`; } catch { return `${file}:missing`; }
|
|
296
320
|
}).join("\n");
|
|
297
321
|
}
|
|
298
|
-
const fingerprint = () => fileFingerprint([...sourceFiles(options.dsRoot), ...getDocuments().map((doc) => doc.file), path.join(root, "ds.config.json"), path.join(root, "package.json")]);
|
|
322
|
+
const fingerprint = () => fileFingerprint([...sourceFiles(options.dsRoot), ...getDocuments().map((doc) => doc.file), path.join(root, "ds.config.json"), path.join(root, "package.json"), path.join(root, ".ds/behavior-results.json")]);
|
|
299
323
|
const artifactFingerprint = () => fileFingerprint(allApps().map(app => path.resolve(root, app.file)));
|
|
300
324
|
const assetFingerprint = () => resourceAssets().map(asset => asset.uri).sort().join("\n");
|
|
301
325
|
let previous = fingerprint();
|
|
@@ -330,7 +354,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
330
354
|
previousAssets = assetFingerprint();
|
|
331
355
|
previousBuild = JSON.stringify(builds());
|
|
332
356
|
const targets = filesChanged ? era === "modern" ? resources().map((entry) => entry.uri).filter((uri) => !skills.files.has(uri)) : [...subscribed]
|
|
333
|
-
: [BUILD_URI, `${PROJECT_URI}/status`, ...(options.config.mcp.previewManifest ? [PREVIEW_MANIFEST_URI] : []), ...(artifactsChanged ? allApps().map(app => appUri(app.id)) : [])].filter((uri) => era === "modern" || subscribed.has(uri));
|
|
357
|
+
: [BUILD_URI, BEHAVIOR_URI, `${PROJECT_URI}/status`, ...(options.config.mcp.previewManifest ? [PREVIEW_MANIFEST_URI] : []), ...(artifactsChanged ? allApps().map(app => appUri(app.id)) : [])].filter((uri) => era === "modern" || subscribed.has(uri));
|
|
334
358
|
for (const uri of targets) { if (notifications) notifications.resourceUpdated(uri); else await server.sendResourceUpdated({ uri }); }
|
|
335
359
|
return true;
|
|
336
360
|
}
|
|
@@ -338,7 +362,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
338
362
|
timer?.unref();
|
|
339
363
|
server.oninitialized = () => previewWatch?.start();
|
|
340
364
|
server.onclose = () => { if (timer) clearInterval(timer); };
|
|
341
|
-
return { server, refresh, async close() { if (timer) clearInterval(timer); await server.close(); } };
|
|
365
|
+
return { server, refresh, async close() { if (timer) clearInterval(timer); if (!inspection) await verification.close(); await server.close(); } };
|
|
342
366
|
}
|
|
343
367
|
|
|
344
368
|
export async function startAdsMcp(directory = process.cwd(), { stdio = true, showcase = true, port = 0, mutations = false } = {}) {
|
|
@@ -353,24 +377,37 @@ export async function startAdsMcp(directory = process.cwd(), { stdio = true, sho
|
|
|
353
377
|
{ port, observe: notifications => createAdsMcp(directory, { era: "modern", previewWatch, showcaseUrl, inspection, mutations, notifications }) });
|
|
354
378
|
process.stderr.write(`ADS showcase: ${host.url}\n`);
|
|
355
379
|
}
|
|
356
|
-
const
|
|
380
|
+
const transport = stdio ? new StdioServerTransport() : null;
|
|
381
|
+
const handle = stdio ? serveStdio(({ era }) => createAdsMcp(directory, { era, previewWatch, showcaseUrl, inspection, mutations }).server, { transport, legacy: "serve", onerror: (error) => process.stderr.write(`ads mcp: ${error.message}\n`) }) : null;
|
|
357
382
|
let closing;
|
|
358
383
|
function close() {
|
|
359
384
|
return closing ??= (async () => {
|
|
360
385
|
inspection.edits?.cancel();
|
|
361
|
-
|
|
362
|
-
await
|
|
363
|
-
|
|
364
|
-
|
|
386
|
+
// Stop every owned resource even if another close fails or has in-flight RPCs.
|
|
387
|
+
const results = await Promise.allSettled([
|
|
388
|
+
Promise.resolve().then(() => host?.close()),
|
|
389
|
+
previewWatch.stop(),
|
|
390
|
+
Promise.resolve().then(() => inspection.verification?.close()),
|
|
391
|
+
Promise.resolve().then(() => handle?.close()),
|
|
392
|
+
]);
|
|
393
|
+
for (const event of ["end", "close", "error"]) process.stdin.removeListener(event, disconnect);
|
|
365
394
|
for (const signal of ["SIGINT", "SIGTERM"]) process.removeListener(signal, disconnect);
|
|
366
395
|
process.removeListener("exit", forceCleanup);
|
|
396
|
+
const errors = results.filter(result => result.status === "rejected").map(result => result.reason);
|
|
397
|
+
if (errors.length) throw new AggregateError(errors, "ADS shutdown failed");
|
|
367
398
|
})();
|
|
368
399
|
}
|
|
369
|
-
function disconnect() { void close(); }
|
|
400
|
+
function disconnect() { void close().catch(error => { process.stderr.write(`ads mcp: ${error.message}\n`); process.exitCode = 1; }); }
|
|
370
401
|
function forceCleanup() { previewWatch.kill(); }
|
|
371
402
|
// Probe instances have no managed child; a real connection activates it.
|
|
372
|
-
//
|
|
373
|
-
|
|
403
|
+
// The SDK also closes its wire on stdout failure. Closing a probe instance
|
|
404
|
+
// must not shut the runtime down, so observe the wire, not server.onclose.
|
|
405
|
+
if (transport) {
|
|
406
|
+
const wireClosed = transport.onclose;
|
|
407
|
+
transport.onclose = () => { try { wireClosed?.(); } finally { disconnect(); } };
|
|
408
|
+
for (const event of ["end", "close", "error"]) process.stdin.once(event, disconnect);
|
|
409
|
+
if (process.stdin.readableEnded || process.stdin.destroyed) disconnect();
|
|
410
|
+
}
|
|
374
411
|
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, disconnect);
|
|
375
412
|
process.once("exit", forceCleanup);
|
|
376
413
|
return { close, url: host?.url };
|
|
@@ -56,13 +56,17 @@ export async function startShowcaseHost(factory, { port = 0, observe } = {}) {
|
|
|
56
56
|
let closing;
|
|
57
57
|
return { url: `http://127.0.0.1:${web.address().port}/`, close() {
|
|
58
58
|
return closing ??= (async () => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
await handler.close();
|
|
62
|
-
await sandbox?.close();
|
|
59
|
+
// Close sockets first: live subscriptions and preview requests must not
|
|
60
|
+
// hold shutdown open while protocol instances wait for cancellation.
|
|
63
61
|
const stopped = new Promise(resolve => web.close(resolve));
|
|
62
|
+
for (const request of requests) request.abort();
|
|
64
63
|
web.closeAllConnections();
|
|
65
|
-
await stopped
|
|
64
|
+
const results = await Promise.allSettled([stopped,
|
|
65
|
+
Promise.resolve().then(() => observer?.close()),
|
|
66
|
+
handler.close(), sandbox?.close(),
|
|
67
|
+
]);
|
|
68
|
+
const errors = results.filter(result => result.status === "rejected").map(result => result.reason);
|
|
69
|
+
if (errors.length) throw new AggregateError(errors, "Showcase shutdown failed");
|
|
66
70
|
})();
|
|
67
71
|
} };
|
|
68
72
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { isDevelopmentFile, isInside } from "./config.mjs";
|
|
4
|
+
import { uiOperation } from "./ui-framework.mjs";
|
|
5
|
+
|
|
6
|
+
export function publicApi(program, options) {
|
|
7
|
+
const checker = program.getTypeChecker();
|
|
8
|
+
const entry = program.getSourceFile(path.join(options.dsRoot, options.config.publicEntry));
|
|
9
|
+
const module = entry && checker.getSymbolAtLocation(entry);
|
|
10
|
+
const items = [];
|
|
11
|
+
for (const exported of module ? checker.getExportsOfModule(module) : []) {
|
|
12
|
+
let symbol = exported;
|
|
13
|
+
if (symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
14
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
15
|
+
if (!declaration || !(symbol.flags & ts.SymbolFlags.Value)) continue;
|
|
16
|
+
const file = declaration.getSourceFile().fileName;
|
|
17
|
+
if (isDevelopmentFile(file, options)) continue;
|
|
18
|
+
const kind = isInside(path.join(options.dsRoot, "components"), file) ? "components" : isInside(path.join(options.dsRoot, "patterns"), file) ? "patterns" : isInside(path.join(options.dsRoot, "controllers"), file) ? "controllers" : null;
|
|
19
|
+
if (!kind) continue;
|
|
20
|
+
let renderable = false;
|
|
21
|
+
function visit(node) {
|
|
22
|
+
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) renderable = true;
|
|
23
|
+
if (ts.isCallExpression(node) && ["createElement", "forwardRef", "memo"].includes(uiOperation(node.expression, checker))) renderable = true;
|
|
24
|
+
if (ts.isTaggedTemplateExpression(node)) {
|
|
25
|
+
let tag = node.tag;
|
|
26
|
+
if (ts.isCallExpression(tag)) tag = tag.expression;
|
|
27
|
+
let base = tag;
|
|
28
|
+
while (ts.isPropertyAccessExpression(base)) base = base.expression;
|
|
29
|
+
const symbol = checker.getSymbolAtLocation(base);
|
|
30
|
+
if (symbol?.declarations?.some(item => ts.isImportSpecifier(item) && (item.propertyName ?? item.name).text === "styled" && item.parent.parent.parent.moduleSpecifier.text.startsWith("@linaria/") || ts.isNamespaceImport(item) && item.parent.parent.moduleSpecifier.text.startsWith("@linaria/") && /^\.styled(?:\.|$)/.test(tag.getText().slice(base.getText().length)))) renderable = true;
|
|
31
|
+
}
|
|
32
|
+
ts.forEachChild(node, visit);
|
|
33
|
+
}
|
|
34
|
+
visit(declaration);
|
|
35
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
36
|
+
items.push({ name: exported.name, kind, file, declaration, renderable: kind !== "controllers" && renderable, behavioral: !!(type.getCallSignatures().length || type.getConstructSignatures().length) });
|
|
37
|
+
}
|
|
38
|
+
return items;
|
|
39
|
+
}
|
|
@@ -126,6 +126,16 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
126
126
|
cache.validate(stamp);
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
|
+
async function checkedBuild(stamp, build) {
|
|
130
|
+
const started = generation;
|
|
131
|
+
let checking = true, compiling = true;
|
|
132
|
+
function progress() { if (started === generation) reporter.progress(checking && compiling ? "validation-and-compilation" : checking ? "validation" : compiling ? "compilation" : "publication"); }
|
|
133
|
+
progress();
|
|
134
|
+
return Promise.allSettled([
|
|
135
|
+
gate(stamp).finally(() => { checking = false; progress(); }),
|
|
136
|
+
Promise.resolve().then(build).finally(() => { compiling = false; progress(); }),
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
129
139
|
async function compile(entry) {
|
|
130
140
|
if (buildGraph) return compileGraph();
|
|
131
141
|
const revision = reporter.begin(entry.id);
|
|
@@ -154,16 +164,18 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
154
164
|
if (path.isAbsolute(file) && existsSync(file)) dependencies.add(file);
|
|
155
165
|
}
|
|
156
166
|
} };
|
|
157
|
-
const outcomes = await
|
|
167
|
+
const outcomes = await checkedBuild(stamp, () => reuse ? undefined : build(entry, options, track));
|
|
158
168
|
const failure = outcomes.find(outcome => outcome.status === "rejected");
|
|
159
169
|
if (failure) throw failure.reason;
|
|
160
170
|
cache.assertCompiler();
|
|
161
171
|
if (stopped || started !== generation || stamp !== cache.inputs()) return; // Watch queued a newer revision.
|
|
162
172
|
if (!reuse) cache.record(entry, dependencies);
|
|
163
173
|
reporter.ready(entry.id, revision);
|
|
174
|
+
reporter.progress("idle");
|
|
164
175
|
} catch (error) {
|
|
165
176
|
if (started !== generation) return;
|
|
166
177
|
reporter.error(entry.id, revision, error);
|
|
178
|
+
reporter.progress("idle");
|
|
167
179
|
console.error(error);
|
|
168
180
|
if (!watched) process.exitCode = 1;
|
|
169
181
|
}
|
|
@@ -192,7 +204,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
192
204
|
cache.assertCompiler();
|
|
193
205
|
let reusable = entries.every(entry => cache.reusable(entry));
|
|
194
206
|
if (reusable) { try { reusable = Boolean(readPreviewManifest(options, { verify: true })); } catch { reusable = false; } }
|
|
195
|
-
const outcomes = await
|
|
207
|
+
const outcomes = await checkedBuild(stamp, () => reusable ? null : buildGraph(entries, options, track));
|
|
196
208
|
const failure = outcomes.find(outcome => outcome.status === "rejected");
|
|
197
209
|
if (failure) throw failure.reason;
|
|
198
210
|
cache.assertCompiler();
|
|
@@ -209,9 +221,11 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
209
221
|
}
|
|
210
222
|
for (const entry of entries) { pending.delete(entry.id); reporter.ready(entry.id, revisions.get(entry.id)); }
|
|
211
223
|
publishedStamp = stamp;
|
|
224
|
+
reporter.progress("idle");
|
|
212
225
|
} catch (error) {
|
|
213
226
|
if (started !== generation) return;
|
|
214
227
|
for (const entry of entries) { pending.delete(entry.id); reporter.error(entry.id, revisions.get(entry.id), error); }
|
|
228
|
+
reporter.progress("idle");
|
|
215
229
|
console.error(error);
|
|
216
230
|
if (!watched) process.exitCode = 1;
|
|
217
231
|
}
|
|
@@ -261,6 +275,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
261
275
|
clearTimeout(timer);
|
|
262
276
|
timer = setTimeout(() => {
|
|
263
277
|
++generation;
|
|
278
|
+
reporter.progress("queued");
|
|
264
279
|
validation = undefined;
|
|
265
280
|
const changes = [...changed]; changed.clear();
|
|
266
281
|
try {
|