@graphorin/skills 0.5.0 → 0.6.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/CHANGELOG.md +33 -0
- package/README.md +13 -11
- package/anthropic-spec-snapshot.json +1 -1
- package/dist/activation/index.js +2 -2
- package/dist/activation/index.js.map +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/frontmatter/index.d.ts +1 -1
- package/dist/frontmatter/index.d.ts.map +1 -1
- package/dist/frontmatter/index.js +9 -4
- package/dist/frontmatter/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/loader/index.d.ts.map +1 -1
- package/dist/loader/index.js +3 -3
- package/dist/loader/index.js.map +1 -1
- package/dist/migration/index.d.ts +2 -2
- package/dist/migration/index.js +2 -2
- package/dist/migration/index.js.map +1 -1
- package/dist/registry/bridge.js.map +1 -1
- package/dist/registry/index.d.ts +3 -3
- package/dist/registry/index.js.map +1 -1
- package/dist/spec/index.d.ts +13 -9
- package/dist/spec/index.d.ts.map +1 -1
- package/dist/spec/index.js +13 -9
- package/dist/spec/index.js.map +1 -1
- package/dist/types/index.d.ts +16 -16
- package/package.json +4 -4
package/dist/loader/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DEFAULT_MEDIA_TYPES: Readonly<Record<string, string>>","installArgs: Parameters<typeof installSkillFromNpm>[0]","installArgs: Parameters<typeof installSkillFromGit>[0]","resources: SkillResource[]","stats: Stats","skillMd: string","effectiveTrust: SkillsTrustLevel","metadata: SkillMetadata","signature: SkillSignatureVerificationResult | undefined","out: SkillResource[]","validateOptions: Parameters<typeof validateFrontmatter>[1]","diagnostics: FrontmatterDiagnostic[]","trustLevel: SkillsTrustLevel","result: Mutable<SkillMetadata>","bodyCache: string | null","resourcesCache: ReadonlyArray<SkillResource> | null","bodyPromise: Promise<string> | null","resourcesPromise: Promise<ReadonlyArray<SkillResource>> | null"],"sources":["../../src/loader/index.ts"],"sourcesContent":["/**\n * Skill loader.\n *\n * Implements three-tier progressive disclosure:\n *\n * - **Tier 1** (always): {@link Skill.metadata} — parsed at load\n * time from the SKILL.md frontmatter.\n * - **Tier 2** (on activation): {@link Skill.body} — the loader\n * reads the markdown body lazily; subsequent calls return the\n * cached value.\n * - **Tier 3** (on demand): {@link Skill.resources} — the loader\n * walks the skill directory lazily; resource bytes are only read\n * when {@link SkillResource.read} is invoked.\n *\n * The loader supports four sources:\n *\n * - `{ kind: 'folder', path }` — read SKILL.md from disk.\n * - `{ kind: 'npm-package', ... }` — install via the supply-chain\n * helper from `@graphorin/security/supply-chain`, then read.\n * - `{ kind: 'git-repo', ... }` — shallow-clone via the\n * supply-chain helper, then read.\n * - `{ kind: 'inline', skill: ... }` — caller supplies the parsed\n * payload; the loader only validates the frontmatter. Useful for\n * tests and bundled defaults.\n *\n * @packageDocumentation\n */\n\nimport type { Stats } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join, relative, resolve, sep } from 'node:path';\n\nimport {\n installSkillFromGit,\n installSkillFromNpm,\n type ResolvedSkillTrustPolicy,\n resolveTrustPolicy,\n type SkillSignatureVerificationResult,\n type SupplyChainPolicy,\n verifySkillSignature,\n} from '@graphorin/security/supply-chain';\n\nimport {\n InputFilterRequiredError,\n SkillFrontmatterConflictError,\n SkillLoadError,\n SkillRequiredFieldMissingError,\n SkillRuntimeCompatError,\n} from '../errors/index.js';\nimport {\n parseAllowedToolsValue,\n parseFrontmatterYaml,\n parseHandoffInputFilter,\n parseToolsField,\n splitSkillMd,\n type ValidatedFrontmatter,\n validateFrontmatter,\n} from '../frontmatter/index.js';\nimport type {\n FrontmatterDiagnostic,\n FrontmatterValidatorPolicy,\n HandoffInputFilterDeclaration,\n InlineSkillTool,\n Skill,\n SkillMetadata,\n SkillResource,\n SkillSource,\n SkillsTrustLevel,\n SkillToolDeclaration,\n UnknownFieldPolicy,\n} from '../types/index.js';\n\n/** Options forwarded to {@link loadSkillFromSource}. */\nexport interface LoadSkillOptions {\n readonly conflictPolicy?: FrontmatterValidatorPolicy;\n readonly unknownFieldPolicy?: UnknownFieldPolicy;\n readonly runtimeVersion?: string;\n readonly supplyChainPolicy?: SupplyChainPolicy;\n readonly signal?: AbortSignal;\n /** Override the bundled MIME-type guesser for resource files. */\n readonly mediaTypeFor?: (path: string) => string | undefined;\n}\n\n/** Aggregate options accepted by {@link loadSkills}. */\nexport interface LoadSkillsOptions extends LoadSkillOptions {\n /**\n * Fail fast if any source produces a {@link SkillLoadError}. When\n * `false` (default) the loader logs the source path on the\n * diagnostic and continues with the next source.\n */\n readonly throwOnSourceError?: boolean;\n}\n\nconst SKILL_MANIFEST_FILENAME = 'SKILL.md';\n\nconst DEFAULT_MEDIA_TYPES: Readonly<Record<string, string>> = Object.freeze({\n '.md': 'text/markdown; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n '.json': 'application/json',\n '.yaml': 'application/yaml',\n '.yml': 'application/yaml',\n '.ts': 'text/typescript; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.cjs': 'text/javascript; charset=utf-8',\n '.py': 'text/x-python; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.pdf': 'application/pdf',\n});\n\n/**\n * Load a single skill from any supported source. The loader runs the\n * full frontmatter validator and resolves the supply-chain trust\n * policy so the returned {@link Skill} is ready to be inserted into a\n * `SkillRegistry`.\n *\n * @stable\n */\nexport async function loadSkillFromSource(\n source: SkillSource,\n options: LoadSkillOptions = {},\n): Promise<Skill> {\n switch (source.kind) {\n case 'folder':\n return loadFromFolder(source.path, source, options);\n case 'inline':\n return loadFromInline(source, options);\n case 'npm-package': {\n const installArgs: Parameters<typeof installSkillFromNpm>[0] = {\n packageName: source.packageName,\n };\n if (source.version !== undefined)\n (installArgs as Mutable<typeof installArgs>).version = source.version;\n if (source.trustLevel !== undefined)\n (installArgs as Mutable<typeof installArgs>).trustLevel = source.trustLevel;\n if (options.supplyChainPolicy !== undefined)\n (installArgs as Mutable<typeof installArgs>).policy = options.supplyChainPolicy;\n if (options.signal !== undefined)\n (installArgs as Mutable<typeof installArgs>).signal = options.signal;\n const status = await installSkillFromNpm(installArgs);\n const installPath = status.installPath;\n if (installPath === undefined) {\n throw new SkillLoadError(\n source.packageName,\n 'npm install completed without an install path; cannot continue.',\n );\n }\n const resolved = await locateSkillRoot(installPath, source.packageName);\n return loadFromFolder(resolved, source, options, status.signature);\n }\n case 'git-repo': {\n const installArgs: Parameters<typeof installSkillFromGit>[0] = {\n repoUrl: source.url,\n };\n if (source.ref !== undefined) (installArgs as Mutable<typeof installArgs>).ref = source.ref;\n if (source.trustLevel !== undefined)\n (installArgs as Mutable<typeof installArgs>).trustLevel = source.trustLevel;\n if (options.supplyChainPolicy !== undefined)\n (installArgs as Mutable<typeof installArgs>).policy = options.supplyChainPolicy;\n if (options.signal !== undefined)\n (installArgs as Mutable<typeof installArgs>).signal = options.signal;\n const status = await installSkillFromGit(installArgs);\n const installPath = status.installPath;\n if (installPath === undefined) {\n throw new SkillLoadError(\n source.url,\n 'git clone completed without a clone path; cannot continue.',\n );\n }\n const resolved = await locateSkillRoot(installPath, source.url);\n return loadFromFolder(resolved, source, options, status.signature);\n }\n default: {\n const exhaustive: never = source;\n void exhaustive;\n throw new SkillLoadError('<unknown>', 'unsupported skill source.');\n }\n }\n}\n\n/**\n * Load multiple skills concurrently. The sources are loaded in parallel and\n * the returned array preserves input order. When `throwOnSourceError === false`\n * (default) a failing source is logged and skipped; otherwise the first\n * rejection propagates out unchanged.\n *\n * @stable\n */\nexport async function loadSkills(\n sources: ReadonlyArray<SkillSource>,\n options: LoadSkillsOptions = {},\n): Promise<ReadonlyArray<Skill>> {\n const { throwOnSourceError = false, ...inner } = options;\n const loaded = await Promise.all(\n sources.map(async (source): Promise<Skill | null> => {\n try {\n return await loadSkillFromSource(source, inner);\n } catch (err) {\n if (throwOnSourceError) throw err;\n // We cannot create a Skill from a failed source, but we want to\n // preserve a structured diagnostic so callers can audit which\n // sources failed without re-running the loader.\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Failed to load source ${describeSource(source)}: ${(err as Error).message}`,\n );\n return null;\n }\n }),\n );\n return Object.freeze(loaded.filter((skill): skill is Skill => skill !== null));\n}\n\nasync function loadFromInline(\n source: Extract<SkillSource, { kind: 'inline' }>,\n options: LoadSkillOptions,\n): Promise<Skill> {\n const trustPolicy = resolveTrustPolicy(\n { kind: 'folder', path: source.skill.basePath ?? '<inline>' },\n 'trusted',\n );\n const { metadata, diagnostics, body } = parseAndValidate(source.skill.skillMd, options);\n const resourceList = source.skill.resources ?? [];\n const resources: SkillResource[] = resourceList.map((entry) => {\n const relativePath = entry.path;\n const path =\n source.skill.basePath !== undefined ? join(source.skill.basePath, entry.path) : entry.path;\n const mediaType = options.mediaTypeFor?.(entry.path) ?? guessMediaType(entry.path);\n return Object.freeze({\n path,\n relativePath,\n ...(mediaType === undefined ? {} : { mediaType }),\n async read() {\n return new TextEncoder().encode(entry.content);\n },\n async readText() {\n return entry.content;\n },\n } satisfies SkillResource);\n });\n return buildSkill({\n metadata,\n diagnostics,\n body,\n source,\n trustPolicy,\n ...(source.skill.tools === undefined ? {} : { tools: source.skill.tools }),\n bodyLoader: () => Promise.resolve(body),\n resourceLoader: () => Promise.resolve(resources),\n });\n}\n\nasync function loadFromFolder(\n folderPath: string,\n source: SkillSource,\n options: LoadSkillOptions,\n precomputedSignature?: SkillSignatureVerificationResult | undefined,\n): Promise<Skill> {\n const absolutePath = resolve(folderPath);\n let stats: Stats;\n try {\n stats = await stat(absolutePath);\n } catch (err) {\n throw new SkillLoadError(\n describeSource(source),\n `Could not stat skill directory '${absolutePath}'.`,\n { cause: err },\n );\n }\n if (!stats.isDirectory()) {\n throw new SkillLoadError(\n describeSource(source),\n `Skill source must be a directory; '${absolutePath}' is not.`,\n );\n }\n const manifestPath = join(absolutePath, SKILL_MANIFEST_FILENAME);\n let skillMd: string;\n try {\n skillMd = await readFile(manifestPath, 'utf8');\n } catch (err) {\n throw new SkillLoadError(describeSource(source), `SKILL.md is missing at '${manifestPath}'.`, {\n hint: 'Add a SKILL.md file with a YAML frontmatter block.',\n cause: err,\n });\n }\n const parsed = parseAndValidate(skillMd, options);\n const { diagnostics, body } = parsed;\n // RP-9: trust is granted by the integrator, never the artifact. An operator\n // override on the source wins; absent one, a folder's self-declared\n // 'trusted'/'trusted-with-scripts' is capped at 'unknown' so a downloaded\n // directory cannot promote itself out of the sandbox + taint-marking. The\n // resolved level is written back onto the metadata so every downstream\n // consumer (tool stamping, sandbox tier, inbound sanitization) sees it.\n const operatorTrust = extractTrustLevel(source);\n const effectiveTrust: SkillsTrustLevel =\n operatorTrust ??\n (source.kind === 'folder'\n ? capSelfDeclaredTrust(parsed.metadata.graphorinTrustLevel)\n : parsed.metadata.graphorinTrustLevel);\n const metadata: SkillMetadata =\n effectiveTrust === parsed.metadata.graphorinTrustLevel\n ? parsed.metadata\n : (Object.freeze({\n ...parsed.metadata,\n graphorinTrustLevel: effectiveTrust,\n }) as SkillMetadata);\n\n let signature: SkillSignatureVerificationResult | undefined = precomputedSignature;\n if (signature === undefined && metadata.graphorinSignaturePresent) {\n try {\n signature = await verifySkillSignature({\n skillMd,\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n });\n } catch (err) {\n // Surface the signature failure as a diagnostic — the supply-\n // chain installer is the one that decides whether to refuse the\n // install based on the resolved trust policy. The folder loader\n // tolerates an unverifiable signature so operators can iterate\n // on local skills before signing.\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Signature verification of '${metadata.name}' failed: ${(err as Error).message}`,\n );\n }\n }\n\n const trustPolicy = resolveTrustPolicy(\n source.kind === 'folder'\n ? { kind: 'folder', path: absolutePath }\n : source.kind === 'npm-package'\n ? source.version === undefined\n ? { kind: 'npm-package', packageName: source.packageName }\n : { kind: 'npm-package', packageName: source.packageName, version: source.version }\n : source.kind === 'git-repo'\n ? source.ref === undefined\n ? { kind: 'git-repo', url: source.url }\n : { kind: 'git-repo', url: source.url, ref: source.ref }\n : { kind: 'folder', path: absolutePath },\n coerceForSupplyChain(metadata.graphorinTrustLevel),\n );\n\n return buildSkill({\n metadata,\n diagnostics,\n body,\n source,\n trustPolicy,\n basePath: absolutePath,\n ...(signature === undefined ? {} : { signature }),\n bodyLoader: () => Promise.resolve(body),\n resourceLoader: async (signal) => listResources(absolutePath, options, signal),\n });\n}\n\nasync function listResources(\n rootPath: string,\n options: LoadSkillOptions,\n signal?: AbortSignal,\n): Promise<ReadonlyArray<SkillResource>> {\n const out: SkillResource[] = [];\n for await (const file of walk(rootPath, signal)) {\n const relativePath = relative(rootPath, file);\n if (relativePath === SKILL_MANIFEST_FILENAME) continue;\n const mediaType = options.mediaTypeFor?.(relativePath) ?? guessMediaType(relativePath);\n out.push(\n Object.freeze({\n path: file,\n relativePath: relativePath.split(sep).join('/'),\n ...(mediaType === undefined ? {} : { mediaType }),\n async read(innerSignal?: AbortSignal): Promise<Uint8Array> {\n const buffer = await readFile(file, {\n ...(innerSignal === undefined ? {} : { signal: innerSignal }),\n });\n return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n },\n async readText(innerSignal?: AbortSignal): Promise<string> {\n return readFile(file, {\n encoding: 'utf8',\n ...(innerSignal === undefined ? {} : { signal: innerSignal }),\n });\n },\n } satisfies SkillResource),\n );\n }\n return Object.freeze(out);\n}\n\nasync function* walk(root: string, signal?: AbortSignal): AsyncIterable<string> {\n const entries = await readdir(root, { withFileTypes: true });\n for (const entry of entries) {\n if (signal?.aborted === true) return;\n const full = join(root, entry.name);\n if (entry.isDirectory()) {\n yield* walk(full, signal);\n continue;\n }\n if (entry.isFile()) {\n yield full;\n }\n }\n}\n\nasync function locateSkillRoot(installPath: string, sourceLabel: string): Promise<string> {\n const direct = join(installPath, SKILL_MANIFEST_FILENAME);\n try {\n await stat(direct);\n return installPath;\n } catch {\n // continue\n }\n // RP-10: npm packages land under `node_modules/<packageName>`. The label is\n // the package name for npm sources; for git it is a URL and this probe\n // simply misses, falling through to the one-level-deep scan below.\n const nodeModulesRoot = join(installPath, 'node_modules', sourceLabel);\n try {\n await stat(join(nodeModulesRoot, SKILL_MANIFEST_FILENAME));\n return nodeModulesRoot;\n } catch {\n // continue\n }\n const entries = await readdir(installPath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const child = join(installPath, entry.name, SKILL_MANIFEST_FILENAME);\n try {\n await stat(child);\n return join(installPath, entry.name);\n } catch {\n // continue\n }\n }\n }\n throw new SkillLoadError(\n sourceLabel,\n `Could not locate SKILL.md inside '${installPath}'. Expected at the top-level or one folder deep.`,\n );\n}\n\ninterface ParsedAndValidated {\n readonly metadata: SkillMetadata;\n readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;\n readonly body: string;\n}\n\nfunction parseAndValidate(skillMd: string, options: LoadSkillOptions): ParsedAndValidated {\n const split = splitSkillMd(skillMd);\n const frontmatter = parseFrontmatterYaml(split.frontmatter);\n const validateOptions: Parameters<typeof validateFrontmatter>[1] = {};\n if (options.conflictPolicy !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).conflictPolicy = options.conflictPolicy;\n if (options.unknownFieldPolicy !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).unknownFieldPolicy =\n options.unknownFieldPolicy;\n if (options.runtimeVersion !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).runtimeVersion = options.runtimeVersion;\n const validated = validateFrontmatter(frontmatter, validateOptions);\n for (const diag of validated.diagnostics) {\n if (diag.severity === 'error' && diag.kind === 'missing-required-field') {\n throw new SkillRequiredFieldMissingError(diag.field, {\n ...(diag.hint === undefined ? {} : { hint: diag.hint }),\n });\n }\n }\n const metadata = buildMetadata(validated, frontmatter);\n const diagnostics: FrontmatterDiagnostic[] = [...validated.diagnostics];\n appendUntrustedHandoffDiagnostic(metadata, diagnostics, options.conflictPolicy);\n appendInvalidFieldTypeDiagnostics(frontmatter, validated, diagnostics);\n enforceErrorPolicy(metadata, validated, diagnostics, options);\n return Object.freeze({\n metadata,\n diagnostics: Object.freeze(diagnostics),\n body: split.body,\n });\n}\n\n/**\n * RP-11(d): a frontmatter field that is present but unparseable must not be\n * silently dropped (`?? []`). Surface an `invalid-field-type` diagnostic so\n * callers can audit the rejected value, as the parser contracts mandate.\n */\nfunction appendInvalidFieldTypeDiagnostics(\n raw: Record<string, unknown>,\n validated: ValidatedFrontmatter,\n diagnostics: FrontmatterDiagnostic[],\n): void {\n const handoffRaw = validated.resolved.handoffInputFilter.value;\n if (handoffRaw !== undefined && parseHandoffInputFilter(handoffRaw) === null) {\n diagnostics.push(\n Object.freeze({\n kind: 'invalid-field-type',\n field: 'handoff-input-filter',\n severity: 'warn',\n message: \"'handoff-input-filter' has an unsupported shape; the declaration was ignored.\",\n hint: \"Use 'lastUser', 'lastN: <n>', 'none', or 'full'.\",\n }),\n );\n }\n const toolsRaw = raw['graphorin-tools'];\n if (toolsRaw !== undefined && parseToolsField(toolsRaw) === null) {\n diagnostics.push(\n Object.freeze({\n kind: 'invalid-field-type',\n field: 'graphorin-tools',\n severity: 'warn',\n message: \"'graphorin-tools' must be a list of tool declarations; the value was ignored.\",\n hint: 'Provide a YAML list, e.g. `graphorin-tools:` with `- name: read_file` entries.',\n }),\n );\n }\n}\n\n/**\n * RP-11(b): under `conflictPolicy: 'error'`, an error-severity diagnostic\n * fails the load through the matching typed exception instead of being\n * silently surfaced. Default (`'warn'`) keeps these as diagnostics.\n */\nfunction enforceErrorPolicy(\n metadata: SkillMetadata,\n validated: ValidatedFrontmatter,\n diagnostics: ReadonlyArray<FrontmatterDiagnostic>,\n options: LoadSkillOptions,\n): void {\n if (options.conflictPolicy !== 'error') return;\n for (const diag of diagnostics) {\n if (diag.severity !== 'error') continue;\n if (diag.kind === 'invalid-runtime-compat') {\n const declared = validated.resolved.runtimeCompat.value;\n throw new SkillRuntimeCompatError(\n metadata.name,\n typeof declared === 'string' ? declared : String(declared),\n options.runtimeVersion ?? '<unknown>',\n diag.hint === undefined ? undefined : { hint: diag.hint },\n );\n }\n if (diag.kind === 'conflict') {\n throw new SkillFrontmatterConflictError(\n metadata.name,\n diag.field,\n [diag.field],\n diag.hint === undefined ? undefined : { hint: diag.hint },\n );\n }\n }\n}\n\nfunction buildMetadata(\n validated: ValidatedFrontmatter,\n raw: Record<string, unknown>,\n): SkillMetadata {\n const name = String(validated.resolved.name.value ?? '').trim();\n const description = String(validated.resolved.description.value ?? '').trim();\n const allowedToolsRaw = validated.resolved.allowedTools.value;\n const allowedTools =\n allowedToolsRaw === undefined ? undefined : parseAllowedToolsValue(allowedToolsRaw);\n const handoffFilter =\n validated.resolved.handoffInputFilter.value === undefined\n ? undefined\n : parseHandoffInputFilter(validated.resolved.handoffInputFilter.value);\n const trustLevelRaw = validated.resolved.trustLevel.value;\n // Phase 08 § \"Frontmatter validator\" recognises four explicit trust\n // levels: 'trusted' | 'trusted-with-scripts' | 'unknown' |\n // 'untrusted'. A skill that did not declare the field is treated as\n // 'unknown' (sandbox forced; signature optional) — that is the\n // default-deny posture per DEC-148 risk mitigation.\n const trustLevel: SkillsTrustLevel = (() => {\n if (\n trustLevelRaw === 'trusted' ||\n trustLevelRaw === 'trusted-with-scripts' ||\n trustLevelRaw === 'untrusted' ||\n trustLevelRaw === 'unknown'\n ) {\n return trustLevelRaw;\n }\n return 'unknown';\n })();\n const sandbox = validated.resolved.sandbox.value;\n const sensitivity = validated.resolved.sensitivity.value;\n const sensitivityDefaultsRaw = validated.resolved.sensitivityDefaults.value;\n const sensitivityDefaults =\n sensitivityDefaultsRaw !== null && typeof sensitivityDefaultsRaw === 'object'\n ? Object.freeze({ ...(sensitivityDefaultsRaw as Record<string, string>) })\n : undefined;\n const metadataObj = validated.resolved.metadata.value;\n const metadataValue =\n metadataObj !== null && typeof metadataObj === 'object'\n ? Object.freeze({ ...(metadataObj as Record<string, unknown>) })\n : undefined;\n const result: Mutable<SkillMetadata> = {\n name,\n description,\n disableModelInvocation: validated.resolved.disableModelInvocation.value === true,\n graphorinTrustLevel: trustLevel,\n graphorinSignaturePresent: 'graphorin-signature' in raw,\n raw: Object.freeze({ ...raw }),\n };\n if (typeof validated.resolved.license.value === 'string')\n result.license = validated.resolved.license.value;\n if (typeof validated.resolved.compatibility.value === 'string')\n result.compatibility = validated.resolved.compatibility.value;\n if (metadataValue !== undefined) result.metadata = metadataValue;\n if (allowedTools !== undefined && allowedTools !== null)\n result.allowedTools = Object.freeze([...allowedTools]);\n if (typeof validated.resolved.runtimeCompat.value === 'string')\n result.graphorinRuntimeCompat = validated.resolved.runtimeCompat.value;\n if (typeof sensitivity === 'string') result.graphorinSensitivity = sensitivity;\n if (sensitivityDefaults !== undefined) result.graphorinSensitivityDefaults = sensitivityDefaults;\n if (sandbox !== null && typeof sandbox === 'object')\n result.graphorinSandbox = Object.freeze({ ...(sandbox as Record<string, unknown>) });\n if (handoffFilter !== undefined && handoffFilter !== null)\n result.graphorinHandoffInputFilter = handoffFilter;\n if (typeof validated.resolved.anthropicSpec.value === 'string')\n result.graphorinAnthropicSpec = validated.resolved.anthropicSpec.value;\n if (typeof validated.resolved.version.value === 'string')\n result.graphorinVersion = validated.resolved.version.value;\n return Object.freeze(result) as SkillMetadata;\n}\n\nfunction appendUntrustedHandoffDiagnostic(\n metadata: SkillMetadata,\n diagnostics: FrontmatterDiagnostic[],\n conflictPolicy: FrontmatterValidatorPolicy = 'warn',\n): void {\n // Untrusted skills that declared `filter: full` are an explicit\n // attempt to exfiltrate the entire conversation through a sub-agent\n // — Phase 08 / ADR-040 mandate that we WARN and ignore. The agent\n // runtime in Phase 12 will refuse the filter regardless of this\n // diagnostic.\n if (metadata.graphorinTrustLevel === 'untrusted') {\n if (metadata.graphorinHandoffInputFilter?.kind === 'full') {\n diagnostics.push(\n Object.freeze({\n kind: 'untrusted-handoff-filter-required',\n field: 'graphorin-handoff-input-filter',\n severity: 'warn',\n message: `Untrusted skill '${metadata.name}' declared 'graphorin-handoff-input-filter: full'. The full-message filter is rejected for untrusted skills; the runtime will fall back to 'lastUser'.`,\n hint: \"Replace 'full' with 'lastUser' (or another bounded filter) to silence this diagnostic.\",\n }),\n );\n } else if (metadata.graphorinHandoffInputFilter === undefined) {\n diagnostics.push(\n Object.freeze({\n kind: 'untrusted-handoff-filter-required',\n field: 'graphorin-handoff-input-filter',\n severity: conflictPolicy === 'error' ? 'error' : 'warn',\n message: `Untrusted skill '${metadata.name}' did not declare 'graphorin-handoff-input-filter'. The runtime will throw if the skill invokes Agent.toTool()/handoff().`,\n hint: \"Add 'graphorin-handoff-input-filter: lastUser' to the SKILL.md frontmatter.\",\n }),\n );\n }\n }\n}\n\ninterface BuildSkillArgs {\n readonly metadata: SkillMetadata;\n readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;\n readonly body: string;\n readonly source: SkillSource;\n readonly trustPolicy: ResolvedSkillTrustPolicy;\n readonly basePath?: string;\n readonly signature?: SkillSignatureVerificationResult;\n readonly tools?: ReadonlyArray<InlineSkillTool>;\n readonly bodyLoader: () => Promise<string>;\n readonly resourceLoader: (signal?: AbortSignal) => Promise<ReadonlyArray<SkillResource>>;\n}\n\nfunction buildSkill(args: BuildSkillArgs): Skill {\n let bodyCache: string | null = null;\n let resourcesCache: ReadonlyArray<SkillResource> | null = null;\n let bodyPromise: Promise<string> | null = null;\n let resourcesPromise: Promise<ReadonlyArray<SkillResource>> | null = null;\n const toolDeclarations = parseToolsField(args.metadata.raw['graphorin-tools']) ?? [];\n const tools = Object.freeze([...(args.tools ?? [])]);\n return Object.freeze({\n metadata: args.metadata,\n source: args.source,\n ...(args.basePath === undefined ? {} : { basePath: args.basePath }),\n trustPolicy: args.trustPolicy,\n ...(args.signature === undefined ? {} : { signature: args.signature }),\n async body(_signal?: AbortSignal): Promise<string> {\n if (bodyCache !== null) return bodyCache;\n if (bodyPromise === null) {\n bodyPromise = args.bodyLoader().then((value) => {\n bodyCache = value;\n return value;\n });\n }\n return bodyPromise;\n },\n async resources(signal?: AbortSignal): Promise<ReadonlyArray<SkillResource>> {\n if (resourcesCache !== null) return resourcesCache;\n if (resourcesPromise === null) {\n resourcesPromise = args.resourceLoader(signal).then((value) => {\n resourcesCache = value;\n return value;\n });\n }\n return resourcesPromise;\n },\n tools(): ReadonlyArray<InlineSkillTool> {\n return tools;\n },\n toolDeclarations(): ReadonlyArray<SkillToolDeclaration> {\n return toolDeclarations;\n },\n diagnostics(): ReadonlyArray<FrontmatterDiagnostic> {\n return args.diagnostics;\n },\n } satisfies Skill);\n}\n\nfunction describeSource(source: SkillSource): string {\n switch (source.kind) {\n case 'folder':\n return `folder:${source.path}`;\n case 'npm-package':\n return source.version === undefined\n ? `npm:${source.packageName}`\n : `npm:${source.packageName}@${source.version}`;\n case 'git-repo':\n return source.ref === undefined ? `git:${source.url}` : `git:${source.url}@${source.ref}`;\n case 'inline':\n return 'inline';\n default: {\n const exhaustive: never = source;\n void exhaustive;\n return 'unknown';\n }\n }\n}\n\nfunction extractTrustLevel(source: SkillSource): SkillsTrustLevel | undefined {\n if (source.kind === 'npm-package' || source.kind === 'git-repo' || source.kind === 'folder')\n return source.trustLevel;\n return undefined;\n}\n\n/**\n * Cap a folder skill's self-declared trust level. A directory on disk —\n * possibly downloaded from the internet — cannot self-promote to a trusted\n * tier without an operator override (RP-9): `trusted` /\n * `trusted-with-scripts` collapse to `'unknown'` (sandbox forced, signature\n * optional, outputs taint-marked), while `untrusted` / `unknown` pass\n * through unchanged.\n */\nfunction capSelfDeclaredTrust(level: SkillsTrustLevel): SkillsTrustLevel {\n return level === 'trusted' || level === 'trusted-with-scripts' ? 'unknown' : level;\n}\n\n/**\n * Translate the loader's four-valued trust level into the three-\n * valued trust level the supply-chain installer expects.\n *\n * The 'unknown' value collapses to 'untrusted' so the supply-chain\n * resolver picks the strict signature + `--ignore-scripts` policy.\n * The loader keeps the original 'unknown' on the skill's metadata\n * record so the runtime sandbox tier resolver continues to apply\n * the correct policy.\n */\nfunction coerceForSupplyChain(\n level: SkillsTrustLevel,\n): 'trusted' | 'trusted-with-scripts' | 'untrusted' {\n if (level === 'unknown') return 'untrusted';\n return level;\n}\n\nfunction guessMediaType(path: string): string | undefined {\n const ext = extname(path).toLowerCase();\n return DEFAULT_MEDIA_TYPES[ext];\n}\n\n/**\n * Required handoff-filter declaration helper. Returns the typed\n * declaration the loader parsed from frontmatter; throws\n * {@link InputFilterRequiredError} when the skill is untrusted and the\n * field is missing. Used by the agent runtime in Phase 12 right\n * before instantiating an untrusted skill's sub-agent.\n *\n * @stable\n */\nexport function requireHandoffInputFilter(metadata: SkillMetadata): HandoffInputFilterDeclaration {\n if (metadata.graphorinHandoffInputFilter !== undefined) {\n if (\n metadata.graphorinTrustLevel === 'untrusted' &&\n metadata.graphorinHandoffInputFilter.kind === 'full'\n ) {\n // ADR-040: `full` is rejected for untrusted skills regardless\n // of declaration. Fall back to the bounded default.\n return Object.freeze({ kind: 'lastUser' });\n }\n return metadata.graphorinHandoffInputFilter;\n }\n if (metadata.graphorinTrustLevel === 'untrusted') {\n throw new InputFilterRequiredError(metadata.name);\n }\n // `'unknown'` skills require an explicit declaration too — default-\n // deny posture per Phase 08. Trusted skills inherit the framework's\n // bounded `lastN(10)` default.\n if (metadata.graphorinTrustLevel === 'unknown') {\n throw new InputFilterRequiredError(metadata.name);\n }\n return Object.freeze({ kind: 'lastN', n: 10 });\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["DEFAULT_MEDIA_TYPES: Readonly<Record<string, string>>","installArgs: Parameters<typeof installSkillFromNpm>[0]","installArgs: Parameters<typeof installSkillFromGit>[0]","resources: SkillResource[]","stats: Stats","skillMd: string","effectiveTrust: SkillsTrustLevel","metadata: SkillMetadata","signature: SkillSignatureVerificationResult | undefined","out: SkillResource[]","validateOptions: Parameters<typeof validateFrontmatter>[1]","diagnostics: FrontmatterDiagnostic[]","trustLevel: SkillsTrustLevel","result: Mutable<SkillMetadata>","bodyCache: string | null","resourcesCache: ReadonlyArray<SkillResource> | null","bodyPromise: Promise<string> | null","resourcesPromise: Promise<ReadonlyArray<SkillResource>> | null"],"sources":["../../src/loader/index.ts"],"sourcesContent":["/**\n * Skill loader.\n *\n * Implements three-tier progressive disclosure:\n *\n * - **Tier 1** (always): {@link Skill.metadata} - parsed at load\n * time from the SKILL.md frontmatter.\n * - **Tier 2** (on activation): {@link Skill.body} - the loader\n * reads the markdown body lazily; subsequent calls return the\n * cached value.\n * - **Tier 3** (on demand): {@link Skill.resources} - the loader\n * walks the skill directory lazily; resource bytes are only read\n * when {@link SkillResource.read} is invoked.\n *\n * The loader supports four sources:\n *\n * - `{ kind: 'folder', path }` - read SKILL.md from disk.\n * - `{ kind: 'npm-package', ... }` - install via the supply-chain\n * helper from `@graphorin/security/supply-chain`, then read.\n * - `{ kind: 'git-repo', ... }` - shallow-clone via the\n * supply-chain helper, then read.\n * - `{ kind: 'inline', skill: ... }` - caller supplies the parsed\n * payload; the loader only validates the frontmatter. Useful for\n * tests and bundled defaults.\n *\n * @packageDocumentation\n */\n\nimport type { Stats } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join, relative, resolve, sep } from 'node:path';\n\nimport {\n installSkillFromGit,\n installSkillFromNpm,\n type ResolvedSkillTrustPolicy,\n resolveTrustPolicy,\n type SkillSignatureVerificationResult,\n type SupplyChainPolicy,\n verifySkillSignature,\n} from '@graphorin/security/supply-chain';\n\nimport {\n InputFilterRequiredError,\n SkillFrontmatterConflictError,\n SkillLoadError,\n SkillRequiredFieldMissingError,\n SkillRuntimeCompatError,\n} from '../errors/index.js';\nimport {\n parseAllowedToolsValue,\n parseFrontmatterYaml,\n parseHandoffInputFilter,\n parseToolsField,\n splitSkillMd,\n type ValidatedFrontmatter,\n validateFrontmatter,\n} from '../frontmatter/index.js';\nimport type {\n FrontmatterDiagnostic,\n FrontmatterValidatorPolicy,\n HandoffInputFilterDeclaration,\n InlineSkillTool,\n Skill,\n SkillMetadata,\n SkillResource,\n SkillSource,\n SkillsTrustLevel,\n SkillToolDeclaration,\n UnknownFieldPolicy,\n} from '../types/index.js';\n\n/** Options forwarded to {@link loadSkillFromSource}. */\nexport interface LoadSkillOptions {\n readonly conflictPolicy?: FrontmatterValidatorPolicy;\n readonly unknownFieldPolicy?: UnknownFieldPolicy;\n readonly runtimeVersion?: string;\n readonly supplyChainPolicy?: SupplyChainPolicy;\n readonly signal?: AbortSignal;\n /** Override the bundled MIME-type guesser for resource files. */\n readonly mediaTypeFor?: (path: string) => string | undefined;\n}\n\n/** Aggregate options accepted by {@link loadSkills}. */\nexport interface LoadSkillsOptions extends LoadSkillOptions {\n /**\n * Fail fast if any source produces a {@link SkillLoadError}. When\n * `false` (default) the loader logs the source path on the\n * diagnostic and continues with the next source.\n */\n readonly throwOnSourceError?: boolean;\n}\n\nconst SKILL_MANIFEST_FILENAME = 'SKILL.md';\n\nconst DEFAULT_MEDIA_TYPES: Readonly<Record<string, string>> = Object.freeze({\n '.md': 'text/markdown; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n '.json': 'application/json',\n '.yaml': 'application/yaml',\n '.yml': 'application/yaml',\n '.ts': 'text/typescript; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.cjs': 'text/javascript; charset=utf-8',\n '.py': 'text/x-python; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.pdf': 'application/pdf',\n});\n\n/**\n * Load a single skill from any supported source. The loader runs the\n * full frontmatter validator and resolves the supply-chain trust\n * policy so the returned {@link Skill} is ready to be inserted into a\n * `SkillRegistry`.\n *\n * @stable\n */\nexport async function loadSkillFromSource(\n source: SkillSource,\n options: LoadSkillOptions = {},\n): Promise<Skill> {\n switch (source.kind) {\n case 'folder':\n return loadFromFolder(source.path, source, options);\n case 'inline':\n return loadFromInline(source, options);\n case 'npm-package': {\n const installArgs: Parameters<typeof installSkillFromNpm>[0] = {\n packageName: source.packageName,\n };\n if (source.version !== undefined)\n (installArgs as Mutable<typeof installArgs>).version = source.version;\n if (source.trustLevel !== undefined)\n (installArgs as Mutable<typeof installArgs>).trustLevel = source.trustLevel;\n if (options.supplyChainPolicy !== undefined)\n (installArgs as Mutable<typeof installArgs>).policy = options.supplyChainPolicy;\n if (options.signal !== undefined)\n (installArgs as Mutable<typeof installArgs>).signal = options.signal;\n const status = await installSkillFromNpm(installArgs);\n const installPath = status.installPath;\n if (installPath === undefined) {\n throw new SkillLoadError(\n source.packageName,\n 'npm install completed without an install path; cannot continue.',\n );\n }\n const resolved = await locateSkillRoot(installPath, source.packageName);\n return loadFromFolder(resolved, source, options, status.signature);\n }\n case 'git-repo': {\n const installArgs: Parameters<typeof installSkillFromGit>[0] = {\n repoUrl: source.url,\n };\n if (source.ref !== undefined) (installArgs as Mutable<typeof installArgs>).ref = source.ref;\n if (source.trustLevel !== undefined)\n (installArgs as Mutable<typeof installArgs>).trustLevel = source.trustLevel;\n if (options.supplyChainPolicy !== undefined)\n (installArgs as Mutable<typeof installArgs>).policy = options.supplyChainPolicy;\n if (options.signal !== undefined)\n (installArgs as Mutable<typeof installArgs>).signal = options.signal;\n const status = await installSkillFromGit(installArgs);\n const installPath = status.installPath;\n if (installPath === undefined) {\n throw new SkillLoadError(\n source.url,\n 'git clone completed without a clone path; cannot continue.',\n );\n }\n const resolved = await locateSkillRoot(installPath, source.url);\n return loadFromFolder(resolved, source, options, status.signature);\n }\n default: {\n const exhaustive: never = source;\n void exhaustive;\n throw new SkillLoadError('<unknown>', 'unsupported skill source.');\n }\n }\n}\n\n/**\n * Load multiple skills concurrently. The sources are loaded in parallel and\n * the returned array preserves input order. When `throwOnSourceError === false`\n * (default) a failing source is logged and skipped; otherwise the first\n * rejection propagates out unchanged.\n *\n * @stable\n */\nexport async function loadSkills(\n sources: ReadonlyArray<SkillSource>,\n options: LoadSkillsOptions = {},\n): Promise<ReadonlyArray<Skill>> {\n const { throwOnSourceError = false, ...inner } = options;\n const loaded = await Promise.all(\n sources.map(async (source): Promise<Skill | null> => {\n try {\n return await loadSkillFromSource(source, inner);\n } catch (err) {\n if (throwOnSourceError) throw err;\n // We cannot create a Skill from a failed source, but we want to\n // preserve a structured diagnostic so callers can audit which\n // sources failed without re-running the loader.\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Failed to load source ${describeSource(source)}: ${(err as Error).message}`,\n );\n return null;\n }\n }),\n );\n return Object.freeze(loaded.filter((skill): skill is Skill => skill !== null));\n}\n\nasync function loadFromInline(\n source: Extract<SkillSource, { kind: 'inline' }>,\n options: LoadSkillOptions,\n): Promise<Skill> {\n const trustPolicy = resolveTrustPolicy(\n { kind: 'folder', path: source.skill.basePath ?? '<inline>' },\n 'trusted',\n );\n const { metadata, diagnostics, body } = parseAndValidate(source.skill.skillMd, options);\n const resourceList = source.skill.resources ?? [];\n const resources: SkillResource[] = resourceList.map((entry) => {\n const relativePath = entry.path;\n const path =\n source.skill.basePath !== undefined ? join(source.skill.basePath, entry.path) : entry.path;\n const mediaType = options.mediaTypeFor?.(entry.path) ?? guessMediaType(entry.path);\n return Object.freeze({\n path,\n relativePath,\n ...(mediaType === undefined ? {} : { mediaType }),\n async read() {\n return new TextEncoder().encode(entry.content);\n },\n async readText() {\n return entry.content;\n },\n } satisfies SkillResource);\n });\n return buildSkill({\n metadata,\n diagnostics,\n body,\n source,\n trustPolicy,\n ...(source.skill.tools === undefined ? {} : { tools: source.skill.tools }),\n bodyLoader: () => Promise.resolve(body),\n resourceLoader: () => Promise.resolve(resources),\n });\n}\n\nasync function loadFromFolder(\n folderPath: string,\n source: SkillSource,\n options: LoadSkillOptions,\n precomputedSignature?: SkillSignatureVerificationResult | undefined,\n): Promise<Skill> {\n const absolutePath = resolve(folderPath);\n let stats: Stats;\n try {\n stats = await stat(absolutePath);\n } catch (err) {\n throw new SkillLoadError(\n describeSource(source),\n `Could not stat skill directory '${absolutePath}'.`,\n { cause: err },\n );\n }\n if (!stats.isDirectory()) {\n throw new SkillLoadError(\n describeSource(source),\n `Skill source must be a directory; '${absolutePath}' is not.`,\n );\n }\n const manifestPath = join(absolutePath, SKILL_MANIFEST_FILENAME);\n let skillMd: string;\n try {\n skillMd = await readFile(manifestPath, 'utf8');\n } catch (err) {\n throw new SkillLoadError(describeSource(source), `SKILL.md is missing at '${manifestPath}'.`, {\n hint: 'Add a SKILL.md file with a YAML frontmatter block.',\n cause: err,\n });\n }\n const parsed = parseAndValidate(skillMd, options);\n const { diagnostics, body } = parsed;\n // RP-9: trust is granted by the integrator, never the artifact. An operator\n // override on the source wins; absent one, the artifact's self-declared\n // 'trusted'/'trusted-with-scripts' is capped at 'unknown' so a downloaded\n // skill cannot promote itself out of the sandbox + taint-marking. The cap\n // applies to EVERY source kind (mcp-skills-01): npm/git sources previously\n // took the SKILL.md's self-declared level verbatim, and - because the\n // signature trust root allows an inline key in the same SKILL.md - a\n // malicious package could inline its own key, self-sign, declare\n // 'trusted', and load unsandboxed with no operator involvement. The\n // resolved level is written back onto the metadata so every downstream\n // consumer (tool stamping, sandbox tier, inbound sanitization) sees it.\n const operatorTrust = extractTrustLevel(source);\n const effectiveTrust: SkillsTrustLevel =\n operatorTrust ?? capSelfDeclaredTrust(parsed.metadata.graphorinTrustLevel);\n const metadata: SkillMetadata =\n effectiveTrust === parsed.metadata.graphorinTrustLevel\n ? parsed.metadata\n : (Object.freeze({\n ...parsed.metadata,\n graphorinTrustLevel: effectiveTrust,\n }) as SkillMetadata);\n\n let signature: SkillSignatureVerificationResult | undefined = precomputedSignature;\n if (signature === undefined && metadata.graphorinSignaturePresent) {\n try {\n signature = await verifySkillSignature({\n skillMd,\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n });\n } catch (err) {\n // Surface the signature failure as a diagnostic - the supply-\n // chain installer is the one that decides whether to refuse the\n // install based on the resolved trust policy. The folder loader\n // tolerates an unverifiable signature so operators can iterate\n // on local skills before signing.\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Signature verification of '${metadata.name}' failed: ${(err as Error).message}`,\n );\n }\n }\n\n const trustPolicy = resolveTrustPolicy(\n source.kind === 'folder'\n ? { kind: 'folder', path: absolutePath }\n : source.kind === 'npm-package'\n ? source.version === undefined\n ? { kind: 'npm-package', packageName: source.packageName }\n : { kind: 'npm-package', packageName: source.packageName, version: source.version }\n : source.kind === 'git-repo'\n ? source.ref === undefined\n ? { kind: 'git-repo', url: source.url }\n : { kind: 'git-repo', url: source.url, ref: source.ref }\n : { kind: 'folder', path: absolutePath },\n coerceForSupplyChain(metadata.graphorinTrustLevel),\n );\n\n return buildSkill({\n metadata,\n diagnostics,\n body,\n source,\n trustPolicy,\n basePath: absolutePath,\n ...(signature === undefined ? {} : { signature }),\n bodyLoader: () => Promise.resolve(body),\n resourceLoader: async (signal) => listResources(absolutePath, options, signal),\n });\n}\n\nasync function listResources(\n rootPath: string,\n options: LoadSkillOptions,\n signal?: AbortSignal,\n): Promise<ReadonlyArray<SkillResource>> {\n const out: SkillResource[] = [];\n for await (const file of walk(rootPath, signal)) {\n const relativePath = relative(rootPath, file);\n if (relativePath === SKILL_MANIFEST_FILENAME) continue;\n const mediaType = options.mediaTypeFor?.(relativePath) ?? guessMediaType(relativePath);\n out.push(\n Object.freeze({\n path: file,\n relativePath: relativePath.split(sep).join('/'),\n ...(mediaType === undefined ? {} : { mediaType }),\n async read(innerSignal?: AbortSignal): Promise<Uint8Array> {\n const buffer = await readFile(file, {\n ...(innerSignal === undefined ? {} : { signal: innerSignal }),\n });\n return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n },\n async readText(innerSignal?: AbortSignal): Promise<string> {\n return readFile(file, {\n encoding: 'utf8',\n ...(innerSignal === undefined ? {} : { signal: innerSignal }),\n });\n },\n } satisfies SkillResource),\n );\n }\n return Object.freeze(out);\n}\n\nasync function* walk(root: string, signal?: AbortSignal): AsyncIterable<string> {\n const entries = await readdir(root, { withFileTypes: true });\n for (const entry of entries) {\n if (signal?.aborted === true) return;\n const full = join(root, entry.name);\n if (entry.isDirectory()) {\n yield* walk(full, signal);\n continue;\n }\n if (entry.isFile()) {\n yield full;\n }\n }\n}\n\nasync function locateSkillRoot(installPath: string, sourceLabel: string): Promise<string> {\n const direct = join(installPath, SKILL_MANIFEST_FILENAME);\n try {\n await stat(direct);\n return installPath;\n } catch {\n // continue\n }\n // RP-10: npm packages land under `node_modules/<packageName>`. The label is\n // the package name for npm sources; for git it is a URL and this probe\n // simply misses, falling through to the one-level-deep scan below.\n const nodeModulesRoot = join(installPath, 'node_modules', sourceLabel);\n try {\n await stat(join(nodeModulesRoot, SKILL_MANIFEST_FILENAME));\n return nodeModulesRoot;\n } catch {\n // continue\n }\n const entries = await readdir(installPath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const child = join(installPath, entry.name, SKILL_MANIFEST_FILENAME);\n try {\n await stat(child);\n return join(installPath, entry.name);\n } catch {\n // continue\n }\n }\n }\n throw new SkillLoadError(\n sourceLabel,\n `Could not locate SKILL.md inside '${installPath}'. Expected at the top-level or one folder deep.`,\n );\n}\n\ninterface ParsedAndValidated {\n readonly metadata: SkillMetadata;\n readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;\n readonly body: string;\n}\n\nfunction parseAndValidate(skillMd: string, options: LoadSkillOptions): ParsedAndValidated {\n const split = splitSkillMd(skillMd);\n const frontmatter = parseFrontmatterYaml(split.frontmatter);\n const validateOptions: Parameters<typeof validateFrontmatter>[1] = {};\n if (options.conflictPolicy !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).conflictPolicy = options.conflictPolicy;\n if (options.unknownFieldPolicy !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).unknownFieldPolicy =\n options.unknownFieldPolicy;\n if (options.runtimeVersion !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).runtimeVersion = options.runtimeVersion;\n const validated = validateFrontmatter(frontmatter, validateOptions);\n for (const diag of validated.diagnostics) {\n if (diag.severity === 'error' && diag.kind === 'missing-required-field') {\n throw new SkillRequiredFieldMissingError(diag.field, {\n ...(diag.hint === undefined ? {} : { hint: diag.hint }),\n });\n }\n }\n const metadata = buildMetadata(validated, frontmatter);\n const diagnostics: FrontmatterDiagnostic[] = [...validated.diagnostics];\n appendUntrustedHandoffDiagnostic(metadata, diagnostics, options.conflictPolicy);\n appendInvalidFieldTypeDiagnostics(frontmatter, validated, diagnostics);\n enforceErrorPolicy(metadata, validated, diagnostics, options);\n return Object.freeze({\n metadata,\n diagnostics: Object.freeze(diagnostics),\n body: split.body,\n });\n}\n\n/**\n * RP-11(d): a frontmatter field that is present but unparseable must not be\n * silently dropped (`?? []`). Surface an `invalid-field-type` diagnostic so\n * callers can audit the rejected value, as the parser contracts mandate.\n */\nfunction appendInvalidFieldTypeDiagnostics(\n raw: Record<string, unknown>,\n validated: ValidatedFrontmatter,\n diagnostics: FrontmatterDiagnostic[],\n): void {\n const handoffRaw = validated.resolved.handoffInputFilter.value;\n if (handoffRaw !== undefined && parseHandoffInputFilter(handoffRaw) === null) {\n diagnostics.push(\n Object.freeze({\n kind: 'invalid-field-type',\n field: 'handoff-input-filter',\n severity: 'warn',\n message: \"'handoff-input-filter' has an unsupported shape; the declaration was ignored.\",\n hint: \"Use 'lastUser', 'lastN: <n>', 'none', or 'full'.\",\n }),\n );\n }\n const toolsRaw = raw['graphorin-tools'];\n if (toolsRaw !== undefined && parseToolsField(toolsRaw) === null) {\n diagnostics.push(\n Object.freeze({\n kind: 'invalid-field-type',\n field: 'graphorin-tools',\n severity: 'warn',\n message: \"'graphorin-tools' must be a list of tool declarations; the value was ignored.\",\n hint: 'Provide a YAML list, e.g. `graphorin-tools:` with `- name: read_file` entries.',\n }),\n );\n }\n}\n\n/**\n * RP-11(b): under `conflictPolicy: 'error'`, an error-severity diagnostic\n * fails the load through the matching typed exception instead of being\n * silently surfaced. Default (`'warn'`) keeps these as diagnostics.\n */\nfunction enforceErrorPolicy(\n metadata: SkillMetadata,\n validated: ValidatedFrontmatter,\n diagnostics: ReadonlyArray<FrontmatterDiagnostic>,\n options: LoadSkillOptions,\n): void {\n if (options.conflictPolicy !== 'error') return;\n for (const diag of diagnostics) {\n if (diag.severity !== 'error') continue;\n if (diag.kind === 'invalid-runtime-compat') {\n const declared = validated.resolved.runtimeCompat.value;\n throw new SkillRuntimeCompatError(\n metadata.name,\n typeof declared === 'string' ? declared : String(declared),\n options.runtimeVersion ?? '<unknown>',\n diag.hint === undefined ? undefined : { hint: diag.hint },\n );\n }\n if (diag.kind === 'conflict') {\n throw new SkillFrontmatterConflictError(\n metadata.name,\n diag.field,\n [diag.field],\n diag.hint === undefined ? undefined : { hint: diag.hint },\n );\n }\n }\n}\n\nfunction buildMetadata(\n validated: ValidatedFrontmatter,\n raw: Record<string, unknown>,\n): SkillMetadata {\n const name = String(validated.resolved.name.value ?? '').trim();\n const description = String(validated.resolved.description.value ?? '').trim();\n const allowedToolsRaw = validated.resolved.allowedTools.value;\n const allowedTools =\n allowedToolsRaw === undefined ? undefined : parseAllowedToolsValue(allowedToolsRaw);\n const handoffFilter =\n validated.resolved.handoffInputFilter.value === undefined\n ? undefined\n : parseHandoffInputFilter(validated.resolved.handoffInputFilter.value);\n const trustLevelRaw = validated.resolved.trustLevel.value;\n // Phase 08 § \"Frontmatter validator\" recognises four explicit trust\n // levels: 'trusted' | 'trusted-with-scripts' | 'unknown' |\n // 'untrusted'. A skill that did not declare the field is treated as\n // 'unknown' (sandbox forced; signature optional) - that is the\n // default-deny posture per DEC-148 risk mitigation.\n const trustLevel: SkillsTrustLevel = (() => {\n if (\n trustLevelRaw === 'trusted' ||\n trustLevelRaw === 'trusted-with-scripts' ||\n trustLevelRaw === 'untrusted' ||\n trustLevelRaw === 'unknown'\n ) {\n return trustLevelRaw;\n }\n return 'unknown';\n })();\n const sandbox = validated.resolved.sandbox.value;\n const sensitivity = validated.resolved.sensitivity.value;\n const sensitivityDefaultsRaw = validated.resolved.sensitivityDefaults.value;\n const sensitivityDefaults =\n sensitivityDefaultsRaw !== null && typeof sensitivityDefaultsRaw === 'object'\n ? Object.freeze({ ...(sensitivityDefaultsRaw as Record<string, string>) })\n : undefined;\n const metadataObj = validated.resolved.metadata.value;\n const metadataValue =\n metadataObj !== null && typeof metadataObj === 'object'\n ? Object.freeze({ ...(metadataObj as Record<string, unknown>) })\n : undefined;\n const result: Mutable<SkillMetadata> = {\n name,\n description,\n disableModelInvocation: validated.resolved.disableModelInvocation.value === true,\n graphorinTrustLevel: trustLevel,\n graphorinSignaturePresent: 'graphorin-signature' in raw,\n raw: Object.freeze({ ...raw }),\n };\n if (typeof validated.resolved.license.value === 'string')\n result.license = validated.resolved.license.value;\n if (typeof validated.resolved.compatibility.value === 'string')\n result.compatibility = validated.resolved.compatibility.value;\n if (metadataValue !== undefined) result.metadata = metadataValue;\n if (allowedTools !== undefined && allowedTools !== null)\n result.allowedTools = Object.freeze([...allowedTools]);\n if (typeof validated.resolved.runtimeCompat.value === 'string')\n result.graphorinRuntimeCompat = validated.resolved.runtimeCompat.value;\n if (typeof sensitivity === 'string') result.graphorinSensitivity = sensitivity;\n if (sensitivityDefaults !== undefined) result.graphorinSensitivityDefaults = sensitivityDefaults;\n if (sandbox !== null && typeof sandbox === 'object')\n result.graphorinSandbox = Object.freeze({ ...(sandbox as Record<string, unknown>) });\n if (handoffFilter !== undefined && handoffFilter !== null)\n result.graphorinHandoffInputFilter = handoffFilter;\n if (typeof validated.resolved.anthropicSpec.value === 'string')\n result.graphorinAnthropicSpec = validated.resolved.anthropicSpec.value;\n if (typeof validated.resolved.version.value === 'string')\n result.graphorinVersion = validated.resolved.version.value;\n return Object.freeze(result) as SkillMetadata;\n}\n\nfunction appendUntrustedHandoffDiagnostic(\n metadata: SkillMetadata,\n diagnostics: FrontmatterDiagnostic[],\n conflictPolicy: FrontmatterValidatorPolicy = 'warn',\n): void {\n // Untrusted skills that declared `filter: full` are an explicit\n // attempt to exfiltrate the entire conversation through a sub-agent\n // - Phase 08 / ADR-040 mandate that we WARN and ignore. The agent\n // runtime in Phase 12 will refuse the filter regardless of this\n // diagnostic.\n if (metadata.graphorinTrustLevel === 'untrusted') {\n if (metadata.graphorinHandoffInputFilter?.kind === 'full') {\n diagnostics.push(\n Object.freeze({\n kind: 'untrusted-handoff-filter-required',\n field: 'graphorin-handoff-input-filter',\n severity: 'warn',\n message: `Untrusted skill '${metadata.name}' declared 'graphorin-handoff-input-filter: full'. The full-message filter is rejected for untrusted skills; the runtime will fall back to 'lastUser'.`,\n hint: \"Replace 'full' with 'lastUser' (or another bounded filter) to silence this diagnostic.\",\n }),\n );\n } else if (metadata.graphorinHandoffInputFilter === undefined) {\n diagnostics.push(\n Object.freeze({\n kind: 'untrusted-handoff-filter-required',\n field: 'graphorin-handoff-input-filter',\n severity: conflictPolicy === 'error' ? 'error' : 'warn',\n message: `Untrusted skill '${metadata.name}' did not declare 'graphorin-handoff-input-filter'. The runtime will throw if the skill invokes Agent.toTool()/handoff().`,\n hint: \"Add 'graphorin-handoff-input-filter: lastUser' to the SKILL.md frontmatter.\",\n }),\n );\n }\n }\n}\n\ninterface BuildSkillArgs {\n readonly metadata: SkillMetadata;\n readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;\n readonly body: string;\n readonly source: SkillSource;\n readonly trustPolicy: ResolvedSkillTrustPolicy;\n readonly basePath?: string;\n readonly signature?: SkillSignatureVerificationResult;\n readonly tools?: ReadonlyArray<InlineSkillTool>;\n readonly bodyLoader: () => Promise<string>;\n readonly resourceLoader: (signal?: AbortSignal) => Promise<ReadonlyArray<SkillResource>>;\n}\n\nfunction buildSkill(args: BuildSkillArgs): Skill {\n let bodyCache: string | null = null;\n let resourcesCache: ReadonlyArray<SkillResource> | null = null;\n let bodyPromise: Promise<string> | null = null;\n let resourcesPromise: Promise<ReadonlyArray<SkillResource>> | null = null;\n const toolDeclarations = parseToolsField(args.metadata.raw['graphorin-tools']) ?? [];\n const tools = Object.freeze([...(args.tools ?? [])]);\n return Object.freeze({\n metadata: args.metadata,\n source: args.source,\n ...(args.basePath === undefined ? {} : { basePath: args.basePath }),\n trustPolicy: args.trustPolicy,\n ...(args.signature === undefined ? {} : { signature: args.signature }),\n async body(_signal?: AbortSignal): Promise<string> {\n if (bodyCache !== null) return bodyCache;\n if (bodyPromise === null) {\n bodyPromise = args.bodyLoader().then((value) => {\n bodyCache = value;\n return value;\n });\n }\n return bodyPromise;\n },\n async resources(signal?: AbortSignal): Promise<ReadonlyArray<SkillResource>> {\n if (resourcesCache !== null) return resourcesCache;\n if (resourcesPromise === null) {\n resourcesPromise = args.resourceLoader(signal).then((value) => {\n resourcesCache = value;\n return value;\n });\n }\n return resourcesPromise;\n },\n tools(): ReadonlyArray<InlineSkillTool> {\n return tools;\n },\n toolDeclarations(): ReadonlyArray<SkillToolDeclaration> {\n return toolDeclarations;\n },\n diagnostics(): ReadonlyArray<FrontmatterDiagnostic> {\n return args.diagnostics;\n },\n } satisfies Skill);\n}\n\nfunction describeSource(source: SkillSource): string {\n switch (source.kind) {\n case 'folder':\n return `folder:${source.path}`;\n case 'npm-package':\n return source.version === undefined\n ? `npm:${source.packageName}`\n : `npm:${source.packageName}@${source.version}`;\n case 'git-repo':\n return source.ref === undefined ? `git:${source.url}` : `git:${source.url}@${source.ref}`;\n case 'inline':\n return 'inline';\n default: {\n const exhaustive: never = source;\n void exhaustive;\n return 'unknown';\n }\n }\n}\n\nfunction extractTrustLevel(source: SkillSource): SkillsTrustLevel | undefined {\n if (source.kind === 'npm-package' || source.kind === 'git-repo' || source.kind === 'folder')\n return source.trustLevel;\n return undefined;\n}\n\n/**\n * Cap a folder skill's self-declared trust level. A directory on disk -\n * possibly downloaded from the internet - cannot self-promote to a trusted\n * tier without an operator override (RP-9): `trusted` /\n * `trusted-with-scripts` collapse to `'unknown'` (sandbox forced, signature\n * optional, outputs taint-marked), while `untrusted` / `unknown` pass\n * through unchanged.\n */\nfunction capSelfDeclaredTrust(level: SkillsTrustLevel): SkillsTrustLevel {\n return level === 'trusted' || level === 'trusted-with-scripts' ? 'unknown' : level;\n}\n\n/**\n * Translate the loader's four-valued trust level into the three-\n * valued trust level the supply-chain installer expects.\n *\n * The 'unknown' value collapses to 'untrusted' so the supply-chain\n * resolver picks the strict signature + `--ignore-scripts` policy.\n * The loader keeps the original 'unknown' on the skill's metadata\n * record so the runtime sandbox tier resolver continues to apply\n * the correct policy.\n */\nfunction coerceForSupplyChain(\n level: SkillsTrustLevel,\n): 'trusted' | 'trusted-with-scripts' | 'untrusted' {\n if (level === 'unknown') return 'untrusted';\n return level;\n}\n\nfunction guessMediaType(path: string): string | undefined {\n const ext = extname(path).toLowerCase();\n return DEFAULT_MEDIA_TYPES[ext];\n}\n\n/**\n * Required handoff-filter declaration helper. Returns the typed\n * declaration the loader parsed from frontmatter; throws\n * {@link InputFilterRequiredError} when the skill is untrusted and the\n * field is missing. Used by the agent runtime in Phase 12 right\n * before instantiating an untrusted skill's sub-agent.\n *\n * @stable\n */\nexport function requireHandoffInputFilter(metadata: SkillMetadata): HandoffInputFilterDeclaration {\n if (metadata.graphorinHandoffInputFilter !== undefined) {\n if (\n metadata.graphorinTrustLevel === 'untrusted' &&\n metadata.graphorinHandoffInputFilter.kind === 'full'\n ) {\n // ADR-040: `full` is rejected for untrusted skills regardless\n // of declaration. Fall back to the bounded default.\n return Object.freeze({ kind: 'lastUser' });\n }\n return metadata.graphorinHandoffInputFilter;\n }\n if (metadata.graphorinTrustLevel === 'untrusted') {\n throw new InputFilterRequiredError(metadata.name);\n }\n // `'unknown'` skills require an explicit declaration too - default-\n // deny posture per Phase 08. Trusted skills inherit the framework's\n // bounded `lastN(10)` default.\n if (metadata.graphorinTrustLevel === 'unknown') {\n throw new InputFilterRequiredError(metadata.name);\n }\n return Object.freeze({ kind: 'lastN', n: 10 });\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
//#region src/migration/index.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* `migrate-frontmatter`
|
|
3
|
+
* `migrate-frontmatter` - idempotent rewrite helper that migrates
|
|
4
4
|
* legacy `graphorin-*` frontmatter fields onto their upstream
|
|
5
5
|
* equivalents per the `deprecate-graphorin-prefix` mappings recorded
|
|
6
6
|
* in the bundled spec snapshot.
|
|
7
7
|
*
|
|
8
|
-
* The function is dry-run by default
|
|
8
|
+
* The function is dry-run by default - callers must opt in to
|
|
9
9
|
* persisting the rewritten bytes. The CLI binary in Phase 15 wraps
|
|
10
10
|
* this surface; the library is exposed here so other tooling can
|
|
11
11
|
* reuse it.
|
package/dist/migration/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import { parse, stringify } from "yaml";
|
|
|
5
5
|
|
|
6
6
|
//#region src/migration/index.ts
|
|
7
7
|
/**
|
|
8
|
-
* `migrate-frontmatter`
|
|
8
|
+
* `migrate-frontmatter` - idempotent rewrite helper that migrates
|
|
9
9
|
* legacy `graphorin-*` frontmatter fields onto their upstream
|
|
10
10
|
* equivalents per the `deprecate-graphorin-prefix` mappings recorded
|
|
11
11
|
* in the bundled spec snapshot.
|
|
12
12
|
*
|
|
13
|
-
* The function is dry-run by default
|
|
13
|
+
* The function is dry-run by default - callers must opt in to
|
|
14
14
|
* persisting the rewritten bytes. The CLI binary in Phase 15 wraps
|
|
15
15
|
* this surface; the library is exposed here so other tooling can
|
|
16
16
|
* reuse it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["frontmatter: unknown","parseYaml","rewrites: MigrationRewrite[]","stringifyYaml","out: Record<string, unknown>"],"sources":["../../src/migration/index.ts"],"sourcesContent":["/**\n * `migrate-frontmatter`
|
|
1
|
+
{"version":3,"file":"index.js","names":["frontmatter: unknown","parseYaml","rewrites: MigrationRewrite[]","stringifyYaml","out: Record<string, unknown>"],"sources":["../../src/migration/index.ts"],"sourcesContent":["/**\n * `migrate-frontmatter` - idempotent rewrite helper that migrates\n * legacy `graphorin-*` frontmatter fields onto their upstream\n * equivalents per the `deprecate-graphorin-prefix` mappings recorded\n * in the bundled spec snapshot.\n *\n * The function is dry-run by default - callers must opt in to\n * persisting the rewritten bytes. The CLI binary in Phase 15 wraps\n * this surface; the library is exposed here so other tooling can\n * reuse it.\n *\n * @packageDocumentation\n */\n\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport { SkillManifestParseError } from '../errors/index.js';\nimport { splitSkillMd } from '../frontmatter/index.js';\nimport { getSpecSnapshot } from '../spec/index.js';\n\n/** A single rewrite the migrator applied (or would apply in a dry-run). */\nexport interface MigrationRewrite {\n readonly fromField: string;\n readonly toField: string;\n readonly reason: 'deprecate-graphorin-prefix' | 'co-exist-noop' | 'graphorin-only-noop';\n readonly applied: boolean;\n}\n\n/** Result of a single SKILL.md migration. */\nexport interface MigrationResult {\n readonly skillId: string;\n readonly rewrites: ReadonlyArray<MigrationRewrite>;\n readonly originalSkillMd: string;\n readonly migratedSkillMd: string;\n readonly changed: boolean;\n}\n\n/** Options accepted by {@link migrateFrontmatter}. */\nexport interface MigrateFrontmatterOptions {\n /** Identifier used in audit / error messages. Defaults to `'<inline>'`. */\n readonly skillId?: string;\n /**\n * When `true`, rewrites are applied to the returned `migratedSkillMd`.\n * When `false` (default), `migratedSkillMd === originalSkillMd` and\n * the function operates as a dry-run report.\n */\n readonly apply?: boolean;\n}\n\n/**\n * Migrate the bundled `deprecate-graphorin-prefix` mappings on a\n * single SKILL.md. The function is idempotent: re-running it on an\n * already-migrated SKILL.md returns `changed: false` and an empty\n * `rewrites` array.\n *\n * @stable\n */\nexport function migrateFrontmatter(\n skillMd: string,\n options: MigrateFrontmatterOptions = {},\n): MigrationResult {\n const skillId = options.skillId ?? '<inline>';\n const apply = options.apply ?? false;\n const split = splitSkillMd(skillMd);\n let frontmatter: unknown;\n try {\n frontmatter = parseYaml(split.frontmatter);\n } catch (err) {\n throw new SkillManifestParseError(`Skill '${skillId}' frontmatter is not valid YAML.`, {\n cause: err,\n });\n }\n if (frontmatter === null || typeof frontmatter !== 'object' || Array.isArray(frontmatter)) {\n throw new SkillManifestParseError(`Skill '${skillId}' frontmatter must be a top-level object.`);\n }\n const fm = { ...(frontmatter as Record<string, unknown>) };\n\n const snapshot = getSpecSnapshot();\n const rewrites: MigrationRewrite[] = [];\n let mutated = false;\n\n for (const [graphorinField, mapping] of Object.entries(snapshot.graphorinMapping)) {\n if (!(graphorinField in fm)) continue;\n if (mapping.policy !== 'deprecate-graphorin-prefix') {\n rewrites.push(\n Object.freeze({\n fromField: graphorinField,\n toField: graphorinField,\n reason: mapping.policy === 'co-exist' ? 'co-exist-noop' : 'graphorin-only-noop',\n applied: false,\n }),\n );\n continue;\n }\n if (mapping.anthropicEquivalent === null) {\n rewrites.push(\n Object.freeze({\n fromField: graphorinField,\n toField: graphorinField,\n reason: 'graphorin-only-noop',\n applied: false,\n }),\n );\n continue;\n }\n if (mapping.anthropicEquivalent in fm) {\n // Both fields are already set - the validator will surface the\n // diagnostic at load time. The migrator does not silently drop\n // either side; the operator is expected to remove the redundant\n // `graphorin-*` field manually after reviewing the diagnostic.\n rewrites.push(\n Object.freeze({\n fromField: graphorinField,\n toField: mapping.anthropicEquivalent,\n reason: 'deprecate-graphorin-prefix',\n applied: false,\n }),\n );\n continue;\n }\n if (apply) {\n fm[mapping.anthropicEquivalent] = fm[graphorinField];\n delete fm[graphorinField];\n mutated = true;\n }\n rewrites.push(\n Object.freeze({\n fromField: graphorinField,\n toField: mapping.anthropicEquivalent,\n reason: 'deprecate-graphorin-prefix',\n applied: apply,\n }),\n );\n }\n\n if (!apply || !mutated) {\n return Object.freeze({\n skillId,\n rewrites: Object.freeze(rewrites),\n originalSkillMd: skillMd,\n migratedSkillMd: skillMd,\n changed: false,\n });\n }\n\n const sortedFm = sortKeysAnthropicFirst(fm);\n const migratedFrontmatter = stringifyYaml(sortedFm, {\n sortMapEntries: false,\n lineWidth: 0,\n }).trimEnd();\n const migratedSkillMd = `---\\n${migratedFrontmatter}\\n---\\n${split.body}`;\n\n return Object.freeze({\n skillId,\n rewrites: Object.freeze(rewrites),\n originalSkillMd: skillMd,\n migratedSkillMd,\n changed: true,\n });\n}\n\n/**\n * Stable key ordering: Anthropic-base fields first (in their snapshot\n * insertion order), then the `metadata` bucket, then the\n * `graphorin-*` fields, then anything else. The migrator emits in\n * this order so re-running the migrator on the same input yields\n * identical bytes (idempotence).\n *\n * @stable\n */\nexport function sortKeysAnthropicFirst(\n frontmatter: Record<string, unknown>,\n): Record<string, unknown> {\n const snapshot = getSpecSnapshot();\n const baseKeys = Object.keys(snapshot.knownFields);\n const out: Record<string, unknown> = {};\n for (const key of baseKeys) {\n if (key in frontmatter) out[key] = frontmatter[key];\n }\n if ('metadata' in frontmatter) out.metadata = frontmatter.metadata;\n const remaining = Object.keys(frontmatter)\n .filter((k) => !(k in out))\n .sort((a, b) => {\n const aGraphorin = a.startsWith('graphorin-');\n const bGraphorin = b.startsWith('graphorin-');\n if (aGraphorin && !bGraphorin) return 1;\n if (!aGraphorin && bGraphorin) return -1;\n return a < b ? -1 : a > b ? 1 : 0;\n });\n for (const key of remaining) out[key] = frontmatter[key];\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,mBACd,SACA,UAAqC,EAAE,EACtB;CACjB,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,QAAQ,aAAa,QAAQ;CACnC,IAAIA;AACJ,KAAI;AACF,gBAAcC,MAAU,MAAM,YAAY;UACnC,KAAK;AACZ,QAAM,IAAI,wBAAwB,UAAU,QAAQ,mCAAmC,EACrF,OAAO,KACR,CAAC;;AAEJ,KAAI,gBAAgB,QAAQ,OAAO,gBAAgB,YAAY,MAAM,QAAQ,YAAY,CACvF,OAAM,IAAI,wBAAwB,UAAU,QAAQ,2CAA2C;CAEjG,MAAM,KAAK,EAAE,GAAI,aAAyC;CAE1D,MAAM,WAAW,iBAAiB;CAClC,MAAMC,WAA+B,EAAE;CACvC,IAAI,UAAU;AAEd,MAAK,MAAM,CAAC,gBAAgB,YAAY,OAAO,QAAQ,SAAS,iBAAiB,EAAE;AACjF,MAAI,EAAE,kBAAkB,IAAK;AAC7B,MAAI,QAAQ,WAAW,8BAA8B;AACnD,YAAS,KACP,OAAO,OAAO;IACZ,WAAW;IACX,SAAS;IACT,QAAQ,QAAQ,WAAW,aAAa,kBAAkB;IAC1D,SAAS;IACV,CAAC,CACH;AACD;;AAEF,MAAI,QAAQ,wBAAwB,MAAM;AACxC,YAAS,KACP,OAAO,OAAO;IACZ,WAAW;IACX,SAAS;IACT,QAAQ;IACR,SAAS;IACV,CAAC,CACH;AACD;;AAEF,MAAI,QAAQ,uBAAuB,IAAI;AAKrC,YAAS,KACP,OAAO,OAAO;IACZ,WAAW;IACX,SAAS,QAAQ;IACjB,QAAQ;IACR,SAAS;IACV,CAAC,CACH;AACD;;AAEF,MAAI,OAAO;AACT,MAAG,QAAQ,uBAAuB,GAAG;AACrC,UAAO,GAAG;AACV,aAAU;;AAEZ,WAAS,KACP,OAAO,OAAO;GACZ,WAAW;GACX,SAAS,QAAQ;GACjB,QAAQ;GACR,SAAS;GACV,CAAC,CACH;;AAGH,KAAI,CAAC,SAAS,CAAC,QACb,QAAO,OAAO,OAAO;EACnB;EACA,UAAU,OAAO,OAAO,SAAS;EACjC,iBAAiB;EACjB,iBAAiB;EACjB,SAAS;EACV,CAAC;CAQJ,MAAM,kBAAkB,QAJIC,UADX,uBAAuB,GAAG,EACS;EAClD,gBAAgB;EAChB,WAAW;EACZ,CAAC,CAAC,SAAS,CACwC,SAAS,MAAM;AAEnE,QAAO,OAAO,OAAO;EACnB;EACA,UAAU,OAAO,OAAO,SAAS;EACjC,iBAAiB;EACjB;EACA,SAAS;EACV,CAAC;;;;;;;;;;;AAYJ,SAAgB,uBACd,aACyB;CACzB,MAAM,WAAW,iBAAiB;CAClC,MAAM,WAAW,OAAO,KAAK,SAAS,YAAY;CAClD,MAAMC,MAA+B,EAAE;AACvC,MAAK,MAAM,OAAO,SAChB,KAAI,OAAO,YAAa,KAAI,OAAO,YAAY;AAEjD,KAAI,cAAc,YAAa,KAAI,WAAW,YAAY;CAC1D,MAAM,YAAY,OAAO,KAAK,YAAY,CACvC,QAAQ,MAAM,EAAE,KAAK,KAAK,CAC1B,MAAM,GAAG,MAAM;EACd,MAAM,aAAa,EAAE,WAAW,aAAa;EAC7C,MAAM,aAAa,EAAE,WAAW,aAAa;AAC7C,MAAI,cAAc,CAAC,WAAY,QAAO;AACtC,MAAI,CAAC,cAAc,WAAY,QAAO;AACtC,SAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;GAChC;AACJ,MAAK,MAAM,OAAO,UAAW,KAAI,OAAO,YAAY;AACpD,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","names":["sandboxResolverArgs: Parameters<typeof resolveSandbox>[0]","stamped: Mutable<Tool<TInput, TOutput, TDeps>>","source: ToolSource"],"sources":["../../src/registry/bridge.ts"],"sourcesContent":["/**\n * Helper functions that bridge skill-bundled `Tool` records into the\n * `@graphorin/tools` registry.\n *\n * The skills loader does not import the runtime registry directly
|
|
1
|
+
{"version":3,"file":"bridge.js","names":["sandboxResolverArgs: Parameters<typeof resolveSandbox>[0]","stamped: Mutable<Tool<TInput, TOutput, TDeps>>","source: ToolSource"],"sources":["../../src/registry/bridge.ts"],"sourcesContent":["/**\n * Helper functions that bridge skill-bundled `Tool` records into the\n * `@graphorin/tools` registry.\n *\n * The skills loader does not import the runtime registry directly -\n * downstream callers (the agent runtime in Phase 12, the test suite,\n * and any user-supplied bootstrap script) materialise the actual\n * `Tool[]` from the skill module surface and feed each one through\n * {@link stampSkillTool} to get a {@link Tool} + a {@link ToolSource}\n * pair the registry can register with the correct trust-class\n * derivation, sandbox tier propagation, and inbound-sanitization\n * defaults.\n *\n * @packageDocumentation\n */\n\nimport type { InboundSanitizationPolicy, SandboxPolicy, Tool, ToolSource } from '@graphorin/core';\nimport { resolveSandbox, type SandboxTrustLevel } from '@graphorin/security/sandbox';\n\nimport type { Skill, SkillMetadata } from '../types/index.js';\n\n/** Result of {@link stampSkillTool}. */\nexport interface StampedSkillTool<TInput = unknown, TOutput = unknown, TDeps = unknown> {\n readonly tool: Tool<TInput, TOutput, TDeps>;\n readonly source: ToolSource;\n /** Resolved sandbox policy after the tier resolver ran. */\n readonly resolvedSandbox: ReturnType<typeof resolveSandbox>;\n /** `true` when the resolver overrode the operator's choice. */\n readonly sandboxForced: boolean;\n /** `true` when the inbound sanitization policy was upgraded to the untrusted default. */\n readonly inboundSanitizationForced: boolean;\n}\n\n/**\n * Stamp a skill-bundled tool with the metadata the\n * `@graphorin/tools` registry expects:\n *\n * 1. Derive a `ToolSource` of kind `'skill'` carrying the skill's\n * name and trust level.\n * 2. Run `resolveSandbox(...)` so the resulting `Tool.sandboxPolicy`\n * matches the mandatory tier for untrusted skills (DEC-148).\n * 3. Default the `inboundSanitization` policy to\n * `'detect-and-strip-and-wrap'` for untrusted skills when the tool\n * author left it unset; trusted skills inherit the operator's\n * choice.\n *\n * The function returns a freshly-frozen `Tool` with the resolved\n * `sandboxPolicy` and `inboundSanitization` baked in so downstream\n * registries cannot accidentally re-inherit a relaxed policy.\n *\n * @stable\n */\nexport function stampSkillTool<TInput = unknown, TOutput = unknown, TDeps = unknown>(\n tool: Tool<TInput, TOutput, TDeps>,\n skill: Pick<Skill, 'metadata'>,\n): StampedSkillTool<TInput, TOutput, TDeps> {\n return stampSkillToolFromMetadata(tool, skill.metadata);\n}\n\n/**\n * Lower-level variant accepting a raw {@link SkillMetadata} so\n * fixtures and tests do not have to materialise a full {@link Skill}.\n *\n * @stable\n */\nexport function stampSkillToolFromMetadata<TInput = unknown, TOutput = unknown, TDeps = unknown>(\n tool: Tool<TInput, TOutput, TDeps>,\n metadata: SkillMetadata,\n): StampedSkillTool<TInput, TOutput, TDeps> {\n const trustLevel = mapTrustLevel(metadata.graphorinTrustLevel);\n const sandboxResolverArgs: Parameters<typeof resolveSandbox>[0] = {\n trustLevel,\n };\n if (tool.name !== undefined)\n (sandboxResolverArgs as Mutable<typeof sandboxResolverArgs>).toolName = tool.name;\n if (metadata.name !== undefined)\n (sandboxResolverArgs as Mutable<typeof sandboxResolverArgs>).skillName = metadata.name;\n const operatorOverride = sandboxOverrideFromTool(tool);\n if (operatorOverride !== undefined)\n (sandboxResolverArgs as Mutable<typeof sandboxResolverArgs>).override = operatorOverride;\n const resolvedSandbox = resolveSandbox(sandboxResolverArgs);\n const inboundSanitization = inboundSanitizationFor(tool, metadata.graphorinTrustLevel);\n const stamped: Mutable<Tool<TInput, TOutput, TDeps>> = {\n ...tool,\n sandboxPolicy: mapSandboxKindToPolicy(resolvedSandbox.kind),\n ...(inboundSanitization === undefined ? {} : { inboundSanitization }),\n };\n const source: ToolSource = Object.freeze({\n kind: 'skill' as const,\n skillName: metadata.name,\n // 'unknown' inherits the strict sandbox policy of 'untrusted', so\n // the agent runtime registers the source as untrusted too - that\n // forces the inbound-sanitization default\n // ('detect-and-strip-and-wrap') and the precedence ladder used by\n // collision resolution to demote it relative to first-party\n // / trusted-skill registrations.\n trustLevel:\n metadata.graphorinTrustLevel === 'untrusted' || metadata.graphorinTrustLevel === 'unknown'\n ? 'untrusted'\n : 'trusted',\n });\n return Object.freeze({\n tool: Object.freeze(stamped) as Tool<TInput, TOutput, TDeps>,\n source,\n resolvedSandbox,\n sandboxForced: resolvedSandbox.forced,\n inboundSanitizationForced:\n inboundSanitization !== undefined && tool.inboundSanitization === undefined,\n });\n}\n\nfunction mapTrustLevel(level: SkillMetadata['graphorinTrustLevel']): SandboxTrustLevel {\n switch (level) {\n case 'untrusted':\n case 'unknown':\n // Phase 08 § Risks & mitigations: 'unknown' enforces sandbox\n // without requiring signature, so the sandbox tier resolver\n // applies the same strict policy as 'untrusted'.\n return 'untrusted';\n case 'trusted':\n case 'trusted-with-scripts':\n return 'trusted';\n default: {\n const exhaustive: never = level;\n void exhaustive;\n return 'user-defined';\n }\n }\n}\n\nfunction mapSandboxKindToPolicy(kind: ReturnType<typeof resolveSandbox>['kind']): SandboxPolicy {\n if (kind === 'none') return 'none';\n if (kind === 'worker-threads') return 'sandboxed';\n if (kind === 'isolated-vm') return 'isolated';\n if (kind === 'docker') return 'docker';\n return 'sandboxed';\n}\n\nfunction inboundSanitizationFor(\n tool: Pick<Tool, 'inboundSanitization'>,\n trustLevel: SkillMetadata['graphorinTrustLevel'],\n): InboundSanitizationPolicy | undefined {\n if (tool.inboundSanitization !== undefined) return tool.inboundSanitization;\n if (trustLevel === 'untrusted' || trustLevel === 'unknown') {\n return 'detect-and-strip-and-wrap';\n }\n return undefined;\n}\n\nfunction sandboxOverrideFromTool(\n tool: Pick<Tool, 'sandboxPolicy'>,\n): { readonly kind?: ReturnType<typeof resolveSandbox>['kind'] } | undefined {\n switch (tool.sandboxPolicy) {\n case undefined:\n return undefined;\n case 'none':\n return { kind: 'none' };\n case 'sandboxed':\n return { kind: 'worker-threads' };\n case 'isolated':\n return { kind: 'isolated-vm' };\n case 'docker':\n return { kind: 'docker' };\n default:\n return undefined;\n }\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,eACd,MACA,OAC0C;AAC1C,QAAO,2BAA2B,MAAM,MAAM,SAAS;;;;;;;;AASzD,SAAgB,2BACd,MACA,UAC0C;CAE1C,MAAMA,sBAA4D,EAChE,YAFiB,cAAc,SAAS,oBAAoB,EAG7D;AACD,KAAI,KAAK,SAAS,OAChB,CAAC,oBAA4D,WAAW,KAAK;AAC/E,KAAI,SAAS,SAAS,OACpB,CAAC,oBAA4D,YAAY,SAAS;CACpF,MAAM,mBAAmB,wBAAwB,KAAK;AACtD,KAAI,qBAAqB,OACvB,CAAC,oBAA4D,WAAW;CAC1E,MAAM,kBAAkB,eAAe,oBAAoB;CAC3D,MAAM,sBAAsB,uBAAuB,MAAM,SAAS,oBAAoB;CACtF,MAAMC,UAAiD;EACrD,GAAG;EACH,eAAe,uBAAuB,gBAAgB,KAAK;EAC3D,GAAI,wBAAwB,SAAY,EAAE,GAAG,EAAE,qBAAqB;EACrE;CACD,MAAMC,SAAqB,OAAO,OAAO;EACvC,MAAM;EACN,WAAW,SAAS;EAOpB,YACE,SAAS,wBAAwB,eAAe,SAAS,wBAAwB,YAC7E,cACA;EACP,CAAC;AACF,QAAO,OAAO,OAAO;EACnB,MAAM,OAAO,OAAO,QAAQ;EAC5B;EACA;EACA,eAAe,gBAAgB;EAC/B,2BACE,wBAAwB,UAAa,KAAK,wBAAwB;EACrE,CAAC;;AAGJ,SAAS,cAAc,OAAgE;AACrF,SAAQ,OAAR;EACE,KAAK;EACL,KAAK,UAIH,QAAO;EACT,KAAK;EACL,KAAK,uBACH,QAAO;EACT,QAGE,QAAO;;;AAKb,SAAS,uBAAuB,MAAgE;AAC9F,KAAI,SAAS,OAAQ,QAAO;AAC5B,KAAI,SAAS,iBAAkB,QAAO;AACtC,KAAI,SAAS,cAAe,QAAO;AACnC,KAAI,SAAS,SAAU,QAAO;AAC9B,QAAO;;AAGT,SAAS,uBACP,MACA,YACuC;AACvC,KAAI,KAAK,wBAAwB,OAAW,QAAO,KAAK;AACxD,KAAI,eAAe,eAAe,eAAe,UAC/C,QAAO;;AAKX,SAAS,wBACP,MAC2E;AAC3E,SAAQ,KAAK,eAAb;EACE,KAAK,OACH;EACF,KAAK,OACH,QAAO,EAAE,MAAM,QAAQ;EACzB,KAAK,YACH,QAAO,EAAE,MAAM,kBAAkB;EACnC,KAAK,WACH,QAAO,EAAE,MAAM,eAAe;EAChC,KAAK,SACH,QAAO,EAAE,MAAM,UAAU;EAC3B,QACE"}
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface SkillRegistryOptions {
|
|
|
26
26
|
/**
|
|
27
27
|
* Optional stamping function (RP-11). When supplied, `activate()` runs each
|
|
28
28
|
* skill's pre-built `Tool[]` through it and surfaces the results on
|
|
29
|
-
* {@link ActivatedSkill.tools}. Without it, `activate()` surfaces no tools
|
|
29
|
+
* {@link ActivatedSkill.tools}. Without it, `activate()` surfaces no tools -
|
|
30
30
|
* the agent runtime resolves and stamps them itself.
|
|
31
31
|
*/
|
|
32
32
|
readonly stampTool?: SkillToolStamper;
|
|
@@ -37,7 +37,7 @@ interface SkillRegistry {
|
|
|
37
37
|
/**
|
|
38
38
|
* Upsert a skill by name (RP-11). Unlike {@link SkillRegistry.register},
|
|
39
39
|
* `replace` overwrites an existing registration instead of throwing on a
|
|
40
|
-
* name collision
|
|
40
|
+
* name collision - the upgrade path for hot-reloading a re-loaded skill.
|
|
41
41
|
*/
|
|
42
42
|
replace(skill: Skill): void;
|
|
43
43
|
unregister(name: string): boolean;
|
|
@@ -61,7 +61,7 @@ interface SkillRegistry {
|
|
|
61
61
|
* Resolve a single trigger (model-emitted skill name OR the raw
|
|
62
62
|
* `/skill:<name>` slash-command body) into an {@link ActivationRequest}.
|
|
63
63
|
* Returns `null` when no skill matches and the trigger looked like a
|
|
64
|
-
* slash command
|
|
64
|
+
* slash command - callers that want a strict mode should call
|
|
65
65
|
* {@link parseActivationTrigger} themselves.
|
|
66
66
|
*/
|
|
67
67
|
resolveTrigger(trigger: string): ActivationRequest | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["lines: string[]","cues: string[]","matches: Skill[]","out: InlineSkillTool[]","request: Mutable<ActivationRequest>","out: ActivatedSkill[]","resources: ReadonlyArray<SkillResource>","stampedTools: ReadonlyArray<ResolvedTool>","out: RegisteredToolDeclaration[]"],"sources":["../../src/registry/index.ts"],"sourcesContent":["/**\n * `SkillRegistry` — registry over loaded skills.\n *\n * The registry is the surface the agent runtime (Phase 12) and the\n * standalone server (Phase 14) consume. It exposes:\n *\n * - `getMetadata()` — every skill's Tier-1 metadata, used by the\n * ContextEngine to assemble the system prompt's skill metadata\n * block (Phase 10d).\n * - `activate(triggers)` / `getActivationRequest(triggers)` —\n * match a list of trigger strings (slash commands and / or model-\n * emitted skill names) and return the corresponding\n * {@link ActivatedSkill} records.\n * - `getSkill(name)` — direct lookup.\n * - `tools()` — flat list of declared tool entries; the runtime\n * resolves the actual `Tool[]` through the `@graphorin/tools`\n * registry.\n *\n * @packageDocumentation\n */\n\nimport type { ResolvedTool } from '@graphorin/core';\nimport { parseSlashCommand } from '../activation/index.js';\nimport { SkillNameCollisionError, SlashCommandParseError } from '../errors/index.js';\n\nexport type { StampedSkillTool } from './bridge.js';\nexport { stampSkillTool, stampSkillToolFromMetadata } from './bridge.js';\n\nimport type {\n ActivatedSkill,\n InlineSkillTool,\n Skill,\n SkillMetadata,\n SkillResource,\n SkillToolDeclaration,\n} from '../types/index.js';\n\n/**\n * Stamping seam injected by the agent runtime (Phase 12). It turns a skill's\n * pre-built `Tool` into a fully resolved `ResolvedTool` (trust class + sandbox\n * tier + source). The skills package keeps no hard dependency on\n * `@graphorin/tools`; when no stamper is configured, `activate()` surfaces no\n * tools (the runtime resolves them itself).\n */\nexport type SkillToolStamper = (tool: InlineSkillTool, metadata: SkillMetadata) => ResolvedTool;\n\n/** Options accepted by {@link createSkillRegistry}. */\nexport interface SkillRegistryOptions {\n /**\n * Default activation behaviour. When `'metadata-only'` (default),\n * `activate(...)` returns the parsed activation request without\n * invoking `Skill.body()`; callers (the agent runtime) then invoke\n * the body resolver themselves so the runtime can attach a span.\n * When `'eager'`, the registry resolves the body before returning,\n * suitable for tests.\n */\n readonly activationStrategy?: 'metadata-only' | 'eager';\n /**\n * Optional stamping function (RP-11). When supplied, `activate()` runs each\n * skill's pre-built `Tool[]` through it and surfaces the results on\n * {@link ActivatedSkill.tools}. Without it, `activate()` surfaces no tools —\n * the agent runtime resolves and stamps them itself.\n */\n readonly stampTool?: SkillToolStamper;\n}\n\n/** Public registry surface. */\nexport interface SkillRegistry {\n register(skill: Skill): void;\n /**\n * Upsert a skill by name (RP-11). Unlike {@link SkillRegistry.register},\n * `replace` overwrites an existing registration instead of throwing on a\n * name collision — the upgrade path for hot-reloading a re-loaded skill.\n */\n replace(skill: Skill): void;\n unregister(name: string): boolean;\n getSkill(name: string): Skill | undefined;\n has(name: string): boolean;\n list(): ReadonlyArray<Skill>;\n getMetadata(): ReadonlyArray<SkillMetadata>;\n /**\n * Skills surfaced into the system prompt for auto-activation.\n * Skills with `disable-model-invocation: true` are excluded.\n */\n getAutoActivationMetadata(): ReadonlyArray<SkillMetadata>;\n /**\n * Render the auto-activation metadata as a string suitable for the\n * system prompt. The format is bytes-stable and consumed verbatim\n * by the ContextEngine layered template (Phase 10d). Skills with\n * `disable-model-invocation: true` are excluded.\n */\n getMetadataBlock(): string;\n /**\n * Resolve a single trigger (model-emitted skill name OR the raw\n * `/skill:<name>` slash-command body) into an {@link ActivationRequest}.\n * Returns `null` when no skill matches and the trigger looked like a\n * slash command — callers that want a strict mode should call\n * {@link parseActivationTrigger} themselves.\n */\n resolveTrigger(trigger: string): ActivationRequest | null;\n activate(\n triggers: ReadonlyArray<string>,\n signal?: AbortSignal,\n ): Promise<ReadonlyArray<ActivatedSkill>>;\n /**\n * Best-effort match: returns every skill whose name OR description\n * contains all of the supplied trigger tokens (case-insensitive).\n * The agent runtime uses this when the model emits a trigger phrase\n * that does not directly map to a skill name.\n */\n search(triggers: ReadonlyArray<string>): ReadonlyArray<Skill>;\n /**\n * Flat, deduplicated list of every pre-built tool shipped by the\n * registered skills. The first registration wins on a `tool.name`\n * collision; later collisions surface a one-time WARN through the\n * console so operators can resolve the conflict (Phase 12 will\n * route these through the audit emitter).\n */\n tools(): ReadonlyArray<InlineSkillTool>;\n toolDeclarations(): ReadonlyArray<RegisteredToolDeclaration>;\n size(): number;\n clear(): void;\n}\n\n/** Activation request produced by {@link SkillRegistry.resolveTrigger}. */\nexport interface ActivationRequest {\n readonly skill: Skill;\n readonly activationKind: ActivatedSkill['activationKind'];\n readonly args?: string;\n}\n\n/**\n * Tool-declaration record exposed by {@link SkillRegistry.toolDeclarations}.\n * Adds the owning skill's name and trust level so downstream\n * registrations into `@graphorin/tools` can stamp the source.\n *\n * @stable\n */\nexport interface RegisteredToolDeclaration extends SkillToolDeclaration {\n readonly skillName: string;\n readonly trustLevel: SkillMetadata['graphorinTrustLevel'];\n}\n\n/**\n * Build a fresh, empty registry. Multiple registries can co-exist\n * within a single process; the framework defaults to a single shared\n * instance per agent instance.\n *\n * @stable\n */\nexport function createSkillRegistry(options: SkillRegistryOptions = {}): SkillRegistry {\n const skillsByName = new Map<string, Skill>();\n const strategy = options.activationStrategy ?? 'metadata-only';\n\n function register(skill: Skill): void {\n if (skillsByName.has(skill.metadata.name)) {\n throw new SkillNameCollisionError(skill.metadata.name);\n }\n skillsByName.set(skill.metadata.name, skill);\n }\n\n function replace(skill: Skill): void {\n skillsByName.set(skill.metadata.name, skill);\n }\n\n function unregister(name: string): boolean {\n return skillsByName.delete(name);\n }\n\n function getSkill(name: string): Skill | undefined {\n return skillsByName.get(name);\n }\n\n function has(name: string): boolean {\n return skillsByName.has(name);\n }\n\n function list(): ReadonlyArray<Skill> {\n return Object.freeze([...skillsByName.values()]);\n }\n\n function getMetadata(): ReadonlyArray<SkillMetadata> {\n return Object.freeze([...skillsByName.values()].map((skill) => skill.metadata));\n }\n\n function getAutoActivationMetadata(): ReadonlyArray<SkillMetadata> {\n return Object.freeze(\n [...skillsByName.values()]\n .map((skill) => skill.metadata)\n .filter((metadata) => !metadata.disableModelInvocation),\n );\n }\n\n function getMetadataBlock(): string {\n const lines: string[] = [];\n const auto = getAutoActivationMetadata();\n if (auto.length === 0) return '';\n lines.push('# Available skills');\n lines.push('');\n for (const meta of auto) {\n lines.push(`## ${meta.name}`);\n lines.push('');\n const description = meta.description.replace(/\\s+/gu, ' ').trim();\n if (description.length > 0) lines.push(description);\n const cues: string[] = [];\n if (meta.allowedTools !== undefined && meta.allowedTools.length > 0) {\n cues.push(`Allowed tools: ${[...meta.allowedTools].join(', ')}`);\n }\n if (meta.graphorinSensitivity !== undefined) {\n cues.push(`Sensitivity: ${meta.graphorinSensitivity}`);\n }\n if (cues.length > 0) {\n lines.push('');\n for (const cue of cues) lines.push(`- ${cue}`);\n }\n lines.push('');\n }\n return lines.join('\\n').replace(/\\n+$/u, '\\n');\n }\n\n function search(triggers: ReadonlyArray<string>): ReadonlyArray<Skill> {\n const tokens = [...triggers]\n .flatMap((trigger) => trigger.toLowerCase().split(/\\s+/u))\n .filter((tok) => tok.length > 0);\n if (tokens.length === 0) return Object.freeze([]);\n const matches: Skill[] = [];\n const seen = new Set<string>();\n for (const skill of skillsByName.values()) {\n const haystack = `${skill.metadata.name}\\n${skill.metadata.description}`.toLowerCase();\n const isMatch = tokens.every((tok) => haystack.includes(tok));\n if (isMatch && !seen.has(skill.metadata.name)) {\n seen.add(skill.metadata.name);\n matches.push(skill);\n }\n }\n return Object.freeze(matches);\n }\n\n function tools(): ReadonlyArray<InlineSkillTool> {\n const seen = new Set<string>();\n const out: InlineSkillTool[] = [];\n const collisions = new Set<string>();\n for (const skill of skillsByName.values()) {\n for (const tool of skill.tools()) {\n if (seen.has(tool.name)) {\n if (!collisions.has(tool.name)) {\n collisions.add(tool.name);\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Duplicate tool name '${tool.name}' across skills; first registration wins.`,\n );\n }\n continue;\n }\n seen.add(tool.name);\n out.push(tool);\n }\n }\n return Object.freeze(out);\n }\n\n function resolveTrigger(trigger: string): ActivationRequest | null {\n const parsed = parseActivationTrigger(trigger);\n const skill = skillsByName.get(parsed.name);\n if (skill === undefined) return null;\n if (parsed.activationKind === 'auto' && skill.metadata.disableModelInvocation) {\n // Auto-activation refused — the skill opted out.\n return null;\n }\n const request: Mutable<ActivationRequest> = {\n skill,\n activationKind: parsed.activationKind,\n };\n if (parsed.args !== undefined && parsed.args.length > 0) request.args = parsed.args;\n return Object.freeze(request);\n }\n\n async function activate(\n triggers: ReadonlyArray<string>,\n signal?: AbortSignal,\n ): Promise<ReadonlyArray<ActivatedSkill>> {\n const out: ActivatedSkill[] = [];\n const seen = new Set<string>();\n for (const trigger of triggers) {\n const request = resolveTrigger(trigger);\n if (request === null) continue;\n if (seen.has(request.skill.metadata.name)) continue;\n seen.add(request.skill.metadata.name);\n const body = strategy === 'eager' ? await request.skill.body(signal) : '';\n const resources: ReadonlyArray<SkillResource> =\n strategy === 'eager' ? await request.skill.resources(signal) : Object.freeze([]);\n // Pre-built tools (inline source) are surfaced via `Skill.tools()`.\n // When the caller wired a `stampTool` function (the agent runtime does),\n // each tool is stamped into a `ResolvedTool` and surfaced here; without\n // it the registry exposes no tools (the runtime resolves them itself).\n const stampedTools: ReadonlyArray<ResolvedTool> =\n options.stampTool !== undefined\n ? Object.freeze(\n request.skill\n .tools()\n .map((tool) =>\n (options.stampTool as SkillToolStamper)(tool, request.skill.metadata),\n ),\n )\n : Object.freeze([]);\n out.push(\n Object.freeze({\n skill: request.skill,\n body,\n resources,\n tools: stampedTools,\n activationKind: request.activationKind,\n activatedAt: Date.now(),\n }),\n );\n }\n return Object.freeze(out);\n }\n\n function toolDeclarations(): ReadonlyArray<RegisteredToolDeclaration> {\n const out: RegisteredToolDeclaration[] = [];\n for (const skill of skillsByName.values()) {\n for (const decl of skill.toolDeclarations()) {\n out.push(\n Object.freeze({\n ...decl,\n skillName: skill.metadata.name,\n trustLevel: skill.metadata.graphorinTrustLevel,\n }),\n );\n }\n }\n return Object.freeze(out);\n }\n\n function size(): number {\n return skillsByName.size;\n }\n\n function clear(): void {\n skillsByName.clear();\n }\n\n return Object.freeze({\n register,\n replace,\n unregister,\n getSkill,\n has,\n list,\n getMetadata,\n getAutoActivationMetadata,\n getMetadataBlock,\n resolveTrigger,\n activate,\n search,\n tools,\n toolDeclarations,\n size,\n clear,\n } satisfies SkillRegistry);\n}\n\n/**\n * Parsed activation trigger. The registry uses this to discriminate\n * slash-command activations (which override\n * `disable-model-invocation: true`) from model-emitted auto\n * activations (which honour it).\n *\n * @stable\n */\nexport interface ParsedActivationTrigger {\n readonly name: string;\n readonly activationKind: ActivatedSkill['activationKind'];\n readonly args?: string;\n}\n\n/**\n * Parse a single activation trigger. Slash-command bodies\n * (`/skill:<name>`) are routed through the slash parser; bare names\n * are treated as auto-activation requests emitted by the model.\n *\n * Throws {@link SlashCommandParseError} when the body looks like a\n * slash command but does not match the grammar (so the caller can\n * surface the error to the user).\n *\n * @stable\n */\nexport function parseActivationTrigger(raw: string): ParsedActivationTrigger {\n const trimmed = raw.trim();\n if (trimmed.startsWith('/skill:') || /^\\s*\\/skill:/u.test(raw)) {\n const parsed = parseSlashCommand(raw);\n return Object.freeze({\n name: parsed.name,\n activationKind: 'slash-command' as const,\n ...(parsed.args.length > 0 ? { args: parsed.args } : {}),\n });\n }\n if (trimmed.length === 0) {\n throw new SlashCommandParseError(raw, 'activation trigger must be non-empty.');\n }\n if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u.test(trimmed)) {\n throw new SlashCommandParseError(\n raw,\n 'auto-activation trigger must match /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.',\n );\n }\n return Object.freeze({\n name: trimmed,\n activationKind: 'auto' as const,\n });\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n"],"mappings":";;;;;;;;;;;;AAsJA,SAAgB,oBAAoB,UAAgC,EAAE,EAAiB;CACrF,MAAM,+BAAe,IAAI,KAAoB;CAC7C,MAAM,WAAW,QAAQ,sBAAsB;CAE/C,SAAS,SAAS,OAAoB;AACpC,MAAI,aAAa,IAAI,MAAM,SAAS,KAAK,CACvC,OAAM,IAAI,wBAAwB,MAAM,SAAS,KAAK;AAExD,eAAa,IAAI,MAAM,SAAS,MAAM,MAAM;;CAG9C,SAAS,QAAQ,OAAoB;AACnC,eAAa,IAAI,MAAM,SAAS,MAAM,MAAM;;CAG9C,SAAS,WAAW,MAAuB;AACzC,SAAO,aAAa,OAAO,KAAK;;CAGlC,SAAS,SAAS,MAAiC;AACjD,SAAO,aAAa,IAAI,KAAK;;CAG/B,SAAS,IAAI,MAAuB;AAClC,SAAO,aAAa,IAAI,KAAK;;CAG/B,SAAS,OAA6B;AACpC,SAAO,OAAO,OAAO,CAAC,GAAG,aAAa,QAAQ,CAAC,CAAC;;CAGlD,SAAS,cAA4C;AACnD,SAAO,OAAO,OAAO,CAAC,GAAG,aAAa,QAAQ,CAAC,CAAC,KAAK,UAAU,MAAM,SAAS,CAAC;;CAGjF,SAAS,4BAA0D;AACjE,SAAO,OAAO,OACZ,CAAC,GAAG,aAAa,QAAQ,CAAC,CACvB,KAAK,UAAU,MAAM,SAAS,CAC9B,QAAQ,aAAa,CAAC,SAAS,uBAAuB,CAC1D;;CAGH,SAAS,mBAA2B;EAClC,MAAMA,QAAkB,EAAE;EAC1B,MAAM,OAAO,2BAA2B;AACxC,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,KAAK,qBAAqB;AAChC,QAAM,KAAK,GAAG;AACd,OAAK,MAAM,QAAQ,MAAM;AACvB,SAAM,KAAK,MAAM,KAAK,OAAO;AAC7B,SAAM,KAAK,GAAG;GACd,MAAM,cAAc,KAAK,YAAY,QAAQ,SAAS,IAAI,CAAC,MAAM;AACjE,OAAI,YAAY,SAAS,EAAG,OAAM,KAAK,YAAY;GACnD,MAAMC,OAAiB,EAAE;AACzB,OAAI,KAAK,iBAAiB,UAAa,KAAK,aAAa,SAAS,EAChE,MAAK,KAAK,kBAAkB,CAAC,GAAG,KAAK,aAAa,CAAC,KAAK,KAAK,GAAG;AAElE,OAAI,KAAK,yBAAyB,OAChC,MAAK,KAAK,gBAAgB,KAAK,uBAAuB;AAExD,OAAI,KAAK,SAAS,GAAG;AACnB,UAAM,KAAK,GAAG;AACd,SAAK,MAAM,OAAO,KAAM,OAAM,KAAK,KAAK,MAAM;;AAEhD,SAAM,KAAK,GAAG;;AAEhB,SAAO,MAAM,KAAK,KAAK,CAAC,QAAQ,SAAS,KAAK;;CAGhD,SAAS,OAAO,UAAuD;EACrE,MAAM,SAAS,CAAC,GAAG,SAAS,CACzB,SAAS,YAAY,QAAQ,aAAa,CAAC,MAAM,OAAO,CAAC,CACzD,QAAQ,QAAQ,IAAI,SAAS,EAAE;AAClC,MAAI,OAAO,WAAW,EAAG,QAAO,OAAO,OAAO,EAAE,CAAC;EACjD,MAAMC,UAAmB,EAAE;EAC3B,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,MAAM,SAAS,aAAa,QAAQ,EAAE;GACzC,MAAM,WAAW,GAAG,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,cAAc,aAAa;AAEtF,OADgB,OAAO,OAAO,QAAQ,SAAS,SAAS,IAAI,CAAC,IAC9C,CAAC,KAAK,IAAI,MAAM,SAAS,KAAK,EAAE;AAC7C,SAAK,IAAI,MAAM,SAAS,KAAK;AAC7B,YAAQ,KAAK,MAAM;;;AAGvB,SAAO,OAAO,OAAO,QAAQ;;CAG/B,SAAS,QAAwC;EAC/C,MAAM,uBAAO,IAAI,KAAa;EAC9B,MAAMC,MAAyB,EAAE;EACjC,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,SAAS,aAAa,QAAQ,CACvC,MAAK,MAAM,QAAQ,MAAM,OAAO,EAAE;AAChC,OAAI,KAAK,IAAI,KAAK,KAAK,EAAE;AACvB,QAAI,CAAC,WAAW,IAAI,KAAK,KAAK,EAAE;AAC9B,gBAAW,IAAI,KAAK,KAAK;AAEzB,aAAQ,KACN,2CAA2C,KAAK,KAAK,2CACtD;;AAEH;;AAEF,QAAK,IAAI,KAAK,KAAK;AACnB,OAAI,KAAK,KAAK;;AAGlB,SAAO,OAAO,OAAO,IAAI;;CAG3B,SAAS,eAAe,SAA2C;EACjE,MAAM,SAAS,uBAAuB,QAAQ;EAC9C,MAAM,QAAQ,aAAa,IAAI,OAAO,KAAK;AAC3C,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,mBAAmB,UAAU,MAAM,SAAS,uBAErD,QAAO;EAET,MAAMC,UAAsC;GAC1C;GACA,gBAAgB,OAAO;GACxB;AACD,MAAI,OAAO,SAAS,UAAa,OAAO,KAAK,SAAS,EAAG,SAAQ,OAAO,OAAO;AAC/E,SAAO,OAAO,OAAO,QAAQ;;CAG/B,eAAe,SACb,UACA,QACwC;EACxC,MAAMC,MAAwB,EAAE;EAChC,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,UAAU,eAAe,QAAQ;AACvC,OAAI,YAAY,KAAM;AACtB,OAAI,KAAK,IAAI,QAAQ,MAAM,SAAS,KAAK,CAAE;AAC3C,QAAK,IAAI,QAAQ,MAAM,SAAS,KAAK;GACrC,MAAM,OAAO,aAAa,UAAU,MAAM,QAAQ,MAAM,KAAK,OAAO,GAAG;GACvE,MAAMC,YACJ,aAAa,UAAU,MAAM,QAAQ,MAAM,UAAU,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC;GAKlF,MAAMC,eACJ,QAAQ,cAAc,SAClB,OAAO,OACL,QAAQ,MACL,OAAO,CACP,KAAK,SACH,QAAQ,UAA+B,MAAM,QAAQ,MAAM,SAAS,CACtE,CACJ,GACD,OAAO,OAAO,EAAE,CAAC;AACvB,OAAI,KACF,OAAO,OAAO;IACZ,OAAO,QAAQ;IACf;IACA;IACA,OAAO;IACP,gBAAgB,QAAQ;IACxB,aAAa,KAAK,KAAK;IACxB,CAAC,CACH;;AAEH,SAAO,OAAO,OAAO,IAAI;;CAG3B,SAAS,mBAA6D;EACpE,MAAMC,MAAmC,EAAE;AAC3C,OAAK,MAAM,SAAS,aAAa,QAAQ,CACvC,MAAK,MAAM,QAAQ,MAAM,kBAAkB,CACzC,KAAI,KACF,OAAO,OAAO;GACZ,GAAG;GACH,WAAW,MAAM,SAAS;GAC1B,YAAY,MAAM,SAAS;GAC5B,CAAC,CACH;AAGL,SAAO,OAAO,OAAO,IAAI;;CAG3B,SAAS,OAAe;AACtB,SAAO,aAAa;;CAGtB,SAAS,QAAc;AACrB,eAAa,OAAO;;AAGtB,QAAO,OAAO,OAAO;EACnB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAyB;;;;;;;;;;;;;AA4B5B,SAAgB,uBAAuB,KAAsC;CAC3E,MAAM,UAAU,IAAI,MAAM;AAC1B,KAAI,QAAQ,WAAW,UAAU,IAAI,gBAAgB,KAAK,IAAI,EAAE;EAC9D,MAAM,SAAS,kBAAkB,IAAI;AACrC,SAAO,OAAO,OAAO;GACnB,MAAM,OAAO;GACb,gBAAgB;GAChB,GAAI,OAAO,KAAK,SAAS,IAAI,EAAE,MAAM,OAAO,MAAM,GAAG,EAAE;GACxD,CAAC;;AAEJ,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,uBAAuB,KAAK,wCAAwC;AAEhF,KAAI,CAAC,sCAAsC,KAAK,QAAQ,CACtD,OAAM,IAAI,uBACR,KACA,2EACD;AAEH,QAAO,OAAO,OAAO;EACnB,MAAM;EACN,gBAAgB;EACjB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["lines: string[]","cues: string[]","matches: Skill[]","out: InlineSkillTool[]","request: Mutable<ActivationRequest>","out: ActivatedSkill[]","resources: ReadonlyArray<SkillResource>","stampedTools: ReadonlyArray<ResolvedTool>","out: RegisteredToolDeclaration[]"],"sources":["../../src/registry/index.ts"],"sourcesContent":["/**\n * `SkillRegistry` - registry over loaded skills.\n *\n * The registry is the surface the agent runtime (Phase 12) and the\n * standalone server (Phase 14) consume. It exposes:\n *\n * - `getMetadata()` - every skill's Tier-1 metadata, used by the\n * ContextEngine to assemble the system prompt's skill metadata\n * block (Phase 10d).\n * - `activate(triggers)` / `getActivationRequest(triggers)` -\n * match a list of trigger strings (slash commands and / or model-\n * emitted skill names) and return the corresponding\n * {@link ActivatedSkill} records.\n * - `getSkill(name)` - direct lookup.\n * - `tools()` - flat list of declared tool entries; the runtime\n * resolves the actual `Tool[]` through the `@graphorin/tools`\n * registry.\n *\n * @packageDocumentation\n */\n\nimport type { ResolvedTool } from '@graphorin/core';\nimport { parseSlashCommand } from '../activation/index.js';\nimport { SkillNameCollisionError, SlashCommandParseError } from '../errors/index.js';\n\nexport type { StampedSkillTool } from './bridge.js';\nexport { stampSkillTool, stampSkillToolFromMetadata } from './bridge.js';\n\nimport type {\n ActivatedSkill,\n InlineSkillTool,\n Skill,\n SkillMetadata,\n SkillResource,\n SkillToolDeclaration,\n} from '../types/index.js';\n\n/**\n * Stamping seam injected by the agent runtime (Phase 12). It turns a skill's\n * pre-built `Tool` into a fully resolved `ResolvedTool` (trust class + sandbox\n * tier + source). The skills package keeps no hard dependency on\n * `@graphorin/tools`; when no stamper is configured, `activate()` surfaces no\n * tools (the runtime resolves them itself).\n */\nexport type SkillToolStamper = (tool: InlineSkillTool, metadata: SkillMetadata) => ResolvedTool;\n\n/** Options accepted by {@link createSkillRegistry}. */\nexport interface SkillRegistryOptions {\n /**\n * Default activation behaviour. When `'metadata-only'` (default),\n * `activate(...)` returns the parsed activation request without\n * invoking `Skill.body()`; callers (the agent runtime) then invoke\n * the body resolver themselves so the runtime can attach a span.\n * When `'eager'`, the registry resolves the body before returning,\n * suitable for tests.\n */\n readonly activationStrategy?: 'metadata-only' | 'eager';\n /**\n * Optional stamping function (RP-11). When supplied, `activate()` runs each\n * skill's pre-built `Tool[]` through it and surfaces the results on\n * {@link ActivatedSkill.tools}. Without it, `activate()` surfaces no tools -\n * the agent runtime resolves and stamps them itself.\n */\n readonly stampTool?: SkillToolStamper;\n}\n\n/** Public registry surface. */\nexport interface SkillRegistry {\n register(skill: Skill): void;\n /**\n * Upsert a skill by name (RP-11). Unlike {@link SkillRegistry.register},\n * `replace` overwrites an existing registration instead of throwing on a\n * name collision - the upgrade path for hot-reloading a re-loaded skill.\n */\n replace(skill: Skill): void;\n unregister(name: string): boolean;\n getSkill(name: string): Skill | undefined;\n has(name: string): boolean;\n list(): ReadonlyArray<Skill>;\n getMetadata(): ReadonlyArray<SkillMetadata>;\n /**\n * Skills surfaced into the system prompt for auto-activation.\n * Skills with `disable-model-invocation: true` are excluded.\n */\n getAutoActivationMetadata(): ReadonlyArray<SkillMetadata>;\n /**\n * Render the auto-activation metadata as a string suitable for the\n * system prompt. The format is bytes-stable and consumed verbatim\n * by the ContextEngine layered template (Phase 10d). Skills with\n * `disable-model-invocation: true` are excluded.\n */\n getMetadataBlock(): string;\n /**\n * Resolve a single trigger (model-emitted skill name OR the raw\n * `/skill:<name>` slash-command body) into an {@link ActivationRequest}.\n * Returns `null` when no skill matches and the trigger looked like a\n * slash command - callers that want a strict mode should call\n * {@link parseActivationTrigger} themselves.\n */\n resolveTrigger(trigger: string): ActivationRequest | null;\n activate(\n triggers: ReadonlyArray<string>,\n signal?: AbortSignal,\n ): Promise<ReadonlyArray<ActivatedSkill>>;\n /**\n * Best-effort match: returns every skill whose name OR description\n * contains all of the supplied trigger tokens (case-insensitive).\n * The agent runtime uses this when the model emits a trigger phrase\n * that does not directly map to a skill name.\n */\n search(triggers: ReadonlyArray<string>): ReadonlyArray<Skill>;\n /**\n * Flat, deduplicated list of every pre-built tool shipped by the\n * registered skills. The first registration wins on a `tool.name`\n * collision; later collisions surface a one-time WARN through the\n * console so operators can resolve the conflict (Phase 12 will\n * route these through the audit emitter).\n */\n tools(): ReadonlyArray<InlineSkillTool>;\n toolDeclarations(): ReadonlyArray<RegisteredToolDeclaration>;\n size(): number;\n clear(): void;\n}\n\n/** Activation request produced by {@link SkillRegistry.resolveTrigger}. */\nexport interface ActivationRequest {\n readonly skill: Skill;\n readonly activationKind: ActivatedSkill['activationKind'];\n readonly args?: string;\n}\n\n/**\n * Tool-declaration record exposed by {@link SkillRegistry.toolDeclarations}.\n * Adds the owning skill's name and trust level so downstream\n * registrations into `@graphorin/tools` can stamp the source.\n *\n * @stable\n */\nexport interface RegisteredToolDeclaration extends SkillToolDeclaration {\n readonly skillName: string;\n readonly trustLevel: SkillMetadata['graphorinTrustLevel'];\n}\n\n/**\n * Build a fresh, empty registry. Multiple registries can co-exist\n * within a single process; the framework defaults to a single shared\n * instance per agent instance.\n *\n * @stable\n */\nexport function createSkillRegistry(options: SkillRegistryOptions = {}): SkillRegistry {\n const skillsByName = new Map<string, Skill>();\n const strategy = options.activationStrategy ?? 'metadata-only';\n\n function register(skill: Skill): void {\n if (skillsByName.has(skill.metadata.name)) {\n throw new SkillNameCollisionError(skill.metadata.name);\n }\n skillsByName.set(skill.metadata.name, skill);\n }\n\n function replace(skill: Skill): void {\n skillsByName.set(skill.metadata.name, skill);\n }\n\n function unregister(name: string): boolean {\n return skillsByName.delete(name);\n }\n\n function getSkill(name: string): Skill | undefined {\n return skillsByName.get(name);\n }\n\n function has(name: string): boolean {\n return skillsByName.has(name);\n }\n\n function list(): ReadonlyArray<Skill> {\n return Object.freeze([...skillsByName.values()]);\n }\n\n function getMetadata(): ReadonlyArray<SkillMetadata> {\n return Object.freeze([...skillsByName.values()].map((skill) => skill.metadata));\n }\n\n function getAutoActivationMetadata(): ReadonlyArray<SkillMetadata> {\n return Object.freeze(\n [...skillsByName.values()]\n .map((skill) => skill.metadata)\n .filter((metadata) => !metadata.disableModelInvocation),\n );\n }\n\n function getMetadataBlock(): string {\n const lines: string[] = [];\n const auto = getAutoActivationMetadata();\n if (auto.length === 0) return '';\n lines.push('# Available skills');\n lines.push('');\n for (const meta of auto) {\n lines.push(`## ${meta.name}`);\n lines.push('');\n const description = meta.description.replace(/\\s+/gu, ' ').trim();\n if (description.length > 0) lines.push(description);\n const cues: string[] = [];\n if (meta.allowedTools !== undefined && meta.allowedTools.length > 0) {\n cues.push(`Allowed tools: ${[...meta.allowedTools].join(', ')}`);\n }\n if (meta.graphorinSensitivity !== undefined) {\n cues.push(`Sensitivity: ${meta.graphorinSensitivity}`);\n }\n if (cues.length > 0) {\n lines.push('');\n for (const cue of cues) lines.push(`- ${cue}`);\n }\n lines.push('');\n }\n return lines.join('\\n').replace(/\\n+$/u, '\\n');\n }\n\n function search(triggers: ReadonlyArray<string>): ReadonlyArray<Skill> {\n const tokens = [...triggers]\n .flatMap((trigger) => trigger.toLowerCase().split(/\\s+/u))\n .filter((tok) => tok.length > 0);\n if (tokens.length === 0) return Object.freeze([]);\n const matches: Skill[] = [];\n const seen = new Set<string>();\n for (const skill of skillsByName.values()) {\n const haystack = `${skill.metadata.name}\\n${skill.metadata.description}`.toLowerCase();\n const isMatch = tokens.every((tok) => haystack.includes(tok));\n if (isMatch && !seen.has(skill.metadata.name)) {\n seen.add(skill.metadata.name);\n matches.push(skill);\n }\n }\n return Object.freeze(matches);\n }\n\n function tools(): ReadonlyArray<InlineSkillTool> {\n const seen = new Set<string>();\n const out: InlineSkillTool[] = [];\n const collisions = new Set<string>();\n for (const skill of skillsByName.values()) {\n for (const tool of skill.tools()) {\n if (seen.has(tool.name)) {\n if (!collisions.has(tool.name)) {\n collisions.add(tool.name);\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Duplicate tool name '${tool.name}' across skills; first registration wins.`,\n );\n }\n continue;\n }\n seen.add(tool.name);\n out.push(tool);\n }\n }\n return Object.freeze(out);\n }\n\n function resolveTrigger(trigger: string): ActivationRequest | null {\n const parsed = parseActivationTrigger(trigger);\n const skill = skillsByName.get(parsed.name);\n if (skill === undefined) return null;\n if (parsed.activationKind === 'auto' && skill.metadata.disableModelInvocation) {\n // Auto-activation refused - the skill opted out.\n return null;\n }\n const request: Mutable<ActivationRequest> = {\n skill,\n activationKind: parsed.activationKind,\n };\n if (parsed.args !== undefined && parsed.args.length > 0) request.args = parsed.args;\n return Object.freeze(request);\n }\n\n async function activate(\n triggers: ReadonlyArray<string>,\n signal?: AbortSignal,\n ): Promise<ReadonlyArray<ActivatedSkill>> {\n const out: ActivatedSkill[] = [];\n const seen = new Set<string>();\n for (const trigger of triggers) {\n const request = resolveTrigger(trigger);\n if (request === null) continue;\n if (seen.has(request.skill.metadata.name)) continue;\n seen.add(request.skill.metadata.name);\n const body = strategy === 'eager' ? await request.skill.body(signal) : '';\n const resources: ReadonlyArray<SkillResource> =\n strategy === 'eager' ? await request.skill.resources(signal) : Object.freeze([]);\n // Pre-built tools (inline source) are surfaced via `Skill.tools()`.\n // When the caller wired a `stampTool` function (the agent runtime does),\n // each tool is stamped into a `ResolvedTool` and surfaced here; without\n // it the registry exposes no tools (the runtime resolves them itself).\n const stampedTools: ReadonlyArray<ResolvedTool> =\n options.stampTool !== undefined\n ? Object.freeze(\n request.skill\n .tools()\n .map((tool) =>\n (options.stampTool as SkillToolStamper)(tool, request.skill.metadata),\n ),\n )\n : Object.freeze([]);\n out.push(\n Object.freeze({\n skill: request.skill,\n body,\n resources,\n tools: stampedTools,\n activationKind: request.activationKind,\n activatedAt: Date.now(),\n }),\n );\n }\n return Object.freeze(out);\n }\n\n function toolDeclarations(): ReadonlyArray<RegisteredToolDeclaration> {\n const out: RegisteredToolDeclaration[] = [];\n for (const skill of skillsByName.values()) {\n for (const decl of skill.toolDeclarations()) {\n out.push(\n Object.freeze({\n ...decl,\n skillName: skill.metadata.name,\n trustLevel: skill.metadata.graphorinTrustLevel,\n }),\n );\n }\n }\n return Object.freeze(out);\n }\n\n function size(): number {\n return skillsByName.size;\n }\n\n function clear(): void {\n skillsByName.clear();\n }\n\n return Object.freeze({\n register,\n replace,\n unregister,\n getSkill,\n has,\n list,\n getMetadata,\n getAutoActivationMetadata,\n getMetadataBlock,\n resolveTrigger,\n activate,\n search,\n tools,\n toolDeclarations,\n size,\n clear,\n } satisfies SkillRegistry);\n}\n\n/**\n * Parsed activation trigger. The registry uses this to discriminate\n * slash-command activations (which override\n * `disable-model-invocation: true`) from model-emitted auto\n * activations (which honour it).\n *\n * @stable\n */\nexport interface ParsedActivationTrigger {\n readonly name: string;\n readonly activationKind: ActivatedSkill['activationKind'];\n readonly args?: string;\n}\n\n/**\n * Parse a single activation trigger. Slash-command bodies\n * (`/skill:<name>`) are routed through the slash parser; bare names\n * are treated as auto-activation requests emitted by the model.\n *\n * Throws {@link SlashCommandParseError} when the body looks like a\n * slash command but does not match the grammar (so the caller can\n * surface the error to the user).\n *\n * @stable\n */\nexport function parseActivationTrigger(raw: string): ParsedActivationTrigger {\n const trimmed = raw.trim();\n if (trimmed.startsWith('/skill:') || /^\\s*\\/skill:/u.test(raw)) {\n const parsed = parseSlashCommand(raw);\n return Object.freeze({\n name: parsed.name,\n activationKind: 'slash-command' as const,\n ...(parsed.args.length > 0 ? { args: parsed.args } : {}),\n });\n }\n if (trimmed.length === 0) {\n throw new SlashCommandParseError(raw, 'activation trigger must be non-empty.');\n }\n if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u.test(trimmed)) {\n throw new SlashCommandParseError(\n raw,\n 'auto-activation trigger must match /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.',\n );\n }\n return Object.freeze({\n name: trimmed,\n activationKind: 'auto' as const,\n });\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n"],"mappings":"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"}
|
package/dist/spec/index.d.ts
CHANGED
|
@@ -8,12 +8,16 @@
|
|
|
8
8
|
* which `graphorin-*` extensions deprecate (or co-exist with) an
|
|
9
9
|
* upstream field, and whether a skill author's
|
|
10
10
|
* `graphorin-anthropic-spec` hint refers to a snapshot newer or older
|
|
11
|
-
* than the bundled one. The snapshot is checked-in to the repository
|
|
12
|
-
*
|
|
11
|
+
* than the bundled one. The snapshot is checked-in to the repository;
|
|
12
|
+
* `pnpm run check-anthropic-spec` diffs it against an upstream snapshot
|
|
13
|
+
* the maintainer supplies via `--upstream` (there is no scheduled CI
|
|
14
|
+
* job and no auto-refresh - the release `mvp-readiness` gate runs the
|
|
15
|
+
* helper in no-upstream skip mode, which only confirms the bundled
|
|
16
|
+
* snapshot parses).
|
|
13
17
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
18
|
+
* Neither the loader nor the helper fetches the upstream specification;
|
|
19
|
+
* the upstream snapshot is fetched manually. The snapshot lookup is
|
|
20
|
+
* deterministic and side-effect free at runtime.
|
|
17
21
|
*
|
|
18
22
|
* @packageDocumentation
|
|
19
23
|
*/
|
|
@@ -78,10 +82,10 @@ declare function getGraphorinMapping(field: string): GraphorinMappingEntry | und
|
|
|
78
82
|
* Compare an author's `graphorin-anthropic-spec` value against the
|
|
79
83
|
* bundled snapshot date. Returns:
|
|
80
84
|
*
|
|
81
|
-
* - `'same'`
|
|
82
|
-
* - `'older'`
|
|
83
|
-
* - `'newer'`
|
|
84
|
-
* - `'unparseable'`
|
|
85
|
+
* - `'same'` - the author targeted the same snapshot.
|
|
86
|
+
* - `'older'` - the author targeted an older snapshot.
|
|
87
|
+
* - `'newer'` - the author targeted a newer snapshot.
|
|
88
|
+
* - `'unparseable'` - the author's value could not be interpreted as
|
|
85
89
|
* an ISO-8601 date.
|
|
86
90
|
*
|
|
87
91
|
* @stable
|
package/dist/spec/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/spec/index.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/spec/index.ts"],"sourcesContent":[],"mappings":";;AA4BA;AAGA;AAGA;AAQA;AAUA;;;;;;;;AAkBA;AAUA;AAkCA;AAUA;AAgBA;;;;;;KAhHY,cAAA;;KAGA,oBAAA;;UAGK,eAAA;;;;sBAIK;;;UAIL,qBAAA;;mBAEE;;;;;;;UAQF,YAAA;;;;;wBAKO,SAAS,eAAe;6BACnB,SAAS,eAAe;;;;;;;;iBAYrC,0BAAA,WAAqC;;;;;;;iBAUrC,eAAA,CAAA,GAAmB;;;;;;;iBAkCnB,aAAA,iBAA8B;;;;;;;iBAU9B,mBAAA,iBAAoC;;;;;;;;;;;;;iBAgBpC,qBAAA"}
|
package/dist/spec/index.js
CHANGED
|
@@ -12,12 +12,16 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
* which `graphorin-*` extensions deprecate (or co-exist with) an
|
|
13
13
|
* upstream field, and whether a skill author's
|
|
14
14
|
* `graphorin-anthropic-spec` hint refers to a snapshot newer or older
|
|
15
|
-
* than the bundled one. The snapshot is checked-in to the repository
|
|
16
|
-
*
|
|
15
|
+
* than the bundled one. The snapshot is checked-in to the repository;
|
|
16
|
+
* `pnpm run check-anthropic-spec` diffs it against an upstream snapshot
|
|
17
|
+
* the maintainer supplies via `--upstream` (there is no scheduled CI
|
|
18
|
+
* job and no auto-refresh - the release `mvp-readiness` gate runs the
|
|
19
|
+
* helper in no-upstream skip mode, which only confirms the bundled
|
|
20
|
+
* snapshot parses).
|
|
17
21
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
22
|
+
* Neither the loader nor the helper fetches the upstream specification;
|
|
23
|
+
* the upstream snapshot is fetched manually. The snapshot lookup is
|
|
24
|
+
* deterministic and side-effect free at runtime.
|
|
21
25
|
*
|
|
22
26
|
* @packageDocumentation
|
|
23
27
|
*/
|
|
@@ -76,10 +80,10 @@ function getGraphorinMapping(field) {
|
|
|
76
80
|
* Compare an author's `graphorin-anthropic-spec` value against the
|
|
77
81
|
* bundled snapshot date. Returns:
|
|
78
82
|
*
|
|
79
|
-
* - `'same'`
|
|
80
|
-
* - `'older'`
|
|
81
|
-
* - `'newer'`
|
|
82
|
-
* - `'unparseable'`
|
|
83
|
+
* - `'same'` - the author targeted the same snapshot.
|
|
84
|
+
* - `'older'` - the author targeted an older snapshot.
|
|
85
|
+
* - `'newer'` - the author targeted a newer snapshot.
|
|
86
|
+
* - `'unparseable'` - the author's value could not be interpreted as
|
|
83
87
|
* an ISO-8601 date.
|
|
84
88
|
*
|
|
85
89
|
* @stable
|
package/dist/spec/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["cached: SpecSnapshot | null","overrideSnapshot: SpecSnapshot | null","lastError: unknown"],"sources":["../../src/spec/index.ts"],"sourcesContent":["/**\n * Bundled snapshot loader for the `SKILL.md` packaging-format\n * specification.\n *\n * The framework ships an offline copy of the upstream specification\n * so the loader can decide which frontmatter fields are recognised,\n * which `graphorin-*` extensions deprecate (or co-exist with) an\n * upstream field, and whether a skill author's\n * `graphorin-anthropic-spec` hint refers to a snapshot newer or older\n * than the bundled one. The snapshot is checked-in to the repository
|
|
1
|
+
{"version":3,"file":"index.js","names":["cached: SpecSnapshot | null","overrideSnapshot: SpecSnapshot | null","lastError: unknown"],"sources":["../../src/spec/index.ts"],"sourcesContent":["/**\n * Bundled snapshot loader for the `SKILL.md` packaging-format\n * specification.\n *\n * The framework ships an offline copy of the upstream specification\n * so the loader can decide which frontmatter fields are recognised,\n * which `graphorin-*` extensions deprecate (or co-exist with) an\n * upstream field, and whether a skill author's\n * `graphorin-anthropic-spec` hint refers to a snapshot newer or older\n * than the bundled one. The snapshot is checked-in to the repository;\n * `pnpm run check-anthropic-spec` diffs it against an upstream snapshot\n * the maintainer supplies via `--upstream` (there is no scheduled CI\n * job and no auto-refresh - the release `mvp-readiness` gate runs the\n * helper in no-upstream skip mode, which only confirms the bundled\n * snapshot parses).\n *\n * Neither the loader nor the helper fetches the upstream specification;\n * the upstream snapshot is fetched manually. The snapshot lookup is\n * deterministic and side-effect free at runtime.\n *\n * @packageDocumentation\n */\n\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** Stability classification of a known upstream field. */\nexport type FieldStability = 'stable' | 'standardized' | 'experimental';\n\n/** Migration policy applied to a `graphorin-*` field that maps to an upstream field. */\nexport type GraphorinFieldPolicy = 'deprecate-graphorin-prefix' | 'co-exist' | 'graphorin-only';\n\n/** Single entry of the upstream-known fields map. */\nexport interface KnownFieldEntry {\n readonly since: string;\n readonly required: boolean;\n readonly type: string;\n readonly stability: FieldStability;\n}\n\n/** Single entry of the `graphorin-*` mapping map. */\nexport interface GraphorinMappingEntry {\n readonly anthropicEquivalent: string | null;\n readonly policy: GraphorinFieldPolicy;\n readonly since?: string;\n readonly rationale?: string;\n readonly deprecateAt?: string;\n readonly removeAt?: string;\n}\n\n/** Top-level shape of the bundled snapshot. */\nexport interface SpecSnapshot {\n readonly snapshotDate: string;\n readonly specSource: string;\n readonly specCommit: string | null;\n readonly rationale?: string;\n readonly knownFields: Readonly<Record<string, KnownFieldEntry>>;\n readonly graphorinMapping: Readonly<Record<string, GraphorinMappingEntry>>;\n}\n\nlet cached: SpecSnapshot | null = null;\nlet overrideSnapshot: SpecSnapshot | null = null;\n\n/**\n * Override the bundled snapshot. Used by tests that exercise the\n * \"newer / older spec snapshot\" branches of the validator.\n *\n * @experimental\n */\nexport function _setSpecSnapshotForTesting(snapshot: SpecSnapshot | null): void {\n overrideSnapshot = snapshot;\n}\n\n/**\n * Return the currently active snapshot. Loads the bundled JSON file\n * on first call, then caches the parsed object.\n *\n * @stable\n */\nexport function getSpecSnapshot(): SpecSnapshot {\n if (overrideSnapshot !== null) return overrideSnapshot;\n if (cached !== null) return cached;\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const candidates = [\n // Source: packages/skills/src/spec/index.ts → ../../anthropic-spec-snapshot.json\n join(__dirname, '..', '..', 'anthropic-spec-snapshot.json'),\n // Built ESM: packages/skills/dist/spec/index.js → ../../anthropic-spec-snapshot.json\n join(__dirname, '..', '..', 'anthropic-spec-snapshot.json'),\n ];\n let lastError: unknown;\n for (const candidate of candidates) {\n try {\n const raw = readFileSync(candidate, 'utf8');\n const parsed = JSON.parse(raw) as SpecSnapshot;\n cached = parsed;\n return parsed;\n } catch (err) {\n lastError = err;\n }\n }\n throw new Error(\n `Failed to load bundled spec snapshot. Tried: ${candidates.join(', ')}. ` +\n `Cause: ${(lastError as Error).message}`,\n );\n}\n\n/**\n * Resolve a known-field entry by name. Returns `undefined` if the\n * field is not part of the upstream specification.\n *\n * @stable\n */\nexport function getKnownField(field: string): KnownFieldEntry | undefined {\n return getSpecSnapshot().knownFields[field];\n}\n\n/**\n * Resolve the mapping entry for a `graphorin-*` field. Returns\n * `undefined` if the field is not known to the snapshot.\n *\n * @stable\n */\nexport function getGraphorinMapping(field: string): GraphorinMappingEntry | undefined {\n return getSpecSnapshot().graphorinMapping[field];\n}\n\n/**\n * Compare an author's `graphorin-anthropic-spec` value against the\n * bundled snapshot date. Returns:\n *\n * - `'same'` - the author targeted the same snapshot.\n * - `'older'` - the author targeted an older snapshot.\n * - `'newer'` - the author targeted a newer snapshot.\n * - `'unparseable'` - the author's value could not be interpreted as\n * an ISO-8601 date.\n *\n * @stable\n */\nexport function compareAuthorSpecHint(\n authorValue: string,\n): 'same' | 'older' | 'newer' | 'unparseable' {\n const snapshot = getSpecSnapshot();\n const author = parseDate(authorValue);\n const local = parseDate(snapshot.snapshotDate);\n if (author === null || local === null) return 'unparseable';\n if (author.getTime() === local.getTime()) return 'same';\n return author.getTime() > local.getTime() ? 'newer' : 'older';\n}\n\nfunction parseDate(value: string): Date | null {\n const trimmed = value.trim();\n if (!/^\\d{4}-\\d{2}-\\d{2}/u.test(trimmed)) return null;\n const date = new Date(trimmed);\n if (Number.isNaN(date.getTime())) return null;\n return date;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,IAAIA,SAA8B;AAClC,IAAIC,mBAAwC;;;;;;;AAQ5C,SAAgB,2BAA2B,UAAqC;AAC9E,oBAAmB;;;;;;;;AASrB,SAAgB,kBAAgC;AAC9C,KAAI,qBAAqB,KAAM,QAAO;AACtC,KAAI,WAAW,KAAM,QAAO;CAE5B,MAAM,YAAY,QADC,cAAc,OAAO,KAAK,IAAI,CACZ;CACrC,MAAM,aAAa,CAEjB,KAAK,WAAW,MAAM,MAAM,+BAA+B,EAE3D,KAAK,WAAW,MAAM,MAAM,+BAA+B,CAC5D;CACD,IAAIC;AACJ,MAAK,MAAM,aAAa,WACtB,KAAI;EACF,MAAM,MAAM,aAAa,WAAW,OAAO;EAC3C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,WAAS;AACT,SAAO;UACA,KAAK;AACZ,cAAY;;AAGhB,OAAM,IAAI,MACR,gDAAgD,WAAW,KAAK,KAAK,CAAC,WACzD,UAAoB,UAClC;;;;;;;;AASH,SAAgB,cAAc,OAA4C;AACxE,QAAO,iBAAiB,CAAC,YAAY;;;;;;;;AASvC,SAAgB,oBAAoB,OAAkD;AACpF,QAAO,iBAAiB,CAAC,iBAAiB;;;;;;;;;;;;;;AAe5C,SAAgB,sBACd,aAC4C;CAC5C,MAAM,WAAW,iBAAiB;CAClC,MAAM,SAAS,UAAU,YAAY;CACrC,MAAM,QAAQ,UAAU,SAAS,aAAa;AAC9C,KAAI,WAAW,QAAQ,UAAU,KAAM,QAAO;AAC9C,KAAI,OAAO,SAAS,KAAK,MAAM,SAAS,CAAE,QAAO;AACjD,QAAO,OAAO,SAAS,GAAG,MAAM,SAAS,GAAG,UAAU;;AAGxD,SAAS,UAAU,OAA4B;CAC7C,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,sBAAsB,KAAK,QAAQ,CAAE,QAAO;CACjD,MAAM,OAAO,IAAI,KAAK,QAAQ;AAC9B,KAAI,OAAO,MAAM,KAAK,SAAS,CAAC,CAAE,QAAO;AACzC,QAAO"}
|