@webskill/sdk 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js CHANGED
@@ -1107,4 +1107,458 @@ async function connectRemoteEndpoint(registry, config) {
1107
1107
  }
1108
1108
 
1109
1109
  //#endregion
1110
- export { EndpointRegistry, ExperimentalWebMcpAdapter, McpRuntimePlugin, McpToolResolver, MessageChannelTransport, PAGE_HOST_ANCHOR_KEY, TemporarySkillProvider, WEB_MCP_SOURCE_ID_MAX, catalogMerge, connectRemoteEndpoint, createMemoryOAuthStores, createOAuthProvider, declareDataSourcesToPageHost, endpointToolLlmName, notifyPageHost, parseEndpointToolLlmName, parseWebMcpToolLlmName, serveSkillAsMcp, validateJsonRpcMessage, webMcpToolLlmName };
1110
+ //#region ../mcp/src/pageHost/mcpVisibilityStore.ts
1111
+ /** WebMCP 在名单里占用的保留端点名 */
1112
+ const WEB_MCP_ENDPOINT = "webmcp";
1113
+ const toolKey = (endpoint, tool) => `${endpoint}/${tool}`;
1114
+ const readList = (storage, key) => {
1115
+ try {
1116
+ const raw = storage?.getItem(key);
1117
+ const parsed = raw ? JSON.parse(raw) : [];
1118
+ return new Set(Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : []);
1119
+ } catch {
1120
+ return /* @__PURE__ */ new Set();
1121
+ }
1122
+ };
1123
+ const writeList = (storage, key, list) => {
1124
+ try {
1125
+ storage?.setItem(key, JSON.stringify([...list]));
1126
+ } catch {}
1127
+ };
1128
+ const toggle = (list, key, present) => {
1129
+ if (present) list.add(key);
1130
+ else list.delete(key);
1131
+ return list;
1132
+ };
1133
+ /**
1134
+ * localStorage 默认实现。键形:
1135
+ * `<ns>.mcp.endpoints-disabled` / `<ns>.mcp.tools-disabled` /
1136
+ * `<ns>.mcp.tools-on-demand` / `<ns>.webmcp-enabled`。
1137
+ *
1138
+ * 名单里的工具**一律带 endpoint**(`<endpoint>/<tool>`)。旧实现存裸工具名,
1139
+ * 两个端点注册同名工具时禁用其一会连坐另一个——那个缺陷在这里一次修完。
1140
+ *
1141
+ * 一律实时读存储、不做进程内缓存:console 改完当轮生效是既有语义。
1142
+ */
1143
+ function createLocalStorageMcpVisibility(namespace) {
1144
+ const endpointsKey = `${namespace}.mcp.endpoints-disabled`;
1145
+ const toolsKey = `${namespace}.mcp.tools-disabled`;
1146
+ const onDemandKey = `${namespace}.mcp.tools-on-demand`;
1147
+ const webMcpKey = `${namespace}.webmcp-enabled`;
1148
+ const storage = () => {
1149
+ try {
1150
+ return globalThis.localStorage;
1151
+ } catch {
1152
+ return;
1153
+ }
1154
+ };
1155
+ return {
1156
+ isEndpointEnabled: (endpoint) => !readList(storage(), endpointsKey).has(endpoint),
1157
+ setEndpointEnabled: (endpoint, enabled) => writeList(storage(), endpointsKey, toggle(readList(storage(), endpointsKey), endpoint, !enabled)),
1158
+ isToolEnabled: (endpoint, tool) => !readList(storage(), toolsKey).has(toolKey(endpoint, tool)),
1159
+ setToolEnabled: (endpoint, tool, enabled) => writeList(storage(), toolsKey, toggle(readList(storage(), toolsKey), toolKey(endpoint, tool), !enabled)),
1160
+ toolDisclosure: (endpoint, tool) => readList(storage(), onDemandKey).has(toolKey(endpoint, tool)) ? "on-demand" : "always",
1161
+ setToolDisclosure: (endpoint, tool, value) => writeList(storage(), onDemandKey, toggle(readList(storage(), onDemandKey), toolKey(endpoint, tool), value === "on-demand")),
1162
+ isWebMcpEnabled: () => {
1163
+ try {
1164
+ return storage()?.getItem(webMcpKey) !== "0";
1165
+ } catch {
1166
+ return true;
1167
+ }
1168
+ },
1169
+ setWebMcpEnabled: (enabled) => {
1170
+ try {
1171
+ storage()?.setItem(webMcpKey, enabled ? "1" : "0");
1172
+ } catch {}
1173
+ }
1174
+ };
1175
+ }
1176
+
1177
+ //#endregion
1178
+ //#region ../mcp/src/pageHost/createPageMcpEndpoint.ts
1179
+ const resolve = (value) => value === void 0 ? [] : typeof value === "function" ? value() : value;
1180
+ /** 裸返回值 → MCP 响应。structuredContent 只在返回值是非数组对象时给出(规范用 z.record) */
1181
+ function toMcpResult(value) {
1182
+ const content = [{
1183
+ type: "text",
1184
+ text: JSON.stringify(value ?? null)
1185
+ }];
1186
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? {
1187
+ content,
1188
+ structuredContent: value
1189
+ } : { content };
1190
+ }
1191
+ /** zod 为 optional peer:与 serveSkillAsMcp 同款懒加载 */
1192
+ async function toZodShape(schema) {
1193
+ const { fromJSONSchema } = await import("zod");
1194
+ return fromJSONSchema(schema);
1195
+ }
1196
+ const defaultWebMcpApi = () => {
1197
+ const doc = globalThis.document;
1198
+ const nav = globalThis.navigator;
1199
+ return doc?.modelContext ?? nav?.modelContext;
1200
+ };
1201
+ function createPageMcpEndpoint(options) {
1202
+ const { endpoint } = options;
1203
+ const visibility = options.visibility ?? createLocalStorageMcpVisibility(endpoint);
1204
+ const registry = new EndpointRegistry();
1205
+ const pageSkills = new TemporarySkillProvider({
1206
+ registry,
1207
+ endpoint
1208
+ });
1209
+ const webMcpList = options.webMcp === false ? [] : options.webMcp === void 0 ? [new ExperimentalWebMcpAdapter(defaultWebMcpApi, { enabled: visibility.isWebMcpEnabled() })] : options.webMcp instanceof ExperimentalWebMcpAdapter ? [options.webMcp] : options.webMcp;
1210
+ const webmcp = webMcpList[0];
1211
+ const plugin = new McpRuntimePlugin({
1212
+ registry,
1213
+ webMcp: webMcpList,
1214
+ endpoints: [endpoint],
1215
+ ...options.onWarning ? { onWarning: options.onWarning } : {},
1216
+ ...options.trust ? { trust: options.trust } : {},
1217
+ visibility: {
1218
+ isEndpointEnabled: (name) => visibility.isEndpointEnabled(name),
1219
+ isEndpointToolEnabled: (name, tool) => visibility.isToolEnabled(name, tool),
1220
+ isWebMcpToolEnabled: (tool) => visibility.isWebMcpEnabled() && visibility.isToolEnabled("webmcp", tool),
1221
+ endpointToolDisclosure: (name, tool) => {
1222
+ const value = visibility.toolDisclosure(name, tool);
1223
+ return value === "always" ? void 0 : value;
1224
+ },
1225
+ webMcpToolDisclosure: (tool) => {
1226
+ const value = visibility.toolDisclosure(WEB_MCP_ENDPOINT, tool);
1227
+ return value === "always" ? void 0 : value;
1228
+ }
1229
+ }
1230
+ });
1231
+ const toolSource = {
1232
+ kind: "mcp",
1233
+ listToolSpecs: () => plugin.listToolSpecs(),
1234
+ canHandle: (name) => plugin.canHandle(name),
1235
+ call: (name, args) => plugin.call(name, args),
1236
+ argCaptureTrust: (name) => plugin.argCaptureTrust(name),
1237
+ listSkills: () => pageSkills.listSkills(),
1238
+ loadSkill: (name) => pageSkills.loadSkill(name),
1239
+ readFile: (name, path) => pageSkills.readFile(name, path),
1240
+ ...options.systemPrompt ? { systemPrompt: options.systemPrompt } : {}
1241
+ };
1242
+ let current;
1243
+ let serving;
1244
+ async function serveNow() {
1245
+ const [{ McpServer: McpServerCtor }, { Client: ClientCtor }] = await Promise.all([import("@modelcontextprotocol/sdk/server/mcp.js"), import("@modelcontextprotocol/sdk/client/index.js")]);
1246
+ const server = new McpServerCtor({
1247
+ name: options.serverInfo?.name ?? endpoint,
1248
+ version: options.serverInfo?.version ?? "1.0.0"
1249
+ });
1250
+ for (const tool of resolve(options.tools)) server.registerTool(tool.spec.name, {
1251
+ description: tool.spec.description ?? "",
1252
+ ...tool.spec.inputSchema ? { inputSchema: await toZodShape(tool.spec.inputSchema) } : {}
1253
+ }, async (args) => toMcpResult(await tool.run(args ?? {})));
1254
+ for (const skill of resolve(options.skills)) await serveSkillAsMcp(server, skill);
1255
+ const channel = new MessageChannel();
1256
+ await server.connect(new MessageChannelTransport(channel.port2));
1257
+ const client = new ClientCtor({
1258
+ name: options.clientInfo?.name ?? `${endpoint}-host`,
1259
+ version: options.clientInfo?.version ?? "1.0.0"
1260
+ });
1261
+ await client.connect(new MessageChannelTransport(channel.port1));
1262
+ registry.set(endpoint, client);
1263
+ const prev = current;
1264
+ current = {
1265
+ server,
1266
+ client
1267
+ };
1268
+ if (prev) {
1269
+ await prev.client.close().catch(() => void 0);
1270
+ await prev.server.close().catch(() => void 0);
1271
+ }
1272
+ }
1273
+ function serve() {
1274
+ serving = (serving ?? Promise.resolve()).catch(() => void 0).then(() => serveNow());
1275
+ return serving;
1276
+ }
1277
+ return {
1278
+ endpoint,
1279
+ registry,
1280
+ plugin,
1281
+ pageSkills,
1282
+ webmcp,
1283
+ webMcpSources: webMcpList,
1284
+ toolSource,
1285
+ visibility,
1286
+ ready: serve().catch(() => void 0),
1287
+ serve,
1288
+ async close() {
1289
+ const prev = current;
1290
+ current = void 0;
1291
+ registry.unregister(endpoint);
1292
+ if (prev) {
1293
+ await prev.client.close().catch(() => void 0);
1294
+ await prev.server.close().catch(() => void 0);
1295
+ }
1296
+ }
1297
+ };
1298
+ }
1299
+
1300
+ //#endregion
1301
+ //#region ../mcp/src/pageHost/createConnectFacade.ts
1302
+ const SCHEMA_SUMMARY_MAX = 120;
1303
+ /** localStorage 版远程端点存储(键 `<ns>.mcp.remote-endpoints`) */
1304
+ function createLocalStorageRemoteEndpoints(namespace) {
1305
+ const key = `${namespace}.mcp.remote-endpoints`;
1306
+ return {
1307
+ load() {
1308
+ try {
1309
+ const raw = globalThis.localStorage?.getItem(key);
1310
+ const parsed = raw ? JSON.parse(raw) : [];
1311
+ return Array.isArray(parsed) ? parsed : [];
1312
+ } catch {
1313
+ return [];
1314
+ }
1315
+ },
1316
+ save(configs) {
1317
+ try {
1318
+ globalThis.localStorage?.setItem(key, JSON.stringify(configs));
1319
+ } catch {}
1320
+ }
1321
+ };
1322
+ }
1323
+ function createConnectFacade(options) {
1324
+ const host = options.host;
1325
+ const builtin = host.endpoint;
1326
+ const visibility = host.visibility;
1327
+ const store = options.remoteEndpoints === false ? void 0 : options.remoteEndpoints ?? void 0;
1328
+ let remoteConfigs = store?.load() ?? [];
1329
+ const connections = /* @__PURE__ */ new Map();
1330
+ const unsupported = (what) => {
1331
+ throw new WebSkillError("TOOL_UNSUPPORTED", `${what} is not supported by this host: it does not persist remote MCP endpoints.`);
1332
+ };
1333
+ const persist = () => store?.save(remoteConfigs);
1334
+ /**
1335
+ * 配置对象上的 `disabledTools` 是**镜像**:唯一写入源是 `visibility`。
1336
+ * 两处各写一份的话,「治理在工作」就得靠手工保持同步。
1337
+ */
1338
+ const mirrorDisabledTools = (config, tools) => {
1339
+ const disabled = tools.filter((tool) => !visibility.isToolEnabled(config.name, tool));
1340
+ if (disabled.length > 0) config.disabledTools = disabled;
1341
+ else delete config.disabledTools;
1342
+ };
1343
+ const listOf = async (endpoint) => {
1344
+ const { tools } = await host.registry.get(endpoint).listTools();
1345
+ return tools;
1346
+ };
1347
+ const toolView = (endpoint, tool) => ({
1348
+ endpoint,
1349
+ name: tool.name,
1350
+ enabled: visibility.isToolEnabled(endpoint, tool.name),
1351
+ disclosure: visibility.toolDisclosure(endpoint, tool.name),
1352
+ ...tool.description !== void 0 ? { description: tool.description } : {},
1353
+ ...tool.inputSchema !== void 0 ? {
1354
+ schemaSummary: JSON.stringify(tool.inputSchema).slice(0, SCHEMA_SUMMARY_MAX),
1355
+ inputSchema: tool.inputSchema
1356
+ } : {}
1357
+ });
1358
+ async function connectRemote(config) {
1359
+ const prev = connections.get(config.name);
1360
+ if (prev) await prev.close().catch(() => void 0);
1361
+ connections.set(config.name, {
1362
+ close: () => Promise.resolve(),
1363
+ status: "connecting"
1364
+ });
1365
+ try {
1366
+ const handle = await connectRemoteEndpoint(host.registry, {
1367
+ endpoint: config.name,
1368
+ url: config.url,
1369
+ ...config.transport ? { transport: config.transport } : {},
1370
+ ...config.headers ? { headers: config.headers } : {},
1371
+ allowHttp: config.allowHttp ?? false,
1372
+ allowPrivateHosts: config.allowPrivateHosts ?? false
1373
+ });
1374
+ connections.set(config.name, {
1375
+ close: handle.close,
1376
+ status: "connected"
1377
+ });
1378
+ try {
1379
+ const tools = await listOf(config.name);
1380
+ connections.set(config.name, {
1381
+ close: handle.close,
1382
+ status: "connected",
1383
+ toolCount: tools.length
1384
+ });
1385
+ } catch {}
1386
+ } catch (e) {
1387
+ connections.set(config.name, {
1388
+ close: () => Promise.resolve(),
1389
+ status: "failed",
1390
+ error: messageOf(e)
1391
+ });
1392
+ }
1393
+ }
1394
+ for (const config of remoteConfigs) connectRemote(config);
1395
+ const endpointView = (config) => {
1396
+ const conn = connections.get(config.name);
1397
+ return {
1398
+ config: {
1399
+ ...config,
1400
+ enabled: visibility.isEndpointEnabled(config.name)
1401
+ },
1402
+ status: conn?.status ?? "unavailable",
1403
+ ...conn?.error !== void 0 ? { error: conn.error } : {},
1404
+ ...conn?.toolCount !== void 0 ? { toolCount: conn.toolCount } : {}
1405
+ };
1406
+ };
1407
+ /**
1408
+ * 一个 WebMCP 来源的视图。多来源时逐个建、**不合并**——
1409
+ * 合并后「部分来源可用」这个问题就没法回答了(分册 26 FR-26.5)。
1410
+ */
1411
+ const webMcpSourceOf = (adapter) => ({
1412
+ ...adapter.sourceId !== void 0 ? {
1413
+ id: adapter.sourceId,
1414
+ label: adapter.sourceId
1415
+ } : {},
1416
+ isAvailable: () => adapter.isAvailable(),
1417
+ isEnabled: () => visibility.isWebMcpEnabled() && adapter.isEnabled(),
1418
+ setEnabled: (on) => {
1419
+ adapter.setEnabled(on);
1420
+ visibility.setWebMcpEnabled(on);
1421
+ },
1422
+ listTools: async () => {
1423
+ if (!adapter.isEnabled() || !visibility.isWebMcpEnabled()) return [];
1424
+ return (await adapter.listTools() ?? []).map((tool) => ({
1425
+ endpoint: WEB_MCP_ENDPOINT,
1426
+ name: tool.name,
1427
+ enabled: visibility.isToolEnabled(WEB_MCP_ENDPOINT, tool.name),
1428
+ disclosure: visibility.toolDisclosure(WEB_MCP_ENDPOINT, tool.name),
1429
+ ...tool.description !== void 0 ? { description: tool.description } : {},
1430
+ ...tool.origin !== void 0 ? { origin: tool.origin } : {},
1431
+ ...tool.annotations !== void 0 ? { annotations: tool.annotations } : {}
1432
+ }));
1433
+ },
1434
+ setToolEnabled: (tool, enabled) => visibility.setToolEnabled(WEB_MCP_ENDPOINT, tool, enabled),
1435
+ setToolDisclosure: (tool, disclosure) => visibility.setToolDisclosure(WEB_MCP_ENDPOINT, tool, disclosure)
1436
+ });
1437
+ const sources = host.webMcpSources.map(webMcpSourceOf);
1438
+ /**
1439
+ * 装配期建一次并复用:每次读都新建对象的话,React 侧把它放进 `useMemo`
1440
+ * 依赖表就会每帧重算——引用不稳定在这个仓库里烧出过无限重渲染。
1441
+ */
1442
+ const webmcp = sources.length > 1 ? {
1443
+ ...sources[0],
1444
+ sources
1445
+ } : sources[0] ?? {
1446
+ isAvailable: () => false,
1447
+ isEnabled: () => false,
1448
+ setEnabled: () => void 0,
1449
+ listTools: async () => []
1450
+ };
1451
+ return {
1452
+ async listEndpoints() {
1453
+ const builtinTools = await listOf(builtin).catch(() => []);
1454
+ const builtinConfig = {
1455
+ name: builtin,
1456
+ url: options.builtinEndpointUrl ?? `in-process://${builtin}`,
1457
+ enabled: visibility.isEndpointEnabled(builtin)
1458
+ };
1459
+ mirrorDisabledTools(builtinConfig, builtinTools.map((t) => t.name));
1460
+ return [{
1461
+ config: builtinConfig,
1462
+ status: "connected",
1463
+ toolCount: builtinTools.length
1464
+ }, ...remoteConfigs.map(endpointView)];
1465
+ },
1466
+ async addEndpoint(config) {
1467
+ if (!store) unsupported("Adding an endpoint by hand");
1468
+ if (config.name === builtin || remoteConfigs.some((c) => c.name === config.name)) throw new WebSkillError("VALIDATION_FAILED", `Endpoint "${config.name}" is already configured`);
1469
+ remoteConfigs = [...remoteConfigs, config];
1470
+ persist();
1471
+ await connectRemote(config);
1472
+ const conn = connections.get(config.name);
1473
+ if (conn?.status === "failed") throw new WebSkillError("MCP_ENDPOINT_UNAVAILABLE", conn.error ?? `Failed to connect endpoint "${config.name}"`);
1474
+ },
1475
+ async removeEndpoint(name) {
1476
+ if (!store) unsupported("Removing an endpoint by hand");
1477
+ if (name === builtin) throw new WebSkillError("VALIDATION_FAILED", `The built-in "${builtin}" endpoint cannot be removed: it is this page itself.`);
1478
+ const conn = connections.get(name);
1479
+ if (conn) {
1480
+ await conn.close().catch(() => void 0);
1481
+ connections.delete(name);
1482
+ }
1483
+ host.registry.unregister(name);
1484
+ remoteConfigs = remoteConfigs.filter((c) => c.name !== name);
1485
+ persist();
1486
+ },
1487
+ async reconnect(name) {
1488
+ if (name === builtin) {
1489
+ await host.serve();
1490
+ return;
1491
+ }
1492
+ const config = remoteConfigs.find((c) => c.name === name);
1493
+ if (!config) throw new WebSkillError("VALIDATION_FAILED", `Endpoint "${name}" is not configured`);
1494
+ await connectRemote(config);
1495
+ },
1496
+ async listTools() {
1497
+ const out = [];
1498
+ for (const tool of await listOf(builtin).catch(() => [])) out.push(toolView(builtin, tool));
1499
+ for (const config of remoteConfigs) {
1500
+ if (connections.get(config.name)?.status !== "connected") continue;
1501
+ for (const tool of await listOf(config.name).catch(() => [])) out.push(toolView(config.name, tool));
1502
+ }
1503
+ return out;
1504
+ },
1505
+ async testEndpoint(name) {
1506
+ const start = Date.now();
1507
+ try {
1508
+ await listOf(name);
1509
+ return {
1510
+ ok: true,
1511
+ latencyMs: Date.now() - start,
1512
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
1513
+ };
1514
+ } catch (e) {
1515
+ return {
1516
+ ok: false,
1517
+ latencyMs: Date.now() - start,
1518
+ error: messageOf(e),
1519
+ detail: e instanceof Error ? e.stack ?? e.message : String(e),
1520
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
1521
+ };
1522
+ }
1523
+ },
1524
+ async temporarySkills() {
1525
+ if (options.temporarySkills) return options.temporarySkills();
1526
+ return (await host.pageSkills.listSkills().catch(() => [])).map((skill) => ({
1527
+ name: skill.name,
1528
+ ...skill.description !== void 0 ? { description: skill.description } : {},
1529
+ source: skill.source ?? builtin,
1530
+ ...skill.root !== void 0 ? { origin: skill.root } : {}
1531
+ }));
1532
+ },
1533
+ ...options.temporarySkillDetail ? { temporarySkillDetail: async (name, origin) => options.temporarySkillDetail(name, origin) } : {},
1534
+ ...options.pagePerception ? { pagePerception: async () => options.pagePerception() } : {},
1535
+ ...options.pageActionConsent ? {
1536
+ pageActionConsents: () => options.pageActionConsent.list(),
1537
+ forgetPageActionConsent: (id) => options.pageActionConsent.forget(id),
1538
+ forgetPageActionConsentScope: (scope) => options.pageActionConsent.forgetScope(scope)
1539
+ } : {},
1540
+ async setEndpointEnabled(name, enabled) {
1541
+ visibility.setEndpointEnabled(name, enabled);
1542
+ const config = remoteConfigs.find((c) => c.name === name);
1543
+ if (config) {
1544
+ config.enabled = enabled;
1545
+ persist();
1546
+ }
1547
+ },
1548
+ async setToolEnabled(endpoint, tool, enabled) {
1549
+ visibility.setToolEnabled(endpoint, tool, enabled);
1550
+ const config = remoteConfigs.find((c) => c.name === endpoint);
1551
+ if (config) {
1552
+ mirrorDisabledTools(config, (await listOf(endpoint).catch(() => [])).map((t) => t.name));
1553
+ persist();
1554
+ }
1555
+ },
1556
+ async setToolDisclosure(endpoint, tool, disclosure) {
1557
+ visibility.setToolDisclosure(endpoint, tool, disclosure);
1558
+ },
1559
+ webmcp
1560
+ };
1561
+ }
1562
+
1563
+ //#endregion
1564
+ export { EndpointRegistry, ExperimentalWebMcpAdapter, McpRuntimePlugin, McpToolResolver, MessageChannelTransport, PAGE_HOST_ANCHOR_KEY, TemporarySkillProvider, WEB_MCP_ENDPOINT, WEB_MCP_SOURCE_ID_MAX, catalogMerge, connectRemoteEndpoint, createConnectFacade, createLocalStorageMcpVisibility, createLocalStorageRemoteEndpoints, createMemoryOAuthStores, createOAuthProvider, createPageMcpEndpoint, declareDataSourcesToPageHost, endpointToolLlmName, notifyPageHost, parseEndpointToolLlmName, parseWebMcpToolLlmName, serveSkillAsMcp, validateJsonRpcMessage, webMcpToolLlmName };
@@ -29,7 +29,8 @@ const DEFAULT_SURFACE_FORM_TEXTS = {
29
29
  readOnlySnapshot: "This is a saved record — its actions are no longer available.",
30
30
  suggested: DEFAULT_INTERACTION_TEXTS.suggested,
31
31
  useSuggestion: DEFAULT_INTERACTION_TEXTS.useSuggestion,
32
- openDocument: "Open document"
32
+ openDocument: "Open document",
33
+ noOptions: "No options are available for this field."
33
34
  };
34
35
  function resolveSurfaceFormTexts(texts) {
35
36
  return texts ? {
package/dist/node.js CHANGED
@@ -2,7 +2,7 @@ import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
2
2
  import { A as SKILL_MANIFEST_FILE, D as verifySkillSignature, M as buildManifest, O as MANIFEST_EXCLUDED_FILES, P as verifyManifest, b as parseSkillMarkdown, c as unzipWithLimits, f as SKILL_PACK_FILE, k as SKILLS_LOCKFILE, m as parseSkillPackManifest, o as readResponseWithLimit, p as exportSkills, s as resolveArchiveLimits, t as validateSkills, w as readSkillSignature, y as isValidSkillName } from "./skill-CAJMsLod.js";
3
3
  import { a as atomicWriteText, o as isAtomicTempPath, r as resolveInsideRoot, t as assertSafePathSegment } from "./pathSecurity-B1owvJAF.js";
4
4
  import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
5
- import { B as createScriptContext, a as networkPolicyLibSource, c as bridgeError, d as FsMemoryStore, f as WebSkillRuntime, l as parseBridgeRequest, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-CBPnGPIm.js";
5
+ import { B as createScriptContext, a as networkPolicyLibSource, c as bridgeError, d as FsMemoryStore, f as WebSkillRuntime, l as parseBridgeRequest, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-Bbh_Apwg.js";
6
6
  import { n as normalizeToolContent } from "./external-_ZRQe-V9.js";
7
7
  import { i as probeLlmCapabilities } from "./env-Bj1MI2Ww.js";
8
8
  import { t as AUDIT_EVENT_TYPES } from "./eventTypes-CwciaqCU.js";
@@ -1,4 +1,4 @@
1
- import { h as uiCatalog } from "./openUiSpecLang-B4QQ9Tfo.js";
1
+ import { g as uiCatalog } from "./openUiSpecLang-CLXJEeRU.js";
2
2
  import { t as CatalogNode } from "./ui-react.js";
3
3
  import { Renderer, createLibrary, defineComponent } from "./dist-CTsxblSD.js";
4
4
  import { z } from "zod";
@@ -1,4 +1,4 @@
1
- import { u as interactionToFormModel } from "./miniChart-lauNtZu8.js";
1
+ import { u as interactionToFormModel } from "./miniChart-D0nYMzz8.js";
2
2
  import { toJSONSchema, z } from "zod";
3
3
 
4
4
  //#region ../ui/src/chart/echart.ts
@@ -738,11 +738,17 @@ const fieldCondition = z.lazy(() => z.union([
738
738
  z.object({ allOf: z.array(fieldCondition).min(1) }),
739
739
  z.object({ anyOf: z.array(fieldCondition).min(1) })
740
740
  ]));
741
- /** 选项既可以写死,也可以声明成异步取值(取值通道属分册 24) */
742
- const fieldOptions = z.array(z.object({
743
- label: z.string(),
744
- value: z.union([z.string(), z.number()])
745
- }));
741
+ /** 选项写死在声明里。异步取值(`optionsSource`)四档渲染器都没实现,已从模型可见接口撤下,见 deferred-items D41 */
742
+ const optionScalar = z.union([z.string(), z.number()]);
743
+ /**
744
+ * 对选项形状宽容:裸标量、缺 label、缺 value 都收。
745
+ * 严格 schema 的代价是整个 `options` 被 sanitize 丢掉 —— 用户拿到的是空下拉,
746
+ * 而不是「少了一个选项」。归一化归 `normalizeFieldOptions`。
747
+ */
748
+ const fieldOptions = z.array(z.union([optionScalar, z.object({
749
+ label: optionScalar.optional(),
750
+ value: optionScalar.optional()
751
+ }).refine((option) => option.label !== void 0 || option.value !== void 0, { message: "Option needs at least one of label / value" })]));
746
752
  /** Tabs 的面板与 Grid 的格子必须自成容器,否则「每容器至多一个表单」就没有落脚点 */
747
753
  const PANEL = ["Card", "Stack"];
748
754
  /**
@@ -1028,8 +1034,12 @@ const uiCatalog = defineUiCatalog({
1028
1034
  z.boolean(),
1029
1035
  z.null()
1030
1036
  ]))),
1031
- /** 列宽权重,长度需与 columns 一致;不一致时整项忽略而不拒绝渲染 */
1032
- columnWidths: z.array(z.number().positive()).optional()
1037
+ /**
1038
+ * 列宽权重。**这里只管形状,不管取值**:长度不符、负数、全零都由
1039
+ * `normalizeColumnWidths` 整项忽略并回退等权重。在 schema 上写 `positive()`
1040
+ * 会让一个越界的权重把整张表打成降级提示——那正是分册 25 要求不能发生的事。
1041
+ */
1042
+ columnWidths: z.array(z.number()).optional()
1033
1043
  }),
1034
1044
  example: {
1035
1045
  component: "Table",
@@ -1169,12 +1179,10 @@ const uiCatalog = defineUiCatalog({
1169
1179
  description: z.string().optional(),
1170
1180
  defaultValue: z.unknown().optional(),
1171
1181
  options: fieldOptions.optional(),
1172
- /** 声明后选项由宕主异步提供;与写死的 options 互斥 */
1173
- optionsSource: z.string().optional(),
1174
1182
  visibleWhen: fieldCondition.optional()
1175
1183
  }),
1176
1184
  constraints: [
1177
- "select / multi-select must provide options or optionsSource",
1185
+ "select / multi-select must list every choice in options; an empty options array leaves the user with nothing to pick",
1178
1186
  "password values are never persisted, never echoed and never enter the user profile",
1179
1187
  "visibleWhen may only reference fields declared in the same Form"
1180
1188
  ],
@@ -1184,7 +1192,13 @@ const uiCatalog = defineUiCatalog({
1184
1192
  name: "range",
1185
1193
  label: "Range",
1186
1194
  type: "select",
1187
- options: []
1195
+ options: [{
1196
+ label: "Last 7 days",
1197
+ value: "7d"
1198
+ }, {
1199
+ label: "Last 30 days",
1200
+ value: "30d"
1201
+ }]
1188
1202
  }
1189
1203
  }
1190
1204
  },
@@ -1720,6 +1734,45 @@ function resolveColumnWidths(weights, available, minWidth) {
1720
1734
  return result;
1721
1735
  }
1722
1736
 
1737
+ //#endregion
1738
+ //#region ../ui/src/catalog/fieldOptions.ts
1739
+ const isScalar = (value) => typeof value === "string" || typeof value === "number";
1740
+ /**
1741
+ * 模型给出的选项形状五花八门:裸标量(`['January', 'February']`)、只有 `label`、
1742
+ * 只有 `value`。整项丢掉的代价是下拉变空,必填字段再也交不出去,
1743
+ * 所以能补的一律补齐;补不出标签的(既无 label 又无 value)才丢。
1744
+ */
1745
+ function normalizeFieldOptions(raw) {
1746
+ if (!Array.isArray(raw)) return [];
1747
+ const options = [];
1748
+ for (const item of raw) {
1749
+ if (isScalar(item)) {
1750
+ options.push({
1751
+ label: String(item),
1752
+ value: item
1753
+ });
1754
+ continue;
1755
+ }
1756
+ if (item === null || typeof item !== "object") continue;
1757
+ const record = item;
1758
+ const label = record["label"];
1759
+ const value = record["value"];
1760
+ if (isScalar(label) && isScalar(value)) options.push({
1761
+ label: String(label),
1762
+ value
1763
+ });
1764
+ else if (isScalar(label)) options.push({
1765
+ label: String(label),
1766
+ value: label
1767
+ });
1768
+ else if (isScalar(value)) options.push({
1769
+ label: String(value),
1770
+ value
1771
+ });
1772
+ }
1773
+ return options;
1774
+ }
1775
+
1723
1776
  //#endregion
1724
1777
  //#region ../ui/src/catalog/toJsonRenderSpec.ts
1725
1778
  /**
@@ -1847,4 +1900,4 @@ function toOpenUiSpecLang(spec, catalog = uiCatalog) {
1847
1900
  }
1848
1901
 
1849
1902
  //#endregion
1850
- export { chartSpecFromProps as C, toEchartsOption as E, DEFAULT_CHART_FONT_SIZES as S, resolveChartFontSizes as T, CATALOG_BUDGET_STAGE as _, SPEC_TABLE_MIN_COLUMN_WIDTH as a, PLANNED_INCREMENT as b, MAX_CONDITION_DEPTH as c, collectScopedValues as d, qualifyFieldName as f, defineUiCatalog as g, uiCatalog as h, SPEC_TABLE_MIN_COLUMN_VAR as i, evaluateFieldCondition as l, UI_CATALOG_PROMPT_BUDGET_BYTES as m, interactionToUiSpec as n, normalizeColumnWidths as o, UI_CATALOG_GROUPS as p, toJsonRenderSpec as r, resolveColumnWidths as s, toOpenUiSpecLang as t, collectFormScopes as u, CATALOG_PROMPT_MAX as v, mountEchart as w, gaugePercent as x, CATALOG_SCHEMA_MAX as y };
1903
+ export { DEFAULT_CHART_FONT_SIZES as C, toEchartsOption as D, resolveChartFontSizes as E, gaugePercent as S, mountEchart as T, defineUiCatalog as _, SPEC_TABLE_MIN_COLUMN_VAR as a, CATALOG_SCHEMA_MAX as b, resolveColumnWidths as c, collectFormScopes as d, collectScopedValues as f, uiCatalog as g, UI_CATALOG_PROMPT_BUDGET_BYTES as h, normalizeFieldOptions as i, MAX_CONDITION_DEPTH as l, UI_CATALOG_GROUPS as m, interactionToUiSpec as n, SPEC_TABLE_MIN_COLUMN_WIDTH as o, qualifyFieldName as p, toJsonRenderSpec as r, normalizeColumnWidths as s, toOpenUiSpecLang as t, evaluateFieldCondition as u, CATALOG_BUDGET_STAGE as v, chartSpecFromProps as w, PLANNED_INCREMENT as x, CATALOG_PROMPT_MAX as y };
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as MockLlmClient } from "./llm-B7lLH0ZI.js";
1
+ import { t as MockLlmClient } from "./llm-eIQNO9tr.js";
2
2
  import { t as MemoryArtifactStore } from "./memoryArtifactStore-D6dLeY55.js";
3
3
  import { n as loadGoogleConfigFromEnv, r as loadLlmConfigFromEnv, t as loadAnthropicConfigFromEnv } from "./env-Bj1MI2Ww.js";
4
4
 
@@ -58,12 +58,12 @@ function toActionFrameScopes(scope) {
58
58
  }];
59
59
  }
60
60
  /**
61
- * 本版的操作集(FR-25.1 / 0.16.0 FR-13.6)。拖拽、滚动仍不在内;
61
+ * 本版的操作集(FR-25.1 / 0.16.0 FR-13.6 / 0.18.0 FR-10.1)。拖拽仍不在内(D64);
62
62
  * 导航只有 `back` 一个,且它**不接受地址**——可达面被浏览器历史封死(SC-1)。
63
63
  *
64
- * `select`/`set`/`attach` 与既有三个走**同一条** policy 路径,
64
+ * `select`/`set`/`attach`/`scroll` 与既有几个走**同一条** policy 路径,
65
65
  * 范围白名单、逐次确认、审计三条硬约束因此天然覆盖到它们——
66
- * 前提是新动作不绕过 policy 直接调执行器(AC-25.4 守这一点)。
66
+ * 前提是新动作不绕过 policy 直调执行器(AC-25.4 守这一点)。
67
67
  * @experimental
68
68
  */
69
69
  const PAGE_ACTION_KINDS = [
@@ -73,6 +73,7 @@ const PAGE_ACTION_KINDS = [
73
73
  "select",
74
74
  "set",
75
75
  "attach",
76
+ "scroll",
76
77
  "back"
77
78
  ];
78
79
  /**
@@ -1,6 +1,6 @@
1
1
  import { C as UiSpecActionCapability, D as UiSpecSnapshot, O as UiSurfaceActionRequest, Ot as UiSpecNode, S as UiBridge, T as UiSpecEvent, b as RenderResultRequest, c as InteractionResponse, k as UiSurfaceActionResponse, s as InteractionRequest, w as UiSpecDrafts } from "./types-Btpdd1y--BcxQ10Fa.js";
2
2
  import { N as DocumentSurfacePort } from "./index-BDyXe-a5.js";
3
- import { J as SurfaceHostControlTexts, P as InteractionSpecLabels, q as SurfaceFormTexts, v as ChartFontSizes } from "./index-CaLsVI-m.js";
3
+ import { F as InteractionSpecLabels, J as SurfaceFormTexts, Y as SurfaceHostControlTexts, v as ChartFontSizes } from "./index-ad11wflI.js";
4
4
  import { z } from "zod";
5
5
  import React$1, { ComponentType, ReactNode } from "react";
6
6
  import "react/jsx-runtime";