@webskill/sdk 0.1.2 → 0.1.4

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.
@@ -1,4 +1,4 @@
1
- import { S as renderAvailableSkillsXml, T as validateSkills, b as parseSkillMarkdown, c as SkillReader, l as WebSkillError, s as SkillDiscovery, u as buildCatalog, w as resolveInsideRoot } from "./dist-DS1sfgHa.js";
1
+ import { C as renderAvailableSkillsXml, E as validateSkills, T as resolveInsideRoot, c as SkillReader, d as buildCatalog, l as WebSkillError, s as SkillDiscovery, u as assertSafePathSegment, x as parseSkillMarkdown } from "./dist-DvoBsChX.js";
2
2
  import { t as MemoryArtifactStore } from "./memoryArtifactStore-C9lFVqPF-yFz6yJj0.js";
3
3
 
4
4
  //#region ../runtime/dist/index.js
@@ -33,7 +33,7 @@ const toOpenAiTools = (tools) => tools.map((tool) => ({
33
33
  parameters: tool.inputSchema
34
34
  }
35
35
  }));
36
- const errorMessage = (e) => e instanceof Error ? e.message : String(e);
36
+ const errorMessage$2 = (e) => e instanceof Error ? e.message : String(e);
37
37
  /** OpenAI chat completions 协议客户端(兼容端点通用) */
38
38
  var OpenAiCompatibleClient = class {
39
39
  #config;
@@ -57,7 +57,7 @@ var OpenAiCompatibleClient = class {
57
57
  try {
58
58
  data = await res.json();
59
59
  } catch (e) {
60
- throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM response: ${errorMessage(e)}`, e);
60
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM response: ${errorMessage$2(e)}`, e);
61
61
  }
62
62
  return this.#parseResponse(data);
63
63
  }
@@ -79,7 +79,7 @@ var OpenAiCompatibleClient = class {
79
79
  try {
80
80
  chunk = JSON.parse(data);
81
81
  } catch (e) {
82
- throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage(e)}`, data);
82
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage$2(e)}`, data);
83
83
  }
84
84
  const delta = chunk.choices?.[0]?.delta;
85
85
  if (!delta) return;
@@ -161,7 +161,7 @@ var OpenAiCompatibleClient = class {
161
161
  signal: input.signal ?? (this.#config.requestTimeoutMs ? AbortSignal.timeout(this.#config.requestTimeoutMs) : null)
162
162
  });
163
163
  } catch (e) {
164
- throw new WebSkillError("LLM_REQUEST_FAILED", `LLM request failed: ${errorMessage(e)}`, e);
164
+ throw new WebSkillError("LLM_REQUEST_FAILED", `LLM request failed: ${errorMessage$2(e)}`, e);
165
165
  }
166
166
  if (!res.ok) {
167
167
  const detail = await res.text().catch(() => "");
@@ -196,10 +196,433 @@ var OpenAiCompatibleClient = class {
196
196
  toolCalls: toolCalls?.length ? toolCalls : void 0,
197
197
  raw: data
198
198
  };
199
+ } catch (e) {
200
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to interpret LLM response: ${errorMessage$2(e)}`, data);
201
+ }
202
+ }
203
+ };
204
+ const ANTHROPIC_VERSION = "2023-06-01";
205
+ const errorMessage$1 = (e) => e instanceof Error ? e.message : String(e);
206
+ /** LlmMessage 序列 → system 独立参数 + user/assistant 消息(tool 结果合并进 user 消息 tool_result blocks) */
207
+ function toAnthropicMessages(messages) {
208
+ const system = messages.filter((m) => m.role === "system").map((m) => m.content).join("\n");
209
+ const out = [];
210
+ for (const msg of messages) {
211
+ if (msg.role === "system") continue;
212
+ if (msg.role === "tool") {
213
+ const block = {
214
+ type: "tool_result",
215
+ tool_use_id: msg.toolCallId ?? "",
216
+ content: msg.content
217
+ };
218
+ const last = out.at(-1);
219
+ if (last && last.role === "user" && Array.isArray(last.content)) last.content.push(block);
220
+ else out.push({
221
+ role: "user",
222
+ content: [block]
223
+ });
224
+ continue;
225
+ }
226
+ if (msg.role === "assistant" && msg.toolCalls?.length) {
227
+ const content = [];
228
+ if (msg.content !== "") content.push({
229
+ type: "text",
230
+ text: msg.content
231
+ });
232
+ for (const call of msg.toolCalls) content.push({
233
+ type: "tool_use",
234
+ id: call.id,
235
+ name: call.name,
236
+ input: call.arguments
237
+ });
238
+ out.push({
239
+ role: "assistant",
240
+ content
241
+ });
242
+ continue;
243
+ }
244
+ out.push({
245
+ role: msg.role,
246
+ content: msg.content
247
+ });
248
+ }
249
+ return system === "" ? { messages: out } : {
250
+ system,
251
+ messages: out
252
+ };
253
+ }
254
+ const toAnthropicTools = (tools) => tools.map((tool) => ({
255
+ name: tool.name,
256
+ ...tool.description ? { description: tool.description } : {},
257
+ input_schema: tool.inputSchema
258
+ }));
259
+ /** Anthropic Messages API 客户端(零依赖 fetch;Node/浏览器通用) */
260
+ var AnthropicClient = class {
261
+ #config;
262
+ #fetch;
263
+ constructor(config) {
264
+ this.#config = config;
265
+ this.#fetch = config.fetchImpl ?? fetch;
266
+ }
267
+ #baseUrl() {
268
+ return (this.#config.baseUrl ?? "https://api.anthropic.com").replace(/\/+$/, "");
269
+ }
270
+ #headers() {
271
+ return {
272
+ "content-type": "application/json",
273
+ "x-api-key": this.#config.apiKey,
274
+ "anthropic-version": ANTHROPIC_VERSION,
275
+ "anthropic-dangerous-direct-browser-access": "true"
276
+ };
277
+ }
278
+ async complete(input) {
279
+ const res = await this.#post(input, false);
280
+ let data;
281
+ try {
282
+ data = await res.json();
283
+ } catch (e) {
284
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM response: ${errorMessage$1(e)}`, e);
285
+ }
286
+ return this.#parseResponse(data);
287
+ }
288
+ /** SSE 流式:text_delta → text-delta;input_json_delta 按 block index 聚合 → tool-calls */
289
+ async *stream(input) {
290
+ const res = await this.#post(input, true);
291
+ if (!res.body) throw new WebSkillError("LLM_REQUEST_FAILED", "LLM streaming response has no body");
292
+ const toolCallsByIndex = /* @__PURE__ */ new Map();
293
+ const decoder = new TextDecoder();
294
+ const reader = res.body.getReader();
295
+ let buffer = "";
296
+ const handleFrame = function* (data) {
297
+ let chunk;
298
+ try {
299
+ chunk = JSON.parse(data);
300
+ } catch (e) {
301
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage$1(e)}`, data);
302
+ }
303
+ const type = chunk["type"];
304
+ if (type === "error") throw new WebSkillError("LLM_REQUEST_FAILED", `LLM stream error: ${chunk["error"]?.message ?? data}`);
305
+ if (type === "content_block_start") {
306
+ const block = chunk["content_block"];
307
+ if (block?.["type"] === "tool_use") {
308
+ const index = typeof chunk["index"] === "number" ? chunk["index"] : 0;
309
+ toolCallsByIndex.set(index, {
310
+ id: typeof block["id"] === "string" ? block["id"] : "",
311
+ name: typeof block["name"] === "string" ? block["name"] : "",
312
+ arguments: ""
313
+ });
314
+ }
315
+ return;
316
+ }
317
+ if (type === "content_block_delta") {
318
+ const index = typeof chunk["index"] === "number" ? chunk["index"] : 0;
319
+ const delta = chunk["delta"];
320
+ if (delta?.["type"] === "text_delta" && typeof delta["text"] === "string" && delta["text"] !== "") {
321
+ yield {
322
+ type: "text-delta",
323
+ delta: delta["text"]
324
+ };
325
+ return;
326
+ }
327
+ if (delta?.["type"] === "input_json_delta" && typeof delta["partial_json"] === "string") {
328
+ const acc = toolCallsByIndex.get(index) ?? {
329
+ id: "",
330
+ name: "",
331
+ arguments: ""
332
+ };
333
+ acc.arguments += delta["partial_json"];
334
+ toolCallsByIndex.set(index, acc);
335
+ }
336
+ return;
337
+ }
338
+ };
339
+ try {
340
+ for (;;) {
341
+ const { value, done: readerDone } = await reader.read();
342
+ if (readerDone) break;
343
+ buffer += decoder.decode(value, { stream: true });
344
+ buffer = buffer.replace(/\r\n/g, "\n");
345
+ let newline;
346
+ while ((newline = buffer.indexOf("\n")) >= 0) {
347
+ const line = buffer.slice(0, newline).trim();
348
+ buffer = buffer.slice(newline + 1);
349
+ if (line === "" || line.startsWith(":") || line.startsWith("event:")) continue;
350
+ if (!line.startsWith("data:")) continue;
351
+ yield* handleFrame(line.slice(5).trim());
352
+ }
353
+ }
354
+ } finally {
355
+ reader.releaseLock();
356
+ }
357
+ if (toolCallsByIndex.size > 0) yield {
358
+ type: "tool-calls",
359
+ toolCalls: [...toolCallsByIndex.entries()].sort(([a], [b]) => a - b).map(([index, acc]) => {
360
+ let args = {};
361
+ try {
362
+ args = JSON.parse(acc.arguments || "{}");
363
+ } catch {}
364
+ return {
365
+ id: acc.id || `call-${index}`,
366
+ name: acc.name,
367
+ arguments: args
368
+ };
369
+ })
370
+ };
371
+ yield { type: "done" };
372
+ }
373
+ async #post(input, stream) {
374
+ const { system, messages } = toAnthropicMessages(input.messages);
375
+ const body = {
376
+ model: input.model ?? this.#config.model,
377
+ max_tokens: this.#config.maxTokens ?? 4096,
378
+ messages
379
+ };
380
+ if (system !== void 0) body["system"] = system;
381
+ if (input.tools?.length) body["tools"] = toAnthropicTools(input.tools);
382
+ if (input.temperature !== void 0) body["temperature"] = input.temperature;
383
+ if (stream) body["stream"] = true;
384
+ let res;
385
+ try {
386
+ res = await this.#fetch(`${this.#baseUrl()}/v1/messages`, {
387
+ method: "POST",
388
+ headers: this.#headers(),
389
+ body: JSON.stringify(body),
390
+ signal: input.signal ?? (this.#config.requestTimeoutMs ? AbortSignal.timeout(this.#config.requestTimeoutMs) : null)
391
+ });
392
+ } catch (e) {
393
+ throw new WebSkillError("LLM_REQUEST_FAILED", `LLM request failed: ${errorMessage$1(e)}`, e);
394
+ }
395
+ if (!res.ok) {
396
+ const detail = await res.text().catch(() => "");
397
+ throw new WebSkillError("LLM_REQUEST_FAILED", `LLM request failed with HTTP ${res.status}${detail ? `: ${detail.slice(0, 500)}` : ""}`);
398
+ }
399
+ return res;
400
+ }
401
+ /** 轻量探测(GET /v1/models),集成测试据此决定 skip */
402
+ async checkAvailability() {
403
+ try {
404
+ return (await this.#fetch(`${this.#baseUrl()}/v1/models`, { headers: this.#headers() })).ok;
405
+ } catch {
406
+ return false;
407
+ }
408
+ }
409
+ #parseResponse(data) {
410
+ try {
411
+ const blocks = data.content;
412
+ if (!Array.isArray(blocks)) throw new Error("response has no content blocks");
413
+ const text = blocks.filter((b) => b["type"] === "text").map((b) => String(b["text"] ?? "")).join("");
414
+ const toolCalls = blocks.filter((b) => b["type"] === "tool_use").map((b) => ({
415
+ id: typeof b["id"] === "string" ? b["id"] : "",
416
+ name: typeof b["name"] === "string" ? b["name"] : "",
417
+ arguments: typeof b["input"] === "object" && b["input"] !== null ? b["input"] : {}
418
+ }));
419
+ return {
420
+ content: text === "" ? void 0 : text,
421
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
422
+ raw: data
423
+ };
424
+ } catch (e) {
425
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to interpret LLM response: ${errorMessage$1(e)}`, data);
426
+ }
427
+ }
428
+ };
429
+ const errorMessage = (e) => e instanceof Error ? e.message : String(e);
430
+ /** tool 结果文本包装为 functionResponse.response 对象 */
431
+ function responseObject(content) {
432
+ try {
433
+ const parsed = JSON.parse(content);
434
+ if (typeof parsed === "object" && parsed !== null) return parsed;
435
+ } catch {}
436
+ return { result: content };
437
+ }
438
+ /** LlmMessage 序列 → systemInstruction + contents(tool 结果合并进 user 消息 functionResponse parts) */
439
+ function toGenAiContents(messages) {
440
+ const system = messages.filter((m) => m.role === "system").map((m) => m.content).join("\n");
441
+ const nameByCallId = /* @__PURE__ */ new Map();
442
+ for (const msg of messages) if (msg.role === "assistant") for (const call of msg.toolCalls ?? []) nameByCallId.set(call.id, call.name);
443
+ const out = [];
444
+ for (const msg of messages) {
445
+ if (msg.role === "system") continue;
446
+ if (msg.role === "tool") {
447
+ const callId = msg.toolCallId ?? "";
448
+ const part = { functionResponse: {
449
+ name: nameByCallId.get(callId) ?? callId,
450
+ response: responseObject(msg.content)
451
+ } };
452
+ const last = out.at(-1);
453
+ if (last && last.role === "user") last.parts.push(part);
454
+ else out.push({
455
+ role: "user",
456
+ parts: [part]
457
+ });
458
+ continue;
459
+ }
460
+ if (msg.role === "assistant") {
461
+ const parts = [];
462
+ if (msg.content !== "") parts.push({ text: msg.content });
463
+ for (const call of msg.toolCalls ?? []) parts.push({ functionCall: {
464
+ name: call.name,
465
+ args: call.arguments
466
+ } });
467
+ out.push({
468
+ role: "model",
469
+ parts: parts.length > 0 ? parts : [{ text: "" }]
470
+ });
471
+ continue;
472
+ }
473
+ out.push({
474
+ role: "user",
475
+ parts: [{ text: msg.content }]
476
+ });
477
+ }
478
+ return system === "" ? { contents: out } : {
479
+ systemInstruction: { parts: [{ text: system }] },
480
+ contents: out
481
+ };
482
+ }
483
+ const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => ({
484
+ name: tool.name,
485
+ ...tool.description ? { description: tool.description } : {},
486
+ parameters: tool.inputSchema
487
+ })) }];
488
+ /** Google GenAI(generateContent / streamGenerateContent)客户端(零依赖 fetch) */
489
+ var GoogleGenAiClient = class {
490
+ #config;
491
+ #fetch;
492
+ constructor(config) {
493
+ this.#config = config;
494
+ this.#fetch = config.fetchImpl ?? fetch;
495
+ }
496
+ #baseUrl() {
497
+ return (this.#config.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
498
+ }
499
+ async complete(input) {
500
+ const res = await this.#post(input, false);
501
+ let data;
502
+ try {
503
+ data = await res.json();
504
+ } catch (e) {
505
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM response: ${errorMessage(e)}`, e);
506
+ }
507
+ const parts = this.#responseParts(data);
508
+ const text = parts.filter((p) => typeof p["text"] === "string").map((p) => String(p["text"])).join("");
509
+ const toolCalls = this.#functionCalls(parts);
510
+ return {
511
+ content: text === "" ? void 0 : text,
512
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
513
+ raw: data
514
+ };
515
+ }
516
+ /** SSE 流式:chunk.parts text → text-delta;functionCall 聚合 → tool-calls */
517
+ async *stream(input) {
518
+ const res = await this.#post(input, true);
519
+ if (!res.body) throw new WebSkillError("LLM_REQUEST_FAILED", "LLM streaming response has no body");
520
+ const toolCalls = [];
521
+ const decoder = new TextDecoder();
522
+ const reader = res.body.getReader();
523
+ let buffer = "";
524
+ const handleFrame = function* (data) {
525
+ let chunk;
526
+ try {
527
+ chunk = JSON.parse(data);
528
+ } catch (e) {
529
+ throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage(e)}`, data);
530
+ }
531
+ const parts = chunk.candidates?.[0]?.content?.parts;
532
+ for (const part of parts ?? []) {
533
+ if (typeof part["text"] === "string" && part["text"] !== "") {
534
+ yield {
535
+ type: "text-delta",
536
+ delta: part["text"]
537
+ };
538
+ continue;
539
+ }
540
+ const call = part["functionCall"];
541
+ if (call) toolCalls.push({
542
+ id: `call-${toolCalls.length}`,
543
+ name: call.name ?? "",
544
+ arguments: call.args ?? {}
545
+ });
546
+ }
547
+ };
548
+ try {
549
+ for (;;) {
550
+ const { value, done: readerDone } = await reader.read();
551
+ if (readerDone) break;
552
+ buffer += decoder.decode(value, { stream: true });
553
+ buffer = buffer.replace(/\r\n/g, "\n");
554
+ let newline;
555
+ while ((newline = buffer.indexOf("\n")) >= 0) {
556
+ const line = buffer.slice(0, newline).trim();
557
+ buffer = buffer.slice(newline + 1);
558
+ if (line === "" || line.startsWith(":") || line.startsWith("event:")) continue;
559
+ if (!line.startsWith("data:")) continue;
560
+ yield* handleFrame(line.slice(5).trim());
561
+ }
562
+ }
563
+ } finally {
564
+ reader.releaseLock();
565
+ }
566
+ if (toolCalls.length > 0) yield {
567
+ type: "tool-calls",
568
+ toolCalls
569
+ };
570
+ yield { type: "done" };
571
+ }
572
+ async #post(input, stream) {
573
+ const model = input.model ?? this.#config.model;
574
+ const action = stream ? `:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.#config.apiKey)}` : `:generateContent?key=${encodeURIComponent(this.#config.apiKey)}`;
575
+ const { systemInstruction, contents } = toGenAiContents(input.messages);
576
+ const body = { contents };
577
+ if (systemInstruction) body["systemInstruction"] = systemInstruction;
578
+ if (input.tools?.length) body["tools"] = toGenAiTools(input.tools);
579
+ if (input.temperature !== void 0) body["generationConfig"] = { temperature: input.temperature };
580
+ let res;
581
+ try {
582
+ res = await this.#fetch(`${this.#baseUrl()}/v1beta/models/${encodeURIComponent(model)}${action}`, {
583
+ method: "POST",
584
+ headers: { "content-type": "application/json" },
585
+ body: JSON.stringify(body),
586
+ signal: input.signal ?? (this.#config.requestTimeoutMs ? AbortSignal.timeout(this.#config.requestTimeoutMs) : null)
587
+ });
588
+ } catch (e) {
589
+ throw new WebSkillError("LLM_REQUEST_FAILED", `LLM request failed: ${errorMessage(e)}`, e);
590
+ }
591
+ if (!res.ok) {
592
+ const detail = await res.text().catch(() => "");
593
+ throw new WebSkillError("LLM_REQUEST_FAILED", `LLM request failed with HTTP ${res.status}${detail ? `: ${detail.slice(0, 500)}` : ""}`);
594
+ }
595
+ return res;
596
+ }
597
+ /** 轻量探测(GET /v1beta/models),集成测试据此决定 skip */
598
+ async checkAvailability() {
599
+ try {
600
+ return (await this.#fetch(`${this.#baseUrl()}/v1beta/models?key=${encodeURIComponent(this.#config.apiKey)}`)).ok;
601
+ } catch {
602
+ return false;
603
+ }
604
+ }
605
+ #responseParts(data) {
606
+ try {
607
+ const parts = data.candidates?.[0]?.content?.parts;
608
+ if (!Array.isArray(parts)) throw new Error("response has no candidates[0].content.parts");
609
+ return parts;
199
610
  } catch (e) {
200
611
  throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to interpret LLM response: ${errorMessage(e)}`, data);
201
612
  }
202
613
  }
614
+ #functionCalls(parts) {
615
+ const out = [];
616
+ for (const part of parts) {
617
+ const call = part["functionCall"];
618
+ if (call) out.push({
619
+ id: `call-${out.length}`,
620
+ name: call.name ?? "",
621
+ arguments: call.args ?? {}
622
+ });
623
+ }
624
+ return out;
625
+ }
203
626
  };
204
627
  function toVercelToolSpecs(tools) {
205
628
  const out = {};
@@ -1154,6 +1577,7 @@ var AgentLoop = class {
1154
1577
  }
1155
1578
  /** 经外部技能提供者读取技能文档/资源;skillKey 为技能名或 mcp:// URI */
1156
1579
  async #handleReadExternalSkill(skillKey, pathArg, state) {
1580
+ const failures = [];
1157
1581
  for (const provider of this.#deps.skillProviders ?? []) try {
1158
1582
  let name = skillKey;
1159
1583
  if (skillKey.startsWith("mcp://")) {
@@ -1187,10 +1611,12 @@ var AgentLoop = class {
1187
1611
  text: await provider.readFile(name, pathArg)
1188
1612
  }]
1189
1613
  };
1190
- } catch {
1614
+ } catch (e) {
1615
+ failures.push(messageOf$1(e));
1191
1616
  continue;
1192
1617
  }
1193
- return toolError("SKILL_NOT_FOUND", `Skill not found via external providers: ${skillKey}`);
1618
+ const detail = failures.length > 0 ? ` (provider errors: ${failures.join("; ")})` : "";
1619
+ return toolError("SKILL_NOT_FOUND", `Skill not found via external providers: ${skillKey}${detail}`);
1194
1620
  }
1195
1621
  /** 首次读到 SKILL.md 时激活技能:加载其 scripts 工具定义,供后续轮次使用;dependencies 级联激活(via 记录来源) */
1196
1622
  async #activateSkill(skillName, state, via, skillMdText) {
@@ -1212,7 +1638,9 @@ var AgentLoop = class {
1212
1638
  const rawAllowed = metadata["allowed-tools"];
1213
1639
  if (rawAllowed !== void 0) if (Array.isArray(rawAllowed)) allowedTools = rawAllowed.filter((e) => typeof e === "string");
1214
1640
  else state.trace.record("run.warning", { message: `Skill "${skillName}" has a non-array "allowed-tools" metadata entry; ignored` });
1215
- } catch {}
1641
+ } catch (e) {
1642
+ state.trace.record("run.warning", { message: `Failed to read or parse SKILL.md of skill "${skillName}": ${messageOf$1(e)}` });
1643
+ }
1216
1644
  const executor = this.#deps.executor;
1217
1645
  const loaded = [];
1218
1646
  if (executor) {
@@ -1231,7 +1659,9 @@ var AgentLoop = class {
1231
1659
  await this.#enrichDefinition(root, match[1], def, state);
1232
1660
  state.activatedTools.set(def.name, def);
1233
1661
  loaded.push(def.name);
1234
- } catch {}
1662
+ } catch (e) {
1663
+ state.trace.record("run.warning", { message: `Failed to load definition of script "${match[1]}" for skill "${skillName}": ${messageOf$1(e)}` });
1664
+ }
1235
1665
  }
1236
1666
  }
1237
1667
  let note = loaded.length ? `\n\nActivated tools: ${loaded.join(", ")}` : "";
@@ -1501,10 +1931,12 @@ var WebSkillRuntime = class {
1501
1931
  if (!this.#catalogCache) await this.discover();
1502
1932
  const cache = this.#catalogCache;
1503
1933
  if (!cache) throw new Error("discover() did not populate the catalog cache");
1934
+ const providerFailures = [];
1504
1935
  const providerEntries = (await Promise.all((this.#deps.skillProviders ?? []).map(async (p) => {
1505
1936
  try {
1506
1937
  return await p.listSkills();
1507
- } catch {
1938
+ } catch (e) {
1939
+ providerFailures.push(e instanceof Error ? e.message : String(e));
1508
1940
  return [];
1509
1941
  }
1510
1942
  }))).flat();
@@ -1534,6 +1966,13 @@ var WebSkillRuntime = class {
1534
1966
  userPrompt,
1535
1967
  route
1536
1968
  });
1969
+ for (const failure of providerFailures) result.run.trace.push({
1970
+ id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1971
+ runId: result.run.id,
1972
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1973
+ type: "run.warning",
1974
+ message: `Skill provider listSkills() failed: ${failure}`
1975
+ });
1537
1976
  if (this.#deps.onSkillMiss && result.run.activeSkillNames.length === 0) this.#deps.onSkillMiss({
1538
1977
  prompt: userPrompt,
1539
1978
  run: result.run
@@ -1856,6 +2295,7 @@ var FsArtifactStore = class {
1856
2295
  });
1857
2296
  }
1858
2297
  async listArtifacts(runId) {
2298
+ assertSafePathSegment(runId, "runId");
1859
2299
  const indexPath = `${this.#root}/${runId}/${INDEX_FILE}`;
1860
2300
  if (!await this.#fs.exists(indexPath)) return [];
1861
2301
  const raw = await this.#fs.readText(indexPath);
@@ -1867,6 +2307,7 @@ var FsArtifactStore = class {
1867
2307
  }
1868
2308
  }
1869
2309
  #artifactPath(runId, artifactPath) {
2310
+ assertSafePathSegment(runId, "runId");
1870
2311
  return resolveInsideRoot(`${this.#root}/${runId}`, artifactPath);
1871
2312
  }
1872
2313
  async #persist(input) {
@@ -2017,4 +2458,4 @@ var CapabilityApproval = class {
2017
2458
  };
2018
2459
 
2019
2460
  //#endregion
2020
- export { normalizeToolContent as A, createWebSkillApi as C, isNetworkAllowed as D, fromVercelStreamPart as E, toVercelToolSpecs as F, resolveToolName as M, schemaToForm as N, mergeCatalogEntries as O, toLlmToolSpec as P, createScriptContext as S, fromVercelResult as T, RUN_SNAPSHOT_SCHEMA_VERSION as _, CapabilityApproval as a, bridgeError as b, FsMemoryStore as c, HookRunner as d, OpenAiCompatibleClient as f, READ_SKILL_FILE_TOOL_NAME as g, READ_SKILL_FILE_TOOL as h, AgentLoop as i, parseBridgeRequest as j, networkUrlHost as k, FsRunSnapshotStore as l, READ_SKILL_FILE_INPUT_SCHEMA as m, ASK_USER_TOOL as n, EventBus as o, ProgressiveRouter as p, ASK_USER_TOOL_NAME as r, FsArtifactStore as s, ASK_USER_INPUT_SCHEMA as t, FullDisclosureRouter as u, TraceRecorder as v, extractChartSpec as w, buildRenderResult as x, WebSkillRuntime as y };
2461
+ export { mergeCatalogEntries as A, buildRenderResult as C, fromVercelResult as D, extractChartSpec as E, schemaToForm as F, toLlmToolSpec as I, toVercelToolSpecs as L, normalizeToolContent as M, parseBridgeRequest as N, fromVercelStreamPart as O, resolveToolName as P, bridgeError as S, createWebSkillApi as T, READ_SKILL_FILE_TOOL as _, AnthropicClient as a, TraceRecorder as b, FsArtifactStore as c, FullDisclosureRouter as d, GoogleGenAiClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, networkUrlHost as j, isNetworkAllowed as k, FsMemoryStore as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, CapabilityApproval as o, HookRunner as p, ASK_USER_TOOL_NAME as r, EventBus as s, ASK_USER_INPUT_SCHEMA as t, FsRunSnapshotStore as u, READ_SKILL_FILE_TOOL_NAME as v, createScriptContext as w, WebSkillRuntime as x, RUN_SNAPSHOT_SCHEMA_VERSION as y };
@@ -174,8 +174,20 @@ function normalizePath(path) {
174
174
  return path.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".").join("/");
175
175
  }
176
176
  /**
177
+ * 外部标识(runId、candidateId、skillName、versionId 等)作为单一路径段使用前的统一校验:
178
+ * 拒绝空串、`.`、`..`、含 `/` 或 `\`、含 `:`(Windows 盘符/ADS)。
179
+ * 违规抛 FS_PATH_OUTSIDE_ROOT(kind 用于错误消息定位,如 "runId")。
180
+ */
181
+ function assertSafePathSegment(segment, kind) {
182
+ if (segment === "" || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || segment.includes(":")) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Invalid ${kind} (must be a single safe path segment): ${JSON.stringify(segment)}`);
183
+ }
184
+ /**
177
185
  * 将相对路径安全地解析到 root 之内。
178
186
  * 拒绝空路径、绝对路径(`/`、`\\`、`C:\` 形式)与任何 `..` 段。
187
+ *
188
+ * 注意:本函数仅做**词法**校验,不解析符号链接——root 内经符号链接指向
189
+ * 外部的目标无法被词法检查拦截(realpath 防护见 NodeFS root 模式与
190
+ * 安装管线的 lstat 符号链接扫描)。
179
191
  */
180
192
  function resolveInsideRoot(root, relativePath) {
181
193
  const fail = (reason) => {
@@ -587,4 +599,4 @@ const xmlRenderer = {
587
599
  };
588
600
 
589
601
  //#endregion
590
- export { renderCatalogJson as C, xmlRenderer as D, verifyManifest as E, renderAvailableSkillsXml as S, validateSkills as T, isValidSkillName as _, SKILL_NAME_PATTERN as a, parseSkillMarkdown as b, SkillReader as c, buildManifest as d, checkDependencyCycles as f, exportSkills as g, escapeXml as h, SKILL_NAME_MAX_LENGTH as i, WebSkillError as l, computeDigest as m, SKILLS_LOCKFILE as n, SKILL_PACK_FILE as o, checkSkillRules as p, SKILL_MANIFEST_FILE as r, SkillDiscovery as s, MemoryFS as t, buildCatalog as u, jsonRenderer as v, resolveInsideRoot as w, parseSkillPackManifest as x, normalizePath as y };
602
+ export { renderAvailableSkillsXml as C, verifyManifest as D, validateSkills as E, xmlRenderer as O, parseSkillPackManifest as S, resolveInsideRoot as T, exportSkills as _, SKILL_NAME_PATTERN as a, normalizePath as b, SkillReader as c, buildCatalog as d, buildManifest as f, escapeXml as g, computeDigest as h, SKILL_NAME_MAX_LENGTH as i, WebSkillError as l, checkSkillRules as m, SKILLS_LOCKFILE as n, SKILL_PACK_FILE as o, checkDependencyCycles as p, SKILL_MANIFEST_FILE as r, SkillDiscovery as s, MemoryFS as t, assertSafePathSegment as u, isValidSkillName as v, renderCatalogJson as w, parseSkillMarkdown as x, jsonRenderer as y };
@@ -1,4 +1,4 @@
1
- import { l as WebSkillError } from "./dist-DS1sfgHa.js";
1
+ import { l as WebSkillError } from "./dist-DvoBsChX.js";
2
2
 
3
3
  //#region ../ui/dist/index.js
4
4
  /** 提交值按请求类型归形(WebFormBridge 与框架组件库共享单一来源) */
@@ -1040,8 +1040,7 @@ var LitRendererBridge = class {
1040
1040
  values: formValues
1041
1041
  }
1042
1042
  });
1043
- if (input.type === "authorize" && !response.cancelled) response.value = true;
1044
- resolve(response);
1043
+ resolve(this.#decodeValue(input, response));
1045
1044
  }, { version: "v0.9.1" });
1046
1045
  processor.onSurfaceCreated((surface) => {
1047
1046
  const el = this.#doc.createElement("a2ui-surface");
@@ -1052,6 +1051,50 @@ var LitRendererBridge = class {
1052
1051
  processor.processMessages(messages);
1053
1052
  });
1054
1053
  }
1054
+ /**
1055
+ * 按 InteractionRequest.type 解码提交值(绑定模型原样回传的是表单对象):
1056
+ * confirm 仅 confirmed===true 批准;select 还原原始 option 值(非字符串值经 JSON 编码比对);
1057
+ * form 的 number 字段转 number;ask 取 answer;authorize 提交即批准。
1058
+ */
1059
+ #decodeValue(input, response) {
1060
+ if (response.cancelled) return response;
1061
+ const values = response.value;
1062
+ switch (input.type) {
1063
+ case "confirm": return {
1064
+ ...response,
1065
+ value: values?.confirmed === true
1066
+ };
1067
+ case "ask": return {
1068
+ ...response,
1069
+ value: values?.answer
1070
+ };
1071
+ case "select": {
1072
+ const raw = values?.selected;
1073
+ const option = input.options.find((o) => o.value === raw || JSON.stringify(o.value) === raw);
1074
+ return {
1075
+ ...response,
1076
+ value: option ? option.value : raw
1077
+ };
1078
+ }
1079
+ case "form": {
1080
+ if (typeof values !== "object" || values === null) return response;
1081
+ const out = { ...values };
1082
+ for (const field of input.fields) if (field.type === "number" && out[field.name] !== void 0) {
1083
+ const n = Number(out[field.name]);
1084
+ if (!Number.isNaN(n)) out[field.name] = n;
1085
+ }
1086
+ return {
1087
+ ...response,
1088
+ value: out
1089
+ };
1090
+ }
1091
+ case "authorize": return {
1092
+ ...response,
1093
+ value: true
1094
+ };
1095
+ default: return response;
1096
+ }
1097
+ }
1055
1098
  };
1056
1099
 
1057
1100
  //#endregion
@@ -1,6 +1,6 @@
1
- import { L as SkillManifest, N as SkillDocument, S as FileSystemProvider, c as LlmClient, j as SkillCatalogEntry, u as LlmMessage, v as UiBridge } from "./types-CgNC-oQu-Dq_A1yA-.js";
2
- import { U as RuntimeRun, it as WebSkillRuntime } from "./index-fjRF4H5o.js";
3
- import { d as SkillManager } from "./index-DkPK44Ji.js";
1
+ import { L as SkillManifest, N as SkillDocument, S as FileSystemProvider, c as LlmClient, j as SkillCatalogEntry, u as LlmMessage, v as UiBridge } from "./types-CgNC-oQu-DYrxhD3z.js";
2
+ import { ct as WebSkillRuntime, q as RuntimeRun } from "./index-BSS3EWY-.js";
3
+ import { f as SkillManager } from "./index-npFMt2Zw.js";
4
4
  //#region ../governance/dist/index.d.ts
5
5
  //#region src/types.d.ts
6
6
  type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
@@ -311,11 +311,14 @@ interface EvaluationReport {
311
311
  }
312
312
  //#endregion
313
313
  //#region src/evaluation/evaluationRunner.d.ts
314
- /** 批量评估:顺序执行(单任务异常不中断批);expected 参与判定;结构化报告 */
314
+ /** 批量评估:顺序执行(单任务异常不中断批);expected 参与判定;结构化报告。
315
+ * 注意:评估会真实执行技能脚本(试用路径)——必须显式 opt-in(allowExecution: true),
316
+ * 且沙箱执行器是能力面收敛而非安全边界,禁止对不可信候选技能直接跑评估。 */
315
317
  declare class EvaluationRunner {
316
318
  #private;
317
319
  constructor(deps: {
318
320
  runtime: WebSkillRuntime;
321
+ allowExecution?: boolean;
319
322
  now?: () => string;
320
323
  createId?: () => string;
321
324
  });