@webskill/sdk 0.1.1 → 0.1.3

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,5 @@
1
- import { C as renderAvailableSkillsXml, E as validateSkills, T as resolveInsideRoot, c as SkillDiscovery, d as buildCatalog, l as SkillReader, t as MemoryArtifactStore, u as WebSkillError, x as parseSkillMarkdown } from "./memoryArtifactStore-C9lFVqPF-U8nXvqL9.js";
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";
2
+ import { t as MemoryArtifactStore } from "./memoryArtifactStore-C9lFVqPF-yFz6yJj0.js";
2
3
 
3
4
  //#region ../runtime/dist/index.js
4
5
  const toOpenAiMessage = (msg) => {
@@ -32,7 +33,7 @@ const toOpenAiTools = (tools) => tools.map((tool) => ({
32
33
  parameters: tool.inputSchema
33
34
  }
34
35
  }));
35
- const errorMessage = (e) => e instanceof Error ? e.message : String(e);
36
+ const errorMessage$2 = (e) => e instanceof Error ? e.message : String(e);
36
37
  /** OpenAI chat completions 协议客户端(兼容端点通用) */
37
38
  var OpenAiCompatibleClient = class {
38
39
  #config;
@@ -56,7 +57,7 @@ var OpenAiCompatibleClient = class {
56
57
  try {
57
58
  data = await res.json();
58
59
  } catch (e) {
59
- 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);
60
61
  }
61
62
  return this.#parseResponse(data);
62
63
  }
@@ -78,7 +79,7 @@ var OpenAiCompatibleClient = class {
78
79
  try {
79
80
  chunk = JSON.parse(data);
80
81
  } catch (e) {
81
- 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);
82
83
  }
83
84
  const delta = chunk.choices?.[0]?.delta;
84
85
  if (!delta) return;
@@ -160,7 +161,7 @@ var OpenAiCompatibleClient = class {
160
161
  signal: input.signal ?? (this.#config.requestTimeoutMs ? AbortSignal.timeout(this.#config.requestTimeoutMs) : null)
161
162
  });
162
163
  } catch (e) {
163
- 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);
164
165
  }
165
166
  if (!res.ok) {
166
167
  const detail = await res.text().catch(() => "");
@@ -195,10 +196,433 @@ var OpenAiCompatibleClient = class {
195
196
  toolCalls: toolCalls?.length ? toolCalls : void 0,
196
197
  raw: data
197
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;
198
610
  } catch (e) {
199
611
  throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to interpret LLM response: ${errorMessage(e)}`, data);
200
612
  }
201
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
+ }
202
626
  };
203
627
  function toVercelToolSpecs(tools) {
204
628
  const out = {};
@@ -421,7 +845,7 @@ const ASK_USER_TOOL = {
421
845
  * 不暴露 fs 本体(沙箱语义,对齐 deferred-items D1)。
422
846
  */
423
847
  function createScriptContext(deps) {
424
- const { fs, artifactStore, skillName, skillRoot, runId, confirm, onWarning } = deps;
848
+ const { fs, artifactStore, skillName, skillRoot, runId, confirm, onWarning, onArtifactCreated } = deps;
425
849
  return {
426
850
  skillName,
427
851
  runId,
@@ -429,17 +853,19 @@ function createScriptContext(deps) {
429
853
  return fs.readText(resolveInsideRoot(skillRoot, `references/${relativePath}`));
430
854
  },
431
855
  async writeArtifact(path, content, options) {
432
- return typeof content === "string" ? artifactStore.createTextArtifact({
856
+ const artifact = typeof content === "string" ? await artifactStore.createTextArtifact({
433
857
  runId,
434
858
  path,
435
859
  content,
436
860
  mimeType: options?.mimeType
437
- }) : artifactStore.createBinaryArtifact({
861
+ }) : await artifactStore.createBinaryArtifact({
438
862
  runId,
439
863
  path,
440
864
  content,
441
865
  mimeType: options?.mimeType
442
866
  });
867
+ onArtifactCreated?.(artifact);
868
+ return artifact;
443
869
  },
444
870
  ...confirm ? { confirm } : {},
445
871
  ...onWarning ? { onWarning } : {}
@@ -1136,7 +1562,7 @@ var AgentLoop = class {
1136
1562
  try {
1137
1563
  const text = await state.reader.readSkillFile(skillName, pathArg);
1138
1564
  let note = "";
1139
- if (pathArg === "SKILL.md" && !state.activated.has(skillName)) note = await this.#activateSkill(skillName, state);
1565
+ if (pathArg === "SKILL.md" && !state.activated.has(skillName)) note = await this.#activateSkill(skillName, state, void 0, text);
1140
1566
  return {
1141
1567
  ok: true,
1142
1568
  content: [{
@@ -1190,7 +1616,7 @@ var AgentLoop = class {
1190
1616
  return toolError("SKILL_NOT_FOUND", `Skill not found via external providers: ${skillKey}`);
1191
1617
  }
1192
1618
  /** 首次读到 SKILL.md 时激活技能:加载其 scripts 工具定义,供后续轮次使用;dependencies 级联激活(via 记录来源) */
1193
- async #activateSkill(skillName, state, via) {
1619
+ async #activateSkill(skillName, state, via, skillMdText) {
1194
1620
  state.activated.add(skillName);
1195
1621
  state.trace.record("skill.activated", { data: {
1196
1622
  skillName,
@@ -1203,11 +1629,11 @@ var AgentLoop = class {
1203
1629
  let allowedTools;
1204
1630
  let dependencies = [];
1205
1631
  try {
1206
- const { metadata } = parseSkillMarkdown(await this.#deps.fs.readText(`${root}/SKILL.md`));
1632
+ const { metadata } = parseSkillMarkdown(skillMdText ?? await this.#deps.fs.readText(`${root}/SKILL.md`));
1207
1633
  const rawDeps = metadata["dependencies"];
1208
1634
  if (Array.isArray(rawDeps)) dependencies = rawDeps.filter((d) => typeof d === "string");
1209
- const raw = metadata["allowed-tools"];
1210
- if (raw !== void 0) if (Array.isArray(raw)) allowedTools = raw.filter((e) => typeof e === "string");
1635
+ const rawAllowed = metadata["allowed-tools"];
1636
+ if (rawAllowed !== void 0) if (Array.isArray(rawAllowed)) allowedTools = rawAllowed.filter((e) => typeof e === "string");
1211
1637
  else state.trace.record("run.warning", { message: `Skill "${skillName}" has a non-array "allowed-tools" metadata entry; ignored` });
1212
1638
  } catch {}
1213
1639
  const executor = this.#deps.executor;
@@ -1290,6 +1716,7 @@ var AgentLoop = class {
1290
1716
  if (e instanceof BridgeRequestError) return toolError("UI_UNAVAILABLE", `Parameter form failed: ${e.message}`);
1291
1717
  throw e;
1292
1718
  }
1719
+ const createdIds = /* @__PURE__ */ new Set();
1293
1720
  const context = createScriptContext({
1294
1721
  fs: this.#deps.fs,
1295
1722
  artifactStore: this.#deps.artifactStore,
@@ -1297,10 +1724,10 @@ var AgentLoop = class {
1297
1724
  skillRoot: root,
1298
1725
  runId: state.runId,
1299
1726
  confirm: (message) => this.#confirm(message, state),
1300
- onWarning: (message) => state.trace.record("run.warning", { message })
1727
+ onWarning: (message) => state.trace.record("run.warning", { message }),
1728
+ onArtifactCreated: (artifact) => createdIds.add(artifact.id)
1301
1729
  });
1302
1730
  try {
1303
- const before = new Set((await this.#deps.artifactStore.listArtifacts(state.runId)).map((a) => a.id));
1304
1731
  const result = await this.#deps.executor.execute({
1305
1732
  skillRoot: root,
1306
1733
  scriptName,
@@ -1308,8 +1735,10 @@ var AgentLoop = class {
1308
1735
  context,
1309
1736
  timeoutMs: state.toolTimeoutMs
1310
1737
  });
1311
- const fresh = (await this.#deps.artifactStore.listArtifacts(state.runId)).filter((a) => !before.has(a.id));
1312
- if (fresh.length > 0) result.artifacts = [...result.artifacts ?? [], ...fresh];
1738
+ if (createdIds.size > 0) {
1739
+ const fresh = (await this.#deps.artifactStore.listArtifacts(state.runId)).filter((a) => createdIds.has(a.id));
1740
+ if (fresh.length > 0) result.artifacts = [...result.artifacts ?? [], ...fresh];
1741
+ }
1313
1742
  await this.#bumpSkillStat(skillName, result.ok ? "successes" : "failures", state);
1314
1743
  return result;
1315
1744
  } catch (e) {
@@ -1623,6 +2052,16 @@ var WebSkillRuntime = class {
1623
2052
  function createWebSkillApi(deps) {
1624
2053
  const { fs, roots } = deps;
1625
2054
  let runtime;
2055
+ const discoveries = /* @__PURE__ */ new Map();
2056
+ const discoveryFor = (paths) => {
2057
+ const key = paths.join("");
2058
+ let discovery = discoveries.get(key);
2059
+ if (!discovery) {
2060
+ discovery = new SkillDiscovery(fs, paths);
2061
+ discoveries.set(key, discovery);
2062
+ }
2063
+ return discovery;
2064
+ };
1626
2065
  const lazyRuntime = () => {
1627
2066
  runtime ??= new WebSkillRuntime({
1628
2067
  fs,
@@ -1640,11 +2079,11 @@ function createWebSkillApi(deps) {
1640
2079
  };
1641
2080
  return {
1642
2081
  async discover(path) {
1643
- const { entries } = await new SkillDiscovery(fs, [path]).discover();
2082
+ const { entries } = await discoveryFor([path]).discover();
1644
2083
  return buildCatalog(entries);
1645
2084
  },
1646
2085
  async read(name) {
1647
- const { entries } = await new SkillDiscovery(fs, roots).discover();
2086
+ const { entries } = await discoveryFor(roots).discover();
1648
2087
  const entry = entries.find((e) => e.name === name);
1649
2088
  if (!entry) throw new WebSkillError("SKILL_NOT_FOUND", `Unknown skill: ${name}`);
1650
2089
  const skillMdPath = `${entry.root}/SKILL.md`;
@@ -1679,13 +2118,21 @@ function createWebSkillApi(deps) {
1679
2118
  }
1680
2119
  };
1681
2120
  }
1682
- /** install URL 分派:http(s)…​.zip → http;…​.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);其余 → local */
2121
+ /** install URL 分派:http(s)…​.zip → http;…​.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);
2122
+ * 其余 http(s) URL → INSTALL_FAILED(不静默当 local);非 URL → local */
1683
2123
  function sourceFromUrl(url) {
1684
2124
  const bare = url.split("?")[0] ?? url;
1685
- if (/^https?:\/\//.test(url) && bare.endsWith(".zip")) return {
1686
- type: "http",
1687
- url
1688
- };
2125
+ if (/^https?:\/\//.test(url)) {
2126
+ if (bare.endsWith(".zip")) return {
2127
+ type: "http",
2128
+ url
2129
+ };
2130
+ if (bare.endsWith(".git")) return {
2131
+ type: "git",
2132
+ url
2133
+ };
2134
+ throw new WebSkillError("INSTALL_FAILED", `Unsupported install URL "${url}" (supported: http(s) URLs ending in .zip or .git, or local paths)`);
2135
+ }
1689
2136
  if (bare.endsWith(".git")) return {
1690
2137
  type: "git",
1691
2138
  url
@@ -1993,4 +2440,4 @@ var CapabilityApproval = class {
1993
2440
  };
1994
2441
 
1995
2442
  //#endregion
1996
- 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 };
2443
+ 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 };