@agentforge/skills 0.16.74 → 0.16.76

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/index.cjs CHANGED
@@ -16,6 +16,9 @@ var TrustPolicyReason = /* @__PURE__ */ ((TrustPolicyReason2) => {
16
16
  TrustPolicyReason2["NOT_SCRIPT"] = "not-script";
17
17
  TrustPolicyReason2["WORKSPACE_TRUST"] = "workspace-trust";
18
18
  TrustPolicyReason2["TRUSTED_ROOT"] = "trusted-root";
19
+ TrustPolicyReason2["WORKSPACE_SKILL_ACTIVATION"] = "workspace-skill-activation";
20
+ TrustPolicyReason2["TRUSTED_SKILL_ACTIVATION"] = "trusted-skill-activation";
21
+ TrustPolicyReason2["UNTRUSTED_SKILL_ACTIVATION_DENIED"] = "untrusted-skill-activation-denied";
19
22
  TrustPolicyReason2["UNTRUSTED_SCRIPT_DENIED"] = "untrusted-script-denied";
20
23
  TrustPolicyReason2["UNTRUSTED_SCRIPT_ALLOWED"] = "untrusted-script-allowed-override";
21
24
  TrustPolicyReason2["UNKNOWN_TRUST_LEVEL"] = "unknown-trust-level";
@@ -239,44 +242,6 @@ function buildMissingSkillMessage(registry, name) {
239
242
  };
240
243
  }
241
244
 
242
- // src/activation-activate-tool.ts
243
- function createActivateSkillTool(registry) {
244
- return new core.ToolBuilder().name("activate-skill").description(
245
- "Activate an Agent Skill by name, loading its full instructions. Returns the complete SKILL.md body content for the named skill. Use this when you see a relevant skill in <available_skills> and want to follow its instructions."
246
- ).category(core.ToolCategory.SKILLS).tags(["skill", "activation", "agent-skills"]).schema(activateSkillSchema).implement(async ({ name }) => {
247
- const skill = registry.get(name);
248
- if (!skill) {
249
- const { availableCount, errorMessage } = buildMissingSkillMessage(registry, name);
250
- activationLogger.warn("Skill activation failed \u2014 not found", { name, availableCount });
251
- return errorMessage;
252
- }
253
- const skillMdPath = path.resolve(skill.skillPath, "SKILL.md");
254
- try {
255
- const content = fs.readFileSync(skillMdPath, "utf-8");
256
- const body = extractBody(content);
257
- activationLogger.info("Skill activated", {
258
- name: skill.metadata.name,
259
- skillPath: skill.skillPath,
260
- bodyLength: body.length
261
- });
262
- registry.emitEvent("skill:activated" /* SKILL_ACTIVATED */, {
263
- name: skill.metadata.name,
264
- skillPath: skill.skillPath,
265
- bodyLength: body.length
266
- });
267
- return body;
268
- } catch (error) {
269
- const message = error instanceof Error ? error.message : String(error);
270
- activationLogger.error("Skill activation failed \u2014 read error", {
271
- name,
272
- skillPath: skill.skillPath,
273
- error: message
274
- });
275
- return `Failed to read skill "${name}" instructions: ${message}`;
276
- }
277
- }).build();
278
- }
279
-
280
245
  // src/trust.ts
281
246
  var SCRIPT_PATH_PREFIX = "scripts/";
282
247
  var SCRIPT_PATH_EXACT = "scripts";
@@ -337,6 +302,92 @@ function evaluateTrustPolicy(resourcePath, trustLevel, allowUntrustedScripts = f
337
302
  };
338
303
  }
339
304
  }
305
+ function evaluateSkillActivationPolicy(trustLevel) {
306
+ switch (trustLevel) {
307
+ case "workspace":
308
+ return {
309
+ allowed: true,
310
+ reason: "workspace-skill-activation" /* WORKSPACE_SKILL_ACTIVATION */,
311
+ message: "Skill activation allowed \u2014 skill root has workspace trust"
312
+ };
313
+ case "trusted":
314
+ return {
315
+ allowed: true,
316
+ reason: "trusted-skill-activation" /* TRUSTED_SKILL_ACTIVATION */,
317
+ message: "Skill activation allowed \u2014 skill root is explicitly trusted"
318
+ };
319
+ case "untrusted":
320
+ return {
321
+ allowed: false,
322
+ reason: "untrusted-skill-activation-denied" /* UNTRUSTED_SKILL_ACTIVATION_DENIED */,
323
+ message: `Skill activation blocked \u2014 skill root is untrusted. Untrusted skills remain discoverable, but their full SKILL.md bodies are blocked by default. Promote the skill root to 'trusted' or 'workspace' trust level after review to allow activation.`
324
+ };
325
+ default:
326
+ return {
327
+ allowed: false,
328
+ reason: "unknown-trust-level" /* UNKNOWN_TRUST_LEVEL */,
329
+ message: `Skill activation blocked \u2014 trust level "${trustLevel}" is unknown and is treated as untrusted for security.`
330
+ };
331
+ }
332
+ }
333
+
334
+ // src/activation-activate-tool.ts
335
+ function createActivateSkillTool(registry) {
336
+ return new core.ToolBuilder().name("activate-skill").description(
337
+ "Activate an Agent Skill by name, loading its full instructions for trusted roots. Returns the complete SKILL.md body content for workspace or explicitly trusted skills. Use this when you see a relevant skill in <available_skills> or <untrusted_skills>; activation is blocked for untrusted roots until they are promoted."
338
+ ).category(core.ToolCategory.SKILLS).tags(["skill", "activation", "agent-skills"]).schema(activateSkillSchema).implement(async ({ name }) => {
339
+ const skill = registry.get(name);
340
+ if (!skill) {
341
+ const { availableCount, errorMessage } = buildMissingSkillMessage(registry, name);
342
+ activationLogger.warn("Skill activation failed \u2014 not found", { name, availableCount });
343
+ return errorMessage;
344
+ }
345
+ const policyDecision = evaluateSkillActivationPolicy(skill.trustLevel);
346
+ if (!policyDecision.allowed) {
347
+ activationLogger.warn("Skill activation blocked \u2014 trust policy", {
348
+ name,
349
+ trustLevel: skill.trustLevel,
350
+ reason: policyDecision.reason,
351
+ message: policyDecision.message
352
+ });
353
+ registry.emitEvent("trust:policy-denied" /* TRUST_POLICY_DENIED */, {
354
+ name: skill.metadata.name,
355
+ resourcePath: "SKILL.md",
356
+ trustLevel: skill.trustLevel,
357
+ reason: policyDecision.reason,
358
+ message: policyDecision.message
359
+ });
360
+ return policyDecision.message;
361
+ }
362
+ const skillMdPath = path.resolve(skill.skillPath, "SKILL.md");
363
+ try {
364
+ const content = fs.readFileSync(skillMdPath, "utf-8");
365
+ const body = extractBody(content);
366
+ activationLogger.info("Skill activated", {
367
+ name: skill.metadata.name,
368
+ skillPath: skill.skillPath,
369
+ bodyLength: body.length,
370
+ trustLevel: skill.trustLevel,
371
+ activationReason: policyDecision.reason
372
+ });
373
+ registry.emitEvent("skill:activated" /* SKILL_ACTIVATED */, {
374
+ name: skill.metadata.name,
375
+ skillPath: skill.skillPath,
376
+ bodyLength: body.length,
377
+ trustLevel: skill.trustLevel
378
+ });
379
+ return body;
380
+ } catch (error) {
381
+ const message = error instanceof Error ? error.message : String(error);
382
+ activationLogger.error("Skill activation failed \u2014 read error", {
383
+ name,
384
+ skillPath: skill.skillPath,
385
+ error: message
386
+ });
387
+ return `Failed to read skill "${name}" instructions: ${message}`;
388
+ }
389
+ }).build();
390
+ }
340
391
  function resolveResourcePath(skillPath, resourcePath) {
341
392
  if (path.isAbsolute(resourcePath)) {
342
393
  return { success: false, error: "Absolute resource paths are not allowed" };
@@ -366,7 +417,7 @@ function resolveResourcePath(skillPath, resourcePath) {
366
417
  // src/activation-resource-tool.ts
367
418
  function createReadSkillResourceTool(registry) {
368
419
  return new core.ToolBuilder().name("read-skill-resource").description(
369
- "Read a resource file from an activated Agent Skill. Returns the content of a file within the skill directory (e.g., references/, scripts/, assets/). The path must be relative to the skill root and cannot traverse outside it."
420
+ "Read a resource file from an activated Agent Skill. Returns the content of a file within the skill directory (e.g., references/, scripts/, assets/). The path must be relative to the skill root and cannot traverse outside it. SKILL.md is only readable from workspace or trusted roots."
370
421
  ).category(core.ToolCategory.SKILLS).tags(["skill", "resource", "agent-skills"]).schema(readSkillResourceSchema).implement(async ({ name, path: resourcePath }) => {
371
422
  const skill = registry.get(name);
372
423
  if (!skill) {
@@ -383,6 +434,34 @@ function createReadSkillResourceTool(registry) {
383
434
  });
384
435
  return pathResult.error;
385
436
  }
437
+ const skillInstructionsPath = path.resolve(skill.skillPath, "SKILL.md");
438
+ let isSkillInstructions = pathResult.resolvedPath.toLowerCase() === skillInstructionsPath.toLowerCase();
439
+ if (!isSkillInstructions) {
440
+ try {
441
+ isSkillInstructions = fs.realpathSync(pathResult.resolvedPath).toLowerCase() === fs.realpathSync(skillInstructionsPath).toLowerCase();
442
+ } catch {
443
+ }
444
+ }
445
+ if (isSkillInstructions) {
446
+ const activationDecision = evaluateSkillActivationPolicy(skill.trustLevel);
447
+ if (!activationDecision.allowed) {
448
+ activationLogger.warn("Skill resource load blocked \u2014 skill instructions trust policy", {
449
+ name,
450
+ resourcePath,
451
+ trustLevel: skill.trustLevel,
452
+ reason: activationDecision.reason,
453
+ message: activationDecision.message
454
+ });
455
+ registry.emitEvent("trust:policy-denied" /* TRUST_POLICY_DENIED */, {
456
+ name: skill.metadata.name,
457
+ resourcePath: "SKILL.md",
458
+ trustLevel: skill.trustLevel,
459
+ reason: activationDecision.reason,
460
+ message: activationDecision.message
461
+ });
462
+ return activationDecision.message;
463
+ }
464
+ }
386
465
  const policyDecision = evaluateTrustPolicy(
387
466
  resourcePath,
388
467
  skill.trustLevel,
@@ -569,10 +648,19 @@ function generateSkillPrompt(config, allSkills, totalDiscovered, options) {
569
648
  const requested = new Set(options.skills);
570
649
  skills = skills.filter((skill) => requested.has(skill.metadata.name));
571
650
  }
651
+ let trustedSkills = skills.filter(
652
+ (skill) => skill.trustLevel === "workspace" || skill.trustLevel === "trusted"
653
+ );
654
+ let untrustedSkills = skills.filter(
655
+ (skill) => skill.trustLevel !== "workspace" && skill.trustLevel !== "trusted"
656
+ );
572
657
  if (config.maxDiscoveredSkills !== void 0 && config.maxDiscoveredSkills >= 0) {
573
- skills = skills.slice(0, config.maxDiscoveredSkills);
658
+ const trustedLimit = Math.min(config.maxDiscoveredSkills, trustedSkills.length);
659
+ trustedSkills = trustedSkills.slice(0, trustedLimit);
660
+ untrustedSkills = untrustedSkills.slice(0, config.maxDiscoveredSkills - trustedLimit);
661
+ skills = [...trustedSkills, ...untrustedSkills];
574
662
  }
575
- if (skills.length === 0) {
663
+ if (trustedSkills.length === 0 && untrustedSkills.length === 0) {
576
664
  logger4.debug("Skill prompt generation produced empty result", {
577
665
  totalDiscovered,
578
666
  filterApplied: !!(options?.skills && options.skills.length > 0),
@@ -580,12 +668,21 @@ function generateSkillPrompt(config, allSkills, totalDiscovered, options) {
580
668
  });
581
669
  return "";
582
670
  }
583
- const xml = `<available_skills>
584
- ${skills.map(renderSkillEntry).join("\n")}
585
- </available_skills>`;
671
+ const sections = [];
672
+ if (trustedSkills.length > 0) {
673
+ sections.push(`<available_skills>
674
+ ${trustedSkills.map(renderTrustedSkillEntry).join("\n")}
675
+ </available_skills>`);
676
+ }
677
+ if (untrustedSkills.length > 0) {
678
+ sections.push(renderUntrustedSkillsSection(untrustedSkills));
679
+ }
680
+ const xml = sections.join("\n");
586
681
  const estimatedTokens = Math.ceil(xml.length / 4);
587
682
  logger4.info("Skill prompt generated", {
588
683
  skillCount: skills.length,
684
+ trustedSkillCount: trustedSkills.length,
685
+ untrustedSkillCount: untrustedSkills.length,
589
686
  totalDiscovered,
590
687
  filterApplied: !!(options?.skills && options.skills.length > 0),
591
688
  ...config.maxDiscoveredSkills !== void 0 ? { maxCap: config.maxDiscoveredSkills } : {},
@@ -594,12 +691,32 @@ ${skills.map(renderSkillEntry).join("\n")}
594
691
  });
595
692
  return xml;
596
693
  }
597
- function renderSkillEntry(skill) {
694
+ function renderTrustedSkillEntry(skill) {
695
+ return [
696
+ " <skill>",
697
+ ` <name>${escapeXml(skill.metadata.name)}</name>`,
698
+ ` <description>${escapeXml(skill.metadata.description)}</description>`,
699
+ ` <location>${escapeXml(skill.skillPath)}</location>`,
700
+ ` <trust>${escapeXml(skill.trustLevel)}</trust>`,
701
+ " </skill>"
702
+ ].join("\n");
703
+ }
704
+ function renderUntrustedSkillsSection(skills) {
705
+ return [
706
+ "<untrusted_skills>",
707
+ " <trust_notice>Untrusted skills are discoverable, but their full SKILL.md bodies stay blocked until the root is promoted to trusted or workspace status.</trust_notice>",
708
+ ...skills.map(renderUntrustedSkillEntry),
709
+ "</untrusted_skills>"
710
+ ].join("\n");
711
+ }
712
+ function renderUntrustedSkillEntry(skill) {
598
713
  return [
599
714
  " <skill>",
600
715
  ` <name>${escapeXml(skill.metadata.name)}</name>`,
601
716
  ` <description>${escapeXml(skill.metadata.description)}</description>`,
602
717
  ` <location>${escapeXml(skill.skillPath)}</location>`,
718
+ ` <trust>${escapeXml(skill.trustLevel)}</trust>`,
719
+ " <activation_policy>requires-trusted-root</activation_policy>",
603
720
  " </skill>"
604
721
  ].join("\n");
605
722
  }
@@ -696,6 +813,7 @@ exports.TrustPolicyReason = TrustPolicyReason;
696
813
  exports.createActivateSkillTool = createActivateSkillTool;
697
814
  exports.createReadSkillResourceTool = createReadSkillResourceTool;
698
815
  exports.createSkillActivationTools = createSkillActivationTools;
816
+ exports.evaluateSkillActivationPolicy = evaluateSkillActivationPolicy;
699
817
  exports.evaluateTrustPolicy = evaluateTrustPolicy;
700
818
  exports.expandHome = expandHome;
701
819
  exports.isScriptResource = isScriptResource;