@webskill/sdk 0.2.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +42 -0
  2. package/dist/browser.d.ts +2 -2
  3. package/dist/browser.js +4 -9
  4. package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-KsujmL4b-Clx1kCnU.js} +278 -122
  5. package/dist/client-BCM6Z3yq-qUQNkKrK.js +7787 -0
  6. package/dist/{dist-BQzncxXg.js → dist-8oQRa8Xz.js} +212 -13
  7. package/dist/{dist-B9VLwOME.js → dist-C-Sh0MDU.js} +1019 -317
  8. package/dist/{dist-CtBLBbEz.js → dist-D9Lcn5Pp.js} +528 -838
  9. package/dist/governance.d.ts +46 -11
  10. package/dist/governance.js +152 -25
  11. package/dist/{index-7DVaZJU7.d.ts → index-CHXxDccV.d.ts} +62 -144
  12. package/dist/{index-QrHtAudz.d.ts → index-DLfR2Y6I.d.ts} +412 -21
  13. package/dist/index.d.ts +3 -3
  14. package/dist/index.js +4 -3
  15. package/dist/mcp.d.ts +2 -2
  16. package/dist/mcp.js +2 -2
  17. package/dist/memoryArtifactStore-BtOeB_hm-tj3fC5ip.js +78 -0
  18. package/dist/node.d.ts +297 -4
  19. package/dist/node.js +281 -7
  20. package/dist/{openUiLibrary-B8-Cvou9-D3RsU2EB.js → openUiLibrary-YLS-cxyT-C96jWDQq.js} +6 -5
  21. package/dist/skillVersionStore-uyefLPR1-DXOzbksv.d.ts +158 -0
  22. package/dist/stdio-CFMoANJJ-BxrTeXh7.js +31 -0
  23. package/dist/{testing-BUoXvm1u.js → testing-DDCJWvgA.js} +8 -6
  24. package/dist/testing.d.ts +1 -1
  25. package/dist/testing.js +2 -2
  26. package/dist/{types-AmKCKJn_-BogJPQHU.d.ts → types-7Wcg--Vh-1YlQ4jF9.d.ts} +219 -70
  27. package/dist/types-WovEf4ED-CZSDiiBU.js +6215 -0
  28. package/dist/ui-react.d.ts +329 -18
  29. package/dist/ui-react.js +3715 -3464
  30. package/dist/ui-vue.d.ts +1 -1
  31. package/dist/ui-vue.js +1 -1
  32. package/dist/ui.d.ts +4 -3
  33. package/dist/ui.js +3 -3
  34. package/dist/{webskillLitCatalog-CNaUpasU-CfSRvqCZ.js → webskillLitCatalog-CSTbhBe_-CYIs5BX8.js} +312 -122
  35. package/package.json +4 -7
  36. package/dist/jsonRenderRegistry-9GrWP_hE-CQY8bT9w.js +0 -2468
  37. package/dist/memoryArtifactStore-C9lFVqPF-yFz6yJj0.js +0 -48
  38. package/dist/skillVersionStore-B7rGjtMi-BgnQho9v.d.ts +0 -365
package/README.md CHANGED
@@ -49,6 +49,12 @@ npm install @webskill/sdk
49
49
  React/Vue integrations require their respective peer dependencies
50
50
  (`react` + `react-dom`, or `vue`), which are optional.
51
51
 
52
+ `zod` ships as a direct dependency: the `@webskill/sdk/ui` catalog references it
53
+ in its static import graph and in its public types, so declaring it optional
54
+ would misrepresent the artifact. Hosts do not need to install it themselves.
55
+ `tar`, `oxc-parser` and `@modelcontextprotocol/sdk` remain optional peers —
56
+ they are loaded lazily and only when the corresponding API is called.
57
+
52
58
  ## Requirements
53
59
 
54
60
  - Node.js >= 22.18 (native type stripping for `.ts` skill scripts)
@@ -214,6 +220,42 @@ import { MockLlmClient, MockUiBridge, InMemoryStore, MemoryArtifactStore } from
214
220
  const llm = new MockLlmClient([{ content: 'deterministic answer' }]);
215
221
  ```
216
222
 
223
+ ## `allowed-tools`
224
+
225
+ A skill may narrow the tool surface it needs by declaring `allowed-tools` in its
226
+ `SKILL.md` frontmatter. Three kinds of tools can be listed:
227
+
228
+ ```yaml
229
+ allowed-tools:
230
+ - render # a script of this skill (scripts/render.ts)
231
+ - endpoint:github/create_issue # one tool of an MCP endpoint
232
+ - endpoint:github/* # every tool of an MCP endpoint
233
+ - mcp#read_file # one page-provided (WebMCP) tool
234
+ ```
235
+
236
+ Matching rules:
237
+
238
+ - Only whole-entry matches and a trailing `/*` are supported. Wildcards in any
239
+ other position are not, and a bare `endpoint:github` is **not** a prefix
240
+ wildcard — it simply matches nothing.
241
+ - A bare identifier means a script of the declaring skill only. It never grants
242
+ access to another skill's script of the same name.
243
+
244
+ When several skills are active at the same time:
245
+
246
+ | Situation | Result |
247
+ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------- |
248
+ | No active skill declares `allowed-tools` | Unrestricted |
249
+ | At least one active skill declares it | A tool is allowed if it matches any declaring skill's list, **or** if some active skill declares nothing |
250
+
251
+ Enforcement level differs per tool kind in 0.3.0:
252
+
253
+ - **Scripts** — enforced since 0.0.5: a script outside the list is not
254
+ registered, and calling it by name returns `TOOL_NOT_FOUND`.
255
+ - **MCP / WebMCP tools** — 0.3.0 only records a `run.warning` naming the tool
256
+ and the entry to add, and still allows the call. This becomes a rejection in
257
+ 0.4.0, so treat the warning as a build error in your own pipeline.
258
+
217
259
  ## Stability
218
260
 
219
261
  This package follows semver starting at 0.1.0. Public symbols carry JSDoc
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { K as SkillInstallSource, M as FileStat, N as FileSystemProvider, O as ArchiveLimits, Y as SkillManifest, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, et as SkillsLockfile, f as LlmStreamEvent, nt as VerifyResult, o as InteractionRequest, s as InteractionResponse, v as UiBridge } from "./types-AmKCKJn_-BogJPQHU.js";
2
- import { N as NetworkPolicy, Ot as parseBridgeRequest, Q as ScriptExecutor, Z as ScriptExecutionContext, d as BridgeCapabilities, it as ToolResult, m as BridgeResponse, nt as ToolDefinition, p as BridgeRequest, pt as bridgeError, u as ApprovalScope, ut as WebSkillApi, v as ExternalSkillProvider, y as ExternalToolSource } from "./index-QrHtAudz.js";
1
+ import { D as ArchiveLimits, M as FileStat, N as FileSystemProvider, a as InteractionPolicy, c as LlmClient, et as SkillInstallSource, f as LlmResponse, gt as VerifyResult, it as SkillManifest, o as InteractionRequest, p as LlmStreamEvent, s as InteractionResponse, ut as SkillsLockfile, v as RenderResultRequest, y as UiBridge } from "./types-7Wcg--Vh-1YlQ4jF9.js";
2
+ import { Dt as ToolDefinition, It as WebSkillApi, V as NetworkPolicy, b as ExternalSkillProvider, d as ApprovalScope, f as BridgeCapabilities, gt as ScriptExecutor, h as BridgeResponse, ht as ScriptExecutionContext, kt as ToolResult, m as BridgeRequest, tn as parseBridgeRequest, x as ExternalToolSource, zt as bridgeError } from "./index-DLfR2Y6I.js";
3
3
  //#region ../browser/dist/index.d.ts
4
4
  //#region src/fs/featureDetection.d.ts
5
5
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
package/dist/browser.js CHANGED
@@ -1,6 +1,6 @@
1
- import { C as messageOf, D as readResponseWithLimit, E as parseSkillPackManifest, M as unzipWithLimits, N as validateSkills, P as verifyManifest, T as parseSkillMarkdown, b as exportSkills, c as SkillDiscovery, d as assertRemoteUrlAllowed, h as buildManifest, i as SKILL_MANIFEST_FILE, j as resolveInsideRoot, m as buildCatalog, p as atomicWriteText, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, x as isValidSkillName } from "./dist-BQzncxXg.js";
2
- import { C as bridgeError, E as createWebSkillApi, I as normalizeToolContent, L as normalizeToolError, M as mergeCatalogEntries, N as networkPolicyLibSource, R as parseBridgeRequest, c as FsArtifactStore, h as ProgressiveRouter, i as AgentLoop, l as FsMemoryStore, m as OpenAiCompatibleClient, o as CapabilityApproval, u as FsRunSnapshotStore, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-B9VLwOME.js";
3
- import { n as MockLlmClient } from "./testing-BUoXvm1u.js";
1
+ import { A as parseSkillMarkdown, B as unzipWithLimits, H as verifyManifest, L as resolveInsideRoot, M as readResponseWithLimit, O as messageOf, T as isValidSkillName, V as validateSkills, _ as atomicWriteText, f as SkillDiscovery, h as assertRemoteUrlAllowed, j as parseSkillPackManifest, m as WebSkillError, o as SKILLS_LOCKFILE, r as MANIFEST_EXCLUDED_FILES, s as SKILL_MANIFEST_FILE, u as SKILL_PACK_FILE, v as buildCatalog, w as exportSkills, y as buildManifest } from "./dist-8oQRa8Xz.js";
2
+ import { H as normalizeToolContent, O as bridgeError, R as mergeCatalogEntries, U as normalizeToolError, W as parseBridgeRequest, _ as OpenAiCompatibleClient, d as FsRunSnapshotStore, i as AgentLoop, j as createWebSkillApi, l as FsArtifactStore, o as CapabilityApproval, u as FsMemoryStore, v as ProgressiveRouter, z as networkPolicyLibSource } from "./dist-C-Sh0MDU.js";
3
+ import { n as MockLlmClient } from "./testing-DDCJWvgA.js";
4
4
 
5
5
  //#region ../browser/dist/index.js
6
6
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -702,10 +702,9 @@ var BrowserSkillManager = class {
702
702
  }
703
703
  }
704
704
  async #createManifest(skillRoot, input) {
705
- const excluded = /* @__PURE__ */ new Set([SKILL_MANIFEST_FILE, SKILLS_LOCKFILE]);
706
705
  const files = [];
707
706
  for (const rel of (await listFiles(this.#fs, skillRoot)).sort()) {
708
- if (excluded.has(rel.split("/").pop() ?? "")) continue;
707
+ if (MANIFEST_EXCLUDED_FILES.has(rel.split("/").pop() ?? "")) continue;
709
708
  const content = await this.#fs.readBinary(`${skillRoot}/${rel}`);
710
709
  files.push({
711
710
  path: rel,
@@ -1411,10 +1410,6 @@ function startWorkerRuntimeHost(scope, overrides = {}) {
1411
1410
  (async () => {
1412
1411
  const snapshot = await state.snapshotStore.load(message.runId);
1413
1412
  if (!snapshot) throw new WebSkillError("RUN_SNAPSHOT_NOT_FOUND", `No snapshot found for run "${message.runId}"`);
1414
- if (snapshot.schemaVersion !== 1) {
1415
- await state.snapshotStore.delete(message.runId);
1416
- throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${message.runId}" has incompatible schemaVersion ${String(snapshot.schemaVersion)}`);
1417
- }
1418
1413
  if (Date.now() > Date.parse(snapshot.interactionExpiresAt)) {
1419
1414
  await state.snapshotStore.delete(message.runId);
1420
1415
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1,4 +1,6 @@
1
1
  import { a as __require, i as __reExport, n as __esmMin, o as __toCommonJS, r as __exportAll$2, s as __toESM$1, t as __commonJSMin$1 } from "./rolldown-runtime-BOF7iYI8.js";
2
+ import { m as WebSkillError } from "./dist-8oQRa8Xz.js";
3
+ import { G as mountEchart, U as chartSpecFromProps } from "./dist-D9Lcn5Pp.js";
2
4
  import minpath$1 from "node:path";
3
5
  import { fileURLToPath as urlToPath$1 } from "node:url";
4
6
  import * as React$10 from "react";
@@ -99792,32 +99794,6 @@ const sidebarMenuButtonVariants = cva("peer/menu-button flex w-full cursor-point
99792
99794
  size: "default"
99793
99795
  }
99794
99796
  });
99795
- function Select({ options, value, onChange, placeholder, disabled = false, id, label, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, className = "" }) {
99796
- return /* @__PURE__ */ jsxs("span", {
99797
- className: `relative inline-flex ${className}`,
99798
- children: [/* @__PURE__ */ jsxs("select", {
99799
- id,
99800
- value,
99801
- disabled,
99802
- "aria-label": label,
99803
- "aria-labelledby": ariaLabelledBy,
99804
- "aria-describedby": ariaDescribedBy,
99805
- onChange: (event) => onChange(event.target.value),
99806
- className: "h-9 w-full cursor-pointer appearance-none rounded-lg border border-border bg-surface pr-8 pl-3 text-sm text-ink transition-colors hover:border-ring disabled:cursor-not-allowed disabled:opacity-50",
99807
- children: [placeholder !== void 0 && /* @__PURE__ */ jsx("option", {
99808
- value: "",
99809
- disabled: true,
99810
- children: placeholder
99811
- }), options.map((option) => /* @__PURE__ */ jsx("option", {
99812
- value: option.value,
99813
- children: option.label
99814
- }, option.value))]
99815
- }), /* @__PURE__ */ jsx(ChevronDown, {
99816
- className: "pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-muted",
99817
- "aria-hidden": true
99818
- })]
99819
- });
99820
- }
99821
99797
  /**
99822
99798
  * @typedef Options
99823
99799
  * Configuration for `stringify`.
@@ -120111,75 +120087,279 @@ const MarkdownText = memo(function MarkdownText({ className }) {
120111
120087
  });
120112
120088
 
120113
120089
  //#endregion
120114
- //#region ../ui-react/dist/catalogComponents-C_V39rbF.js
120115
- function toEchartsOption(chart) {
120116
- return {
120117
- animation: false,
120118
- title: chart.title ? {
120119
- text: chart.title,
120120
- left: "center",
120121
- textStyle: { fontSize: 14 }
120122
- } : void 0,
120123
- tooltip: { trigger: chart.kind === "pie" ? "item" : "axis" },
120124
- legend: { bottom: 0 },
120125
- xAxis: chart.kind === "pie" ? void 0 : {
120126
- type: "category",
120127
- data: chart.labels
120128
- },
120129
- yAxis: chart.kind === "pie" ? void 0 : { type: "value" },
120130
- series: chart.kind === "pie" ? chart.series.map((series) => ({
120131
- type: "pie",
120132
- name: series.name,
120133
- data: chart.labels.map((label, index) => ({
120134
- name: label,
120135
- value: series.data[index] ?? 0
120136
- }))
120137
- })) : chart.series.map((series) => ({
120138
- type: chart.kind,
120139
- name: series.name,
120140
- data: series.data
120141
- }))
120142
- };
120143
- }
120144
- /** echarts 懒加载挂载点:surface 图表与 catalog 声明树的 Chart 节点共用 */
120090
+ //#region ../ui-react/dist/catalogComponents-KsujmL4b.js
120091
+ /** echarts 挂载点:surface 图表与 catalog 声明树的 Chart 节点共用,实现在 `@webskill/ui`,与 A2UI 档同一份 */
120145
120092
  function EChart({ chart }) {
120146
120093
  const containerRef = useRef(null);
120147
- const chartRef = useRef(void 0);
120148
- const [ready, setReady] = useState(false);
120094
+ const handleRef = useRef(void 0);
120149
120095
  useEffect(() => {
120150
- let disposed = false;
120151
- let observer;
120152
- import("./echarts-DhNm2ene.js").then((echarts) => {
120153
- const container = containerRef.current;
120154
- if (disposed || !container) return;
120155
- const instance = echarts.init(container);
120156
- chartRef.current = instance;
120157
- setReady(true);
120158
- if (typeof ResizeObserver !== "undefined") {
120159
- observer = new ResizeObserver(() => instance.resize());
120160
- observer.observe(container);
120161
- }
120162
- });
120096
+ const container = containerRef.current;
120097
+ if (!container) return;
120098
+ const handle = mountEchart(container);
120099
+ handleRef.current = handle;
120163
120100
  return () => {
120164
- disposed = true;
120165
- observer?.disconnect();
120166
- chartRef.current?.dispose();
120167
- chartRef.current = void 0;
120101
+ handle.dispose();
120102
+ handleRef.current = void 0;
120168
120103
  };
120169
120104
  }, []);
120170
120105
  useEffect(() => {
120171
- if (ready) chartRef.current?.setOption(toEchartsOption(chart), {
120172
- notMerge: true,
120173
- lazyUpdate: true
120174
- });
120175
- }, [ready, chart]);
120106
+ handleRef.current?.setChart(chart);
120107
+ }, [chart]);
120176
120108
  return /* @__PURE__ */ jsx("div", {
120177
120109
  className: "webskill-surface__chart",
120178
- ref: containerRef,
120179
- "aria-label": chart.title
120110
+ ref: containerRef
120180
120111
  });
120181
120112
  }
120113
+ const REQUIRED_MESSAGE = "This field is required";
120182
120114
  const str = (props, key, fallback = "") => typeof props[key] === "string" ? props[key] : fallback;
120115
+ function collectFields(node, out) {
120116
+ if (node.component === "Field" && typeof node.props?.["name"] === "string") out.push(node.props);
120117
+ for (const child of node.children ?? []) collectFields(child, out);
120118
+ }
120119
+ const isBlank = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
120120
+ function useSurfaceForm({ surfaceId, spec, actions, runId, draft, onAction, onDraftChange }) {
120121
+ const [touched, setTouched] = useState(() => ({ ...draft }));
120122
+ const [errors, setErrors] = useState({});
120123
+ const fields = useMemo(() => {
120124
+ const out = [];
120125
+ collectFields(spec, out);
120126
+ return out;
120127
+ }, [spec]);
120128
+ const values = {
120129
+ ...useMemo(() => Object.fromEntries(fields.filter((field) => field["defaultValue"] !== void 0).map((field) => [String(field["name"]), field["defaultValue"]])), [fields]),
120130
+ ...touched
120131
+ };
120132
+ const resolveActionId = (intent, nodeId) => nodeId ?? actions.find((action) => action.intent === intent)?.id ?? intent;
120133
+ const dispatch = (actionId, withValues) => {
120134
+ const declared = actions.find((action) => action.id === actionId);
120135
+ onAction?.({
120136
+ surfaceId,
120137
+ actionId,
120138
+ intent: declared?.intent ?? "select",
120139
+ ...declared?.nonce ? { nonce: declared.nonce } : {},
120140
+ ...withValues ? { value: { ...values } } : {}
120141
+ });
120142
+ };
120143
+ return {
120144
+ surfaceId,
120145
+ actions,
120146
+ values,
120147
+ errors,
120148
+ resolveActionId,
120149
+ setValue: (name, value) => {
120150
+ const next = {
120151
+ ...touched,
120152
+ [name]: value
120153
+ };
120154
+ setTouched(next);
120155
+ setErrors(({ [name]: _cleared, ...rest }) => rest);
120156
+ if (runId) onDraftChange?.({
120157
+ runId,
120158
+ surfaceId,
120159
+ value: next
120160
+ });
120161
+ },
120162
+ emit: (actionId) => dispatch(actionId, false),
120163
+ submit: (intent, nodeId) => {
120164
+ const missing = fields.filter((field) => field["required"] === true && isBlank(values[String(field["name"])]));
120165
+ if (missing.length > 0) {
120166
+ setErrors(Object.fromEntries(missing.map((field) => [String(field["name"]), REQUIRED_MESSAGE])));
120167
+ return;
120168
+ }
120169
+ setErrors({});
120170
+ dispatch(resolveActionId(intent, nodeId), true);
120171
+ }
120172
+ };
120173
+ }
120174
+ /** catalog `Field` 的唯一控件实现:8 种 type 的取值语义在这里定死 */
120175
+ function SurfaceField({ form, props }) {
120176
+ const name = str(props, "name");
120177
+ const label = str(props, "label", name);
120178
+ const type = str(props, "type", "text");
120179
+ const required = props["required"] === true;
120180
+ const value = form.values[name];
120181
+ const error = form.errors[name];
120182
+ const errorId = error ? `webskill-surface-${form.surfaceId}-${name}-error` : void 0;
120183
+ const options = Array.isArray(props["options"]) ? props["options"] : [];
120184
+ /** 控件的机器可读锚点:宿主与测试据此定位字段,不依赖标签文案 */
120185
+ const anchor = {
120186
+ "data-webskill-surface-control": name,
120187
+ ...error ? {
120188
+ "aria-invalid": true,
120189
+ "aria-describedby": errorId
120190
+ } : {}
120191
+ };
120192
+ const control = () => {
120193
+ if (type === "textarea") return /* @__PURE__ */ jsx(Textarea, {
120194
+ id: name,
120195
+ ...anchor,
120196
+ invalid: Boolean(error),
120197
+ value: typeof value === "string" ? value : "",
120198
+ onChange: (event) => form.setValue(name, event.target.value)
120199
+ });
120200
+ if (type === "toggle") return /* @__PURE__ */ jsx(Switch, {
120201
+ id: name,
120202
+ ...anchor,
120203
+ checked: value === true,
120204
+ onCheckedChange: (next) => form.setValue(name, next)
120205
+ });
120206
+ if (type === "select" || type === "multi-select") {
120207
+ const multiple = type === "multi-select";
120208
+ const indexOf = (candidate) => {
120209
+ const index = options.findIndex((option) => option.value === candidate);
120210
+ return index < 0 ? "" : String(index);
120211
+ };
120212
+ return /* @__PURE__ */ jsx("select", {
120213
+ id: name,
120214
+ ...anchor,
120215
+ multiple,
120216
+ value: multiple ? Array.isArray(value) ? value.map(indexOf) : [] : indexOf(value),
120217
+ onChange: (event) => {
120218
+ const pick = (index) => options[Number(index)].value;
120219
+ form.setValue(name, multiple ? [...event.target.selectedOptions].map((option) => pick(option.value)) : pick(event.target.value));
120220
+ },
120221
+ children: options.map((option, index) => /* @__PURE__ */ jsx("option", {
120222
+ value: String(index),
120223
+ children: option.label
120224
+ }, index))
120225
+ });
120226
+ }
120227
+ if (type === "file") return /* @__PURE__ */ jsx("input", {
120228
+ id: name,
120229
+ ...anchor,
120230
+ type: "file",
120231
+ onChange: (event) => form.setValue(name, event.target.files?.[0]?.name ?? "")
120232
+ });
120233
+ return /* @__PURE__ */ jsx(Input, {
120234
+ id: name,
120235
+ ...anchor,
120236
+ invalid: Boolean(error),
120237
+ type: type === "number" ? "number" : type === "date" ? "date" : "text",
120238
+ value: typeof value === "string" || typeof value === "number" ? String(value) : "",
120239
+ onChange: (event) => form.setValue(name, type === "number" ? Number(event.target.value) : event.target.value)
120240
+ });
120241
+ };
120242
+ return /* @__PURE__ */ jsxs("label", {
120243
+ className: "flex flex-col gap-1 text-sm",
120244
+ htmlFor: name,
120245
+ children: [
120246
+ /* @__PURE__ */ jsxs("span", {
120247
+ className: "text-ink",
120248
+ children: [label, required ? " *" : ""]
120249
+ }),
120250
+ control(),
120251
+ props["description"] ? /* @__PURE__ */ jsx("span", {
120252
+ className: "text-xs text-muted",
120253
+ children: str(props, "description")
120254
+ }) : null,
120255
+ error ? /* @__PURE__ */ jsx("small", {
120256
+ id: errorId,
120257
+ role: "alert",
120258
+ className: "text-xs text-destructive",
120259
+ children: error
120260
+ }) : null
120261
+ ]
120262
+ });
120263
+ }
120264
+ /** catalog `Form` 的提交 / 取消按钮:提交带表单值,取消不带 */
120265
+ function SurfaceFormButtons({ form, props }) {
120266
+ return /* @__PURE__ */ jsxs("div", {
120267
+ className: "flex gap-2",
120268
+ children: [/* @__PURE__ */ jsx(Button, {
120269
+ type: "submit",
120270
+ size: "sm",
120271
+ "data-webskill-surface-action": form.resolveActionId("submit"),
120272
+ children: str(props, "submitLabel", "Submit")
120273
+ }), props["cancelLabel"] ? /* @__PURE__ */ jsx(Button, {
120274
+ type: "button",
120275
+ size: "sm",
120276
+ variant: "secondary",
120277
+ "data-webskill-surface-action": form.resolveActionId("cancel"),
120278
+ onClick: () => form.emit(form.resolveActionId("cancel")),
120279
+ children: str(props, "cancelLabel")
120280
+ }) : null]
120281
+ });
120282
+ }
120283
+ /** catalog `Button` 的唯一实现:submit 走带值提交,其余走裸动作 */
120284
+ function SurfaceButton({ form, props, nodeId }) {
120285
+ const intent = str(props, "action");
120286
+ const actionId = form.resolveActionId(intent, nodeId);
120287
+ return /* @__PURE__ */ jsx(Button, {
120288
+ size: "sm",
120289
+ type: "button",
120290
+ disabled: props["disabled"] === true,
120291
+ "data-webskill-surface-action": actionId,
120292
+ onClick: () => intent === "submit" ? form.submit(intent, nodeId) : form.emit(actionId),
120293
+ children: str(props, "label")
120294
+ });
120295
+ }
120296
+ /**
120297
+ * json-render 与 OpenUI 两档的 Renderer 都只把 props 交给组件实现,拿不到节点 id,
120298
+ * 也拿不到表单状态,因此表单值与动作回传统一走这个 Context。
120299
+ * 缺省值故意不给空实现:没包 Provider 就是宿主接线错了,必须失败而不是“点了没反应”。
120300
+ */
120301
+ const CatalogSurfaceContext = createContext(void 0);
120302
+ function useCatalogSurfaceForm() {
120303
+ const form = useContext(CatalogSurfaceContext);
120304
+ if (!form) throw new WebSkillError("UI_UNAVAILABLE", "Catalog surface components must be rendered inside a CatalogSurfaceProvider");
120305
+ return form;
120306
+ }
120307
+ /** 非 native 档的表单状态宿主:状态必须在 Renderer 之上,否则每次重渲染都会丢值 */
120308
+ function CatalogSurfaceProvider({ surfaceId, spec, actions, onAction, children }) {
120309
+ const form = useSurfaceForm({
120310
+ surfaceId,
120311
+ spec,
120312
+ actions,
120313
+ onAction
120314
+ });
120315
+ return /* @__PURE__ */ jsx(CatalogSurfaceContext.Provider, {
120316
+ value: form,
120317
+ children
120318
+ });
120319
+ }
120320
+ /** 把实现表的条目包成真正的 React 组件,使其内部的 hook 调用处于自己的渲染周期 */
120321
+ function CatalogNode({ name, props, children }) {
120322
+ const Impl = catalogComponentImpls[name];
120323
+ if (!Impl) return null;
120324
+ return /* @__PURE__ */ jsx(Impl, {
120325
+ props,
120326
+ children
120327
+ });
120328
+ }
120329
+ function CatalogForm({ props, children }) {
120330
+ const form = useCatalogSurfaceForm();
120331
+ return /* @__PURE__ */ jsxs("form", {
120332
+ className: "flex flex-col gap-3",
120333
+ noValidate: true,
120334
+ onSubmit: (event) => {
120335
+ event.preventDefault();
120336
+ form.submit("submit");
120337
+ },
120338
+ children: [
120339
+ props["title"] ? /* @__PURE__ */ jsx("h3", {
120340
+ className: "text-sm font-semibold text-ink",
120341
+ children: str(props, "title")
120342
+ }) : null,
120343
+ children,
120344
+ /* @__PURE__ */ jsx(SurfaceFormButtons, {
120345
+ form,
120346
+ props
120347
+ })
120348
+ ]
120349
+ });
120350
+ }
120351
+ function CatalogField({ props }) {
120352
+ return /* @__PURE__ */ jsx(SurfaceField, {
120353
+ form: useCatalogSurfaceForm(),
120354
+ props
120355
+ });
120356
+ }
120357
+ function CatalogButton({ props }) {
120358
+ return /* @__PURE__ */ jsx(SurfaceButton, {
120359
+ form: useCatalogSurfaceForm(),
120360
+ props
120361
+ });
120362
+ }
120183
120363
  /**
120184
120364
  * catalog 16 个组件的展示实现:json-render 与 OpenUI 两档共用同一份,
120185
120365
  * 避免"一个 catalog、多套 registry"退化成多套各自演进的视觉。
@@ -120240,50 +120420,26 @@ const catalogComponentImpls = {
120240
120420
  const rows = Array.isArray(props["rows"]) ? props["rows"] : [];
120241
120421
  return /* @__PURE__ */ jsxs(DataTable, {
120242
120422
  "aria-label": str(props, "title", "Data"),
120243
- children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("th", {
120244
- scope: "col",
120245
- children: column
120246
- }, column)) }) }), /* @__PURE__ */ jsx("tbody", { children: rows.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, cellIndex) => /* @__PURE__ */ jsx("td", { children: String(cell ?? "") }, cellIndex)) }, rowIndex)) })]
120423
+ children: [/* @__PURE__ */ jsx("thead", {
120424
+ className: "webskill-surface__table-header",
120425
+ children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("th", {
120426
+ scope: "col",
120427
+ children: column
120428
+ }, column)) })
120429
+ }), /* @__PURE__ */ jsx("tbody", {
120430
+ className: "webskill-surface__table-body",
120431
+ children: rows.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, cellIndex) => /* @__PURE__ */ jsx("td", { children: String(cell ?? "") }, cellIndex)) }, rowIndex))
120432
+ })]
120247
120433
  });
120248
120434
  },
120249
- Chart: ({ props }) => {
120250
- const series = Array.isArray(props["series"]) ? props["series"] : [];
120251
- return /* @__PURE__ */ jsx(EChart, { chart: {
120252
- kind: str(props, "type", "bar"),
120253
- labels: Array.isArray(props["labels"]) ? props["labels"] : [],
120254
- series: series.map((item) => ({
120255
- name: item.name,
120256
- data: item.values
120257
- })),
120258
- ...props["title"] ? { title: str(props, "title") } : {}
120259
- } });
120260
- },
120435
+ Chart: ({ props }) => /* @__PURE__ */ jsx(EChart, { chart: chartSpecFromProps(props) }),
120261
120436
  FileLink: ({ props }) => /* @__PURE__ */ jsx("span", {
120262
120437
  className: "text-sm text-ink",
120263
120438
  children: str(props, "label", str(props, "path"))
120264
120439
  }),
120265
- Form: ({ props, children }) => /* @__PURE__ */ jsxs("form", {
120266
- className: "flex flex-col gap-3",
120267
- children: [props["title"] ? /* @__PURE__ */ jsx("h3", {
120268
- className: "text-sm font-semibold text-ink",
120269
- children: str(props, "title")
120270
- }) : null, children]
120271
- }),
120272
- Field: ({ props }) => {
120273
- const name = str(props, "name");
120274
- return /* @__PURE__ */ jsxs("label", {
120275
- className: "flex flex-col gap-1 text-sm",
120276
- htmlFor: name,
120277
- children: [/* @__PURE__ */ jsx("span", {
120278
- className: "text-ink",
120279
- children: str(props, "label", name)
120280
- }), str(props, "type") === "textarea" ? /* @__PURE__ */ jsx(Textarea, { id: name }) : /* @__PURE__ */ jsx(Input, { id: name })]
120281
- });
120282
- },
120283
- Button: ({ props }) => /* @__PURE__ */ jsx(Button, {
120284
- size: "sm",
120285
- children: str(props, "label")
120286
- }),
120440
+ Form: CatalogForm,
120441
+ Field: CatalogField,
120442
+ Button: CatalogButton,
120287
120443
  Alert: ({ props }) => /* @__PURE__ */ jsxs("div", {
120288
120444
  role: "alert",
120289
120445
  className: "rounded-md border border-border p-2 text-sm",
@@ -120300,4 +120456,4 @@ const catalogComponentImpls = {
120300
120456
  };
120301
120457
 
120302
120458
  //#endregion
120303
- export { Checkbox as a, DropdownMenuCheckboxItem as c, Input as d, Markdown as f, Textarea as g, Switch as h, Button as i, DropdownMenuContent as l, Separator as m, catalogComponentImpls as n, DataTable as o, Select as p, Badge as r, DropdownMenu as s, EChart as t, DropdownMenuTrigger as u };
120459
+ export { DropdownMenuContent as _, SurfaceField as a, Markdown as b, str as c, Badge as d, Button as f, DropdownMenuCheckboxItem as g, DropdownMenu as h, SurfaceButton as i, useCatalogSurfaceForm as l, DataTable as m, CatalogSurfaceProvider as n, SurfaceFormButtons as o, Checkbox as p, EChart as r, catalogComponentImpls as s, CatalogNode as t, useSurfaceForm as u, DropdownMenuTrigger as v, Separator as x, Input as y };