@graphorin/skills 0.5.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 +7 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/anthropic-spec-snapshot.json +114 -0
- package/dist/activation/index.d.ts +30 -0
- package/dist/activation/index.d.ts.map +1 -0
- package/dist/activation/index.js +71 -0
- package/dist/activation/index.js.map +1 -0
- package/dist/errors/index.d.ts +110 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +126 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/frontmatter/index.d.ts +120 -0
- package/dist/frontmatter/index.d.ts.map +1 -0
- package/dist/frontmatter/index.js +451 -0
- package/dist/frontmatter/index.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/loader/index.d.ts +55 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +463 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/migration/index.d.ts +62 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/index.js +131 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/registry/bridge.d.ts +47 -0
- package/dist/registry/bridge.d.ts.map +1 -0
- package/dist/registry/bridge.js +91 -0
- package/dist/registry/bridge.js.map +1 -0
- package/dist/registry/index.d.ts +140 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +193 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/spec/index.d.ts +92 -0
- package/dist/spec/index.d.ts.map +1 -0
- package/dist/spec/index.js +105 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/types/index.d.ts +305 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { SkillManifestParseError } from "../errors/index.js";
|
|
2
|
+
import { getSpecSnapshot } from "../spec/index.js";
|
|
3
|
+
import { splitSkillMd } from "../frontmatter/index.js";
|
|
4
|
+
import { parse, stringify } from "yaml";
|
|
5
|
+
|
|
6
|
+
//#region src/migration/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* `migrate-frontmatter` — idempotent rewrite helper that migrates
|
|
9
|
+
* legacy `graphorin-*` frontmatter fields onto their upstream
|
|
10
|
+
* equivalents per the `deprecate-graphorin-prefix` mappings recorded
|
|
11
|
+
* in the bundled spec snapshot.
|
|
12
|
+
*
|
|
13
|
+
* The function is dry-run by default — callers must opt in to
|
|
14
|
+
* persisting the rewritten bytes. The CLI binary in Phase 15 wraps
|
|
15
|
+
* this surface; the library is exposed here so other tooling can
|
|
16
|
+
* reuse it.
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Migrate the bundled `deprecate-graphorin-prefix` mappings on a
|
|
22
|
+
* single SKILL.md. The function is idempotent: re-running it on an
|
|
23
|
+
* already-migrated SKILL.md returns `changed: false` and an empty
|
|
24
|
+
* `rewrites` array.
|
|
25
|
+
*
|
|
26
|
+
* @stable
|
|
27
|
+
*/
|
|
28
|
+
function migrateFrontmatter(skillMd, options = {}) {
|
|
29
|
+
const skillId = options.skillId ?? "<inline>";
|
|
30
|
+
const apply = options.apply ?? false;
|
|
31
|
+
const split = splitSkillMd(skillMd);
|
|
32
|
+
let frontmatter;
|
|
33
|
+
try {
|
|
34
|
+
frontmatter = parse(split.frontmatter);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw new SkillManifestParseError(`Skill '${skillId}' frontmatter is not valid YAML.`, { cause: err });
|
|
37
|
+
}
|
|
38
|
+
if (frontmatter === null || typeof frontmatter !== "object" || Array.isArray(frontmatter)) throw new SkillManifestParseError(`Skill '${skillId}' frontmatter must be a top-level object.`);
|
|
39
|
+
const fm = { ...frontmatter };
|
|
40
|
+
const snapshot = getSpecSnapshot();
|
|
41
|
+
const rewrites = [];
|
|
42
|
+
let mutated = false;
|
|
43
|
+
for (const [graphorinField, mapping] of Object.entries(snapshot.graphorinMapping)) {
|
|
44
|
+
if (!(graphorinField in fm)) continue;
|
|
45
|
+
if (mapping.policy !== "deprecate-graphorin-prefix") {
|
|
46
|
+
rewrites.push(Object.freeze({
|
|
47
|
+
fromField: graphorinField,
|
|
48
|
+
toField: graphorinField,
|
|
49
|
+
reason: mapping.policy === "co-exist" ? "co-exist-noop" : "graphorin-only-noop",
|
|
50
|
+
applied: false
|
|
51
|
+
}));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (mapping.anthropicEquivalent === null) {
|
|
55
|
+
rewrites.push(Object.freeze({
|
|
56
|
+
fromField: graphorinField,
|
|
57
|
+
toField: graphorinField,
|
|
58
|
+
reason: "graphorin-only-noop",
|
|
59
|
+
applied: false
|
|
60
|
+
}));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (mapping.anthropicEquivalent in fm) {
|
|
64
|
+
rewrites.push(Object.freeze({
|
|
65
|
+
fromField: graphorinField,
|
|
66
|
+
toField: mapping.anthropicEquivalent,
|
|
67
|
+
reason: "deprecate-graphorin-prefix",
|
|
68
|
+
applied: false
|
|
69
|
+
}));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (apply) {
|
|
73
|
+
fm[mapping.anthropicEquivalent] = fm[graphorinField];
|
|
74
|
+
delete fm[graphorinField];
|
|
75
|
+
mutated = true;
|
|
76
|
+
}
|
|
77
|
+
rewrites.push(Object.freeze({
|
|
78
|
+
fromField: graphorinField,
|
|
79
|
+
toField: mapping.anthropicEquivalent,
|
|
80
|
+
reason: "deprecate-graphorin-prefix",
|
|
81
|
+
applied: apply
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
if (!apply || !mutated) return Object.freeze({
|
|
85
|
+
skillId,
|
|
86
|
+
rewrites: Object.freeze(rewrites),
|
|
87
|
+
originalSkillMd: skillMd,
|
|
88
|
+
migratedSkillMd: skillMd,
|
|
89
|
+
changed: false
|
|
90
|
+
});
|
|
91
|
+
const migratedSkillMd = `---\n${stringify(sortKeysAnthropicFirst(fm), {
|
|
92
|
+
sortMapEntries: false,
|
|
93
|
+
lineWidth: 0
|
|
94
|
+
}).trimEnd()}\n---\n${split.body}`;
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
skillId,
|
|
97
|
+
rewrites: Object.freeze(rewrites),
|
|
98
|
+
originalSkillMd: skillMd,
|
|
99
|
+
migratedSkillMd,
|
|
100
|
+
changed: true
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Stable key ordering: Anthropic-base fields first (in their snapshot
|
|
105
|
+
* insertion order), then the `metadata` bucket, then the
|
|
106
|
+
* `graphorin-*` fields, then anything else. The migrator emits in
|
|
107
|
+
* this order so re-running the migrator on the same input yields
|
|
108
|
+
* identical bytes (idempotence).
|
|
109
|
+
*
|
|
110
|
+
* @stable
|
|
111
|
+
*/
|
|
112
|
+
function sortKeysAnthropicFirst(frontmatter) {
|
|
113
|
+
const snapshot = getSpecSnapshot();
|
|
114
|
+
const baseKeys = Object.keys(snapshot.knownFields);
|
|
115
|
+
const out = {};
|
|
116
|
+
for (const key of baseKeys) if (key in frontmatter) out[key] = frontmatter[key];
|
|
117
|
+
if ("metadata" in frontmatter) out.metadata = frontmatter.metadata;
|
|
118
|
+
const remaining = Object.keys(frontmatter).filter((k) => !(k in out)).sort((a, b) => {
|
|
119
|
+
const aGraphorin = a.startsWith("graphorin-");
|
|
120
|
+
const bGraphorin = b.startsWith("graphorin-");
|
|
121
|
+
if (aGraphorin && !bGraphorin) return 1;
|
|
122
|
+
if (!aGraphorin && bGraphorin) return -1;
|
|
123
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
124
|
+
});
|
|
125
|
+
for (const key of remaining) out[key] = frontmatter[key];
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { migrateFrontmatter, sortKeysAnthropicFirst };
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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` — 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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Skill, SkillMetadata } from "../types/index.js";
|
|
2
|
+
import { resolveSandbox } from "@graphorin/security/sandbox";
|
|
3
|
+
import { Tool, ToolSource } from "@graphorin/core";
|
|
4
|
+
|
|
5
|
+
//#region src/registry/bridge.d.ts
|
|
6
|
+
|
|
7
|
+
/** Result of {@link stampSkillTool}. */
|
|
8
|
+
interface StampedSkillTool<TInput = unknown, TOutput = unknown, TDeps = unknown> {
|
|
9
|
+
readonly tool: Tool<TInput, TOutput, TDeps>;
|
|
10
|
+
readonly source: ToolSource;
|
|
11
|
+
/** Resolved sandbox policy after the tier resolver ran. */
|
|
12
|
+
readonly resolvedSandbox: ReturnType<typeof resolveSandbox>;
|
|
13
|
+
/** `true` when the resolver overrode the operator's choice. */
|
|
14
|
+
readonly sandboxForced: boolean;
|
|
15
|
+
/** `true` when the inbound sanitization policy was upgraded to the untrusted default. */
|
|
16
|
+
readonly inboundSanitizationForced: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Stamp a skill-bundled tool with the metadata the
|
|
20
|
+
* `@graphorin/tools` registry expects:
|
|
21
|
+
*
|
|
22
|
+
* 1. Derive a `ToolSource` of kind `'skill'` carrying the skill's
|
|
23
|
+
* name and trust level.
|
|
24
|
+
* 2. Run `resolveSandbox(...)` so the resulting `Tool.sandboxPolicy`
|
|
25
|
+
* matches the mandatory tier for untrusted skills (DEC-148).
|
|
26
|
+
* 3. Default the `inboundSanitization` policy to
|
|
27
|
+
* `'detect-and-strip-and-wrap'` for untrusted skills when the tool
|
|
28
|
+
* author left it unset; trusted skills inherit the operator's
|
|
29
|
+
* choice.
|
|
30
|
+
*
|
|
31
|
+
* The function returns a freshly-frozen `Tool` with the resolved
|
|
32
|
+
* `sandboxPolicy` and `inboundSanitization` baked in so downstream
|
|
33
|
+
* registries cannot accidentally re-inherit a relaxed policy.
|
|
34
|
+
*
|
|
35
|
+
* @stable
|
|
36
|
+
*/
|
|
37
|
+
declare function stampSkillTool<TInput = unknown, TOutput = unknown, TDeps = unknown>(tool: Tool<TInput, TOutput, TDeps>, skill: Pick<Skill, 'metadata'>): StampedSkillTool<TInput, TOutput, TDeps>;
|
|
38
|
+
/**
|
|
39
|
+
* Lower-level variant accepting a raw {@link SkillMetadata} so
|
|
40
|
+
* fixtures and tests do not have to materialise a full {@link Skill}.
|
|
41
|
+
*
|
|
42
|
+
* @stable
|
|
43
|
+
*/
|
|
44
|
+
declare function stampSkillToolFromMetadata<TInput = unknown, TOutput = unknown, TDeps = unknown>(tool: Tool<TInput, TOutput, TDeps>, metadata: SkillMetadata): StampedSkillTool<TInput, TOutput, TDeps>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { StampedSkillTool, stampSkillTool, stampSkillToolFromMetadata };
|
|
47
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","names":[],"sources":["../../src/registry/bridge.ts"],"sourcesContent":[],"mappings":";;;;;;;AAsDc,UAhCG,gBAgCH,CAAA,SAAA,OAAA,EAAA,UAAA,OAAA,EAAA,QAAA,OAAA,CAAA,CAAA;EAAL,SAAA,IAAA,EA/BQ,IA+BR,CA/Ba,MA+Bb,EA/BqB,OA+BrB,EA/B8B,KA+B9B,CAAA;EACW,SAAA,MAAA,EA/BD,UA+BC;EAAQ;EAAS,SAAA,eAAA,EA7BT,UA6BS,CAAA,OA7BS,cA6BT,CAAA;EAAlC;EAAgB,SAAA,aAAA,EAAA,OAAA;EAUH;EACH,SAAA,yBAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;;;;;iBAdG,2EACR,KAAK,QAAQ,SAAS,eACrB,KAAK,qBACX,iBAAiB,QAAQ,SAAS;;;;;;;iBAUrB,uFACR,KAAK,QAAQ,SAAS,kBAClB,gBACT,iBAAiB,QAAQ,SAAS"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { resolveSandbox } from "@graphorin/security/sandbox";
|
|
2
|
+
|
|
3
|
+
//#region src/registry/bridge.ts
|
|
4
|
+
/**
|
|
5
|
+
* Stamp a skill-bundled tool with the metadata the
|
|
6
|
+
* `@graphorin/tools` registry expects:
|
|
7
|
+
*
|
|
8
|
+
* 1. Derive a `ToolSource` of kind `'skill'` carrying the skill's
|
|
9
|
+
* name and trust level.
|
|
10
|
+
* 2. Run `resolveSandbox(...)` so the resulting `Tool.sandboxPolicy`
|
|
11
|
+
* matches the mandatory tier for untrusted skills (DEC-148).
|
|
12
|
+
* 3. Default the `inboundSanitization` policy to
|
|
13
|
+
* `'detect-and-strip-and-wrap'` for untrusted skills when the tool
|
|
14
|
+
* author left it unset; trusted skills inherit the operator's
|
|
15
|
+
* choice.
|
|
16
|
+
*
|
|
17
|
+
* The function returns a freshly-frozen `Tool` with the resolved
|
|
18
|
+
* `sandboxPolicy` and `inboundSanitization` baked in so downstream
|
|
19
|
+
* registries cannot accidentally re-inherit a relaxed policy.
|
|
20
|
+
*
|
|
21
|
+
* @stable
|
|
22
|
+
*/
|
|
23
|
+
function stampSkillTool(tool, skill) {
|
|
24
|
+
return stampSkillToolFromMetadata(tool, skill.metadata);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Lower-level variant accepting a raw {@link SkillMetadata} so
|
|
28
|
+
* fixtures and tests do not have to materialise a full {@link Skill}.
|
|
29
|
+
*
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
function stampSkillToolFromMetadata(tool, metadata) {
|
|
33
|
+
const sandboxResolverArgs = { trustLevel: mapTrustLevel(metadata.graphorinTrustLevel) };
|
|
34
|
+
if (tool.name !== void 0) sandboxResolverArgs.toolName = tool.name;
|
|
35
|
+
if (metadata.name !== void 0) sandboxResolverArgs.skillName = metadata.name;
|
|
36
|
+
const operatorOverride = sandboxOverrideFromTool(tool);
|
|
37
|
+
if (operatorOverride !== void 0) sandboxResolverArgs.override = operatorOverride;
|
|
38
|
+
const resolvedSandbox = resolveSandbox(sandboxResolverArgs);
|
|
39
|
+
const inboundSanitization = inboundSanitizationFor(tool, metadata.graphorinTrustLevel);
|
|
40
|
+
const stamped = {
|
|
41
|
+
...tool,
|
|
42
|
+
sandboxPolicy: mapSandboxKindToPolicy(resolvedSandbox.kind),
|
|
43
|
+
...inboundSanitization === void 0 ? {} : { inboundSanitization }
|
|
44
|
+
};
|
|
45
|
+
const source = Object.freeze({
|
|
46
|
+
kind: "skill",
|
|
47
|
+
skillName: metadata.name,
|
|
48
|
+
trustLevel: metadata.graphorinTrustLevel === "untrusted" || metadata.graphorinTrustLevel === "unknown" ? "untrusted" : "trusted"
|
|
49
|
+
});
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
tool: Object.freeze(stamped),
|
|
52
|
+
source,
|
|
53
|
+
resolvedSandbox,
|
|
54
|
+
sandboxForced: resolvedSandbox.forced,
|
|
55
|
+
inboundSanitizationForced: inboundSanitization !== void 0 && tool.inboundSanitization === void 0
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function mapTrustLevel(level) {
|
|
59
|
+
switch (level) {
|
|
60
|
+
case "untrusted":
|
|
61
|
+
case "unknown": return "untrusted";
|
|
62
|
+
case "trusted":
|
|
63
|
+
case "trusted-with-scripts": return "trusted";
|
|
64
|
+
default: return "user-defined";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function mapSandboxKindToPolicy(kind) {
|
|
68
|
+
if (kind === "none") return "none";
|
|
69
|
+
if (kind === "worker-threads") return "sandboxed";
|
|
70
|
+
if (kind === "isolated-vm") return "isolated";
|
|
71
|
+
if (kind === "docker") return "docker";
|
|
72
|
+
return "sandboxed";
|
|
73
|
+
}
|
|
74
|
+
function inboundSanitizationFor(tool, trustLevel) {
|
|
75
|
+
if (tool.inboundSanitization !== void 0) return tool.inboundSanitization;
|
|
76
|
+
if (trustLevel === "untrusted" || trustLevel === "unknown") return "detect-and-strip-and-wrap";
|
|
77
|
+
}
|
|
78
|
+
function sandboxOverrideFromTool(tool) {
|
|
79
|
+
switch (tool.sandboxPolicy) {
|
|
80
|
+
case void 0: return;
|
|
81
|
+
case "none": return { kind: "none" };
|
|
82
|
+
case "sandboxed": return { kind: "worker-threads" };
|
|
83
|
+
case "isolated": return { kind: "isolated-vm" };
|
|
84
|
+
case "docker": return { kind: "docker" };
|
|
85
|
+
default: return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { stampSkillTool, stampSkillToolFromMetadata };
|
|
91
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +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 —\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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ActivatedSkill, InlineSkillTool, Skill, SkillMetadata, SkillToolDeclaration } from "../types/index.js";
|
|
2
|
+
import { StampedSkillTool, stampSkillTool, stampSkillToolFromMetadata } from "./bridge.js";
|
|
3
|
+
import { ResolvedTool } from "@graphorin/core";
|
|
4
|
+
|
|
5
|
+
//#region src/registry/index.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stamping seam injected by the agent runtime (Phase 12). It turns a skill's
|
|
9
|
+
* pre-built `Tool` into a fully resolved `ResolvedTool` (trust class + sandbox
|
|
10
|
+
* tier + source). The skills package keeps no hard dependency on
|
|
11
|
+
* `@graphorin/tools`; when no stamper is configured, `activate()` surfaces no
|
|
12
|
+
* tools (the runtime resolves them itself).
|
|
13
|
+
*/
|
|
14
|
+
type SkillToolStamper = (tool: InlineSkillTool, metadata: SkillMetadata) => ResolvedTool;
|
|
15
|
+
/** Options accepted by {@link createSkillRegistry}. */
|
|
16
|
+
interface SkillRegistryOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Default activation behaviour. When `'metadata-only'` (default),
|
|
19
|
+
* `activate(...)` returns the parsed activation request without
|
|
20
|
+
* invoking `Skill.body()`; callers (the agent runtime) then invoke
|
|
21
|
+
* the body resolver themselves so the runtime can attach a span.
|
|
22
|
+
* When `'eager'`, the registry resolves the body before returning,
|
|
23
|
+
* suitable for tests.
|
|
24
|
+
*/
|
|
25
|
+
readonly activationStrategy?: 'metadata-only' | 'eager';
|
|
26
|
+
/**
|
|
27
|
+
* Optional stamping function (RP-11). When supplied, `activate()` runs each
|
|
28
|
+
* skill's pre-built `Tool[]` through it and surfaces the results on
|
|
29
|
+
* {@link ActivatedSkill.tools}. Without it, `activate()` surfaces no tools —
|
|
30
|
+
* the agent runtime resolves and stamps them itself.
|
|
31
|
+
*/
|
|
32
|
+
readonly stampTool?: SkillToolStamper;
|
|
33
|
+
}
|
|
34
|
+
/** Public registry surface. */
|
|
35
|
+
interface SkillRegistry {
|
|
36
|
+
register(skill: Skill): void;
|
|
37
|
+
/**
|
|
38
|
+
* Upsert a skill by name (RP-11). Unlike {@link SkillRegistry.register},
|
|
39
|
+
* `replace` overwrites an existing registration instead of throwing on a
|
|
40
|
+
* name collision — the upgrade path for hot-reloading a re-loaded skill.
|
|
41
|
+
*/
|
|
42
|
+
replace(skill: Skill): void;
|
|
43
|
+
unregister(name: string): boolean;
|
|
44
|
+
getSkill(name: string): Skill | undefined;
|
|
45
|
+
has(name: string): boolean;
|
|
46
|
+
list(): ReadonlyArray<Skill>;
|
|
47
|
+
getMetadata(): ReadonlyArray<SkillMetadata>;
|
|
48
|
+
/**
|
|
49
|
+
* Skills surfaced into the system prompt for auto-activation.
|
|
50
|
+
* Skills with `disable-model-invocation: true` are excluded.
|
|
51
|
+
*/
|
|
52
|
+
getAutoActivationMetadata(): ReadonlyArray<SkillMetadata>;
|
|
53
|
+
/**
|
|
54
|
+
* Render the auto-activation metadata as a string suitable for the
|
|
55
|
+
* system prompt. The format is bytes-stable and consumed verbatim
|
|
56
|
+
* by the ContextEngine layered template (Phase 10d). Skills with
|
|
57
|
+
* `disable-model-invocation: true` are excluded.
|
|
58
|
+
*/
|
|
59
|
+
getMetadataBlock(): string;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a single trigger (model-emitted skill name OR the raw
|
|
62
|
+
* `/skill:<name>` slash-command body) into an {@link ActivationRequest}.
|
|
63
|
+
* Returns `null` when no skill matches and the trigger looked like a
|
|
64
|
+
* slash command — callers that want a strict mode should call
|
|
65
|
+
* {@link parseActivationTrigger} themselves.
|
|
66
|
+
*/
|
|
67
|
+
resolveTrigger(trigger: string): ActivationRequest | null;
|
|
68
|
+
activate(triggers: ReadonlyArray<string>, signal?: AbortSignal): Promise<ReadonlyArray<ActivatedSkill>>;
|
|
69
|
+
/**
|
|
70
|
+
* Best-effort match: returns every skill whose name OR description
|
|
71
|
+
* contains all of the supplied trigger tokens (case-insensitive).
|
|
72
|
+
* The agent runtime uses this when the model emits a trigger phrase
|
|
73
|
+
* that does not directly map to a skill name.
|
|
74
|
+
*/
|
|
75
|
+
search(triggers: ReadonlyArray<string>): ReadonlyArray<Skill>;
|
|
76
|
+
/**
|
|
77
|
+
* Flat, deduplicated list of every pre-built tool shipped by the
|
|
78
|
+
* registered skills. The first registration wins on a `tool.name`
|
|
79
|
+
* collision; later collisions surface a one-time WARN through the
|
|
80
|
+
* console so operators can resolve the conflict (Phase 12 will
|
|
81
|
+
* route these through the audit emitter).
|
|
82
|
+
*/
|
|
83
|
+
tools(): ReadonlyArray<InlineSkillTool>;
|
|
84
|
+
toolDeclarations(): ReadonlyArray<RegisteredToolDeclaration>;
|
|
85
|
+
size(): number;
|
|
86
|
+
clear(): void;
|
|
87
|
+
}
|
|
88
|
+
/** Activation request produced by {@link SkillRegistry.resolveTrigger}. */
|
|
89
|
+
interface ActivationRequest {
|
|
90
|
+
readonly skill: Skill;
|
|
91
|
+
readonly activationKind: ActivatedSkill['activationKind'];
|
|
92
|
+
readonly args?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Tool-declaration record exposed by {@link SkillRegistry.toolDeclarations}.
|
|
96
|
+
* Adds the owning skill's name and trust level so downstream
|
|
97
|
+
* registrations into `@graphorin/tools` can stamp the source.
|
|
98
|
+
*
|
|
99
|
+
* @stable
|
|
100
|
+
*/
|
|
101
|
+
interface RegisteredToolDeclaration extends SkillToolDeclaration {
|
|
102
|
+
readonly skillName: string;
|
|
103
|
+
readonly trustLevel: SkillMetadata['graphorinTrustLevel'];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build a fresh, empty registry. Multiple registries can co-exist
|
|
107
|
+
* within a single process; the framework defaults to a single shared
|
|
108
|
+
* instance per agent instance.
|
|
109
|
+
*
|
|
110
|
+
* @stable
|
|
111
|
+
*/
|
|
112
|
+
declare function createSkillRegistry(options?: SkillRegistryOptions): SkillRegistry;
|
|
113
|
+
/**
|
|
114
|
+
* Parsed activation trigger. The registry uses this to discriminate
|
|
115
|
+
* slash-command activations (which override
|
|
116
|
+
* `disable-model-invocation: true`) from model-emitted auto
|
|
117
|
+
* activations (which honour it).
|
|
118
|
+
*
|
|
119
|
+
* @stable
|
|
120
|
+
*/
|
|
121
|
+
interface ParsedActivationTrigger {
|
|
122
|
+
readonly name: string;
|
|
123
|
+
readonly activationKind: ActivatedSkill['activationKind'];
|
|
124
|
+
readonly args?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse a single activation trigger. Slash-command bodies
|
|
128
|
+
* (`/skill:<name>`) are routed through the slash parser; bare names
|
|
129
|
+
* are treated as auto-activation requests emitted by the model.
|
|
130
|
+
*
|
|
131
|
+
* Throws {@link SlashCommandParseError} when the body looks like a
|
|
132
|
+
* slash command but does not match the grammar (so the caller can
|
|
133
|
+
* surface the error to the user).
|
|
134
|
+
*
|
|
135
|
+
* @stable
|
|
136
|
+
*/
|
|
137
|
+
declare function parseActivationTrigger(raw: string): ParsedActivationTrigger;
|
|
138
|
+
//#endregion
|
|
139
|
+
export { ActivationRequest, ParsedActivationTrigger, RegisteredToolDeclaration, SkillRegistry, SkillRegistryOptions, SkillToolStamper, type StampedSkillTool, createSkillRegistry, parseActivationTrigger, stampSkillTool, stampSkillToolFromMetadata };
|
|
140
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/registry/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAsHW,KA1EC,gBAAA,GA0ED,CAAA,IAAA,EA1E2B,eA0E3B,EAAA,QAAA,EA1EsD,aA0EtD,EAAA,GA1EwE,YA0ExE;;AACW,UAxEL,oBAAA,CAwEK;EAAa;AAMnC;AAaA;AAYA;AA6NA;AAiBA;;;;;;;;;;uBArUuB;;;UAIN,aAAA;kBACC;;;;;;iBAMD;;0BAES;;UAEhB,cAAc;iBACP,cAAc;;;;;+BAKA,cAAc;;;;;;;;;;;;;;;mCAeV;qBAErB,gCACD,cACR,QAAQ,cAAc;;;;;;;mBAOR,wBAAwB,cAAc;;;;;;;;WAQ9C,cAAc;sBACH,cAAc;;;;;UAMnB,iBAAA;kBACC;2BACS;;;;;;;;;;UAWV,yBAAA,SAAkC;;uBAE5B;;;;;;;;;iBAUP,mBAAA,WAA6B,uBAA4B;;;;;;;;;UA6NxD,uBAAA;;2BAEU;;;;;;;;;;;;;;iBAeX,sBAAA,eAAqC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { SkillNameCollisionError, SlashCommandParseError } from "../errors/index.js";
|
|
2
|
+
import { parseSlashCommand } from "../activation/index.js";
|
|
3
|
+
import { stampSkillTool, stampSkillToolFromMetadata } from "./bridge.js";
|
|
4
|
+
|
|
5
|
+
//#region src/registry/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build a fresh, empty registry. Multiple registries can co-exist
|
|
8
|
+
* within a single process; the framework defaults to a single shared
|
|
9
|
+
* instance per agent instance.
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
function createSkillRegistry(options = {}) {
|
|
14
|
+
const skillsByName = /* @__PURE__ */ new Map();
|
|
15
|
+
const strategy = options.activationStrategy ?? "metadata-only";
|
|
16
|
+
function register(skill) {
|
|
17
|
+
if (skillsByName.has(skill.metadata.name)) throw new SkillNameCollisionError(skill.metadata.name);
|
|
18
|
+
skillsByName.set(skill.metadata.name, skill);
|
|
19
|
+
}
|
|
20
|
+
function replace(skill) {
|
|
21
|
+
skillsByName.set(skill.metadata.name, skill);
|
|
22
|
+
}
|
|
23
|
+
function unregister(name) {
|
|
24
|
+
return skillsByName.delete(name);
|
|
25
|
+
}
|
|
26
|
+
function getSkill(name) {
|
|
27
|
+
return skillsByName.get(name);
|
|
28
|
+
}
|
|
29
|
+
function has(name) {
|
|
30
|
+
return skillsByName.has(name);
|
|
31
|
+
}
|
|
32
|
+
function list() {
|
|
33
|
+
return Object.freeze([...skillsByName.values()]);
|
|
34
|
+
}
|
|
35
|
+
function getMetadata() {
|
|
36
|
+
return Object.freeze([...skillsByName.values()].map((skill) => skill.metadata));
|
|
37
|
+
}
|
|
38
|
+
function getAutoActivationMetadata() {
|
|
39
|
+
return Object.freeze([...skillsByName.values()].map((skill) => skill.metadata).filter((metadata) => !metadata.disableModelInvocation));
|
|
40
|
+
}
|
|
41
|
+
function getMetadataBlock() {
|
|
42
|
+
const lines = [];
|
|
43
|
+
const auto = getAutoActivationMetadata();
|
|
44
|
+
if (auto.length === 0) return "";
|
|
45
|
+
lines.push("# Available skills");
|
|
46
|
+
lines.push("");
|
|
47
|
+
for (const meta of auto) {
|
|
48
|
+
lines.push(`## ${meta.name}`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
const description = meta.description.replace(/\s+/gu, " ").trim();
|
|
51
|
+
if (description.length > 0) lines.push(description);
|
|
52
|
+
const cues = [];
|
|
53
|
+
if (meta.allowedTools !== void 0 && meta.allowedTools.length > 0) cues.push(`Allowed tools: ${[...meta.allowedTools].join(", ")}`);
|
|
54
|
+
if (meta.graphorinSensitivity !== void 0) cues.push(`Sensitivity: ${meta.graphorinSensitivity}`);
|
|
55
|
+
if (cues.length > 0) {
|
|
56
|
+
lines.push("");
|
|
57
|
+
for (const cue of cues) lines.push(`- ${cue}`);
|
|
58
|
+
}
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
return lines.join("\n").replace(/\n+$/u, "\n");
|
|
62
|
+
}
|
|
63
|
+
function search(triggers) {
|
|
64
|
+
const tokens = [...triggers].flatMap((trigger) => trigger.toLowerCase().split(/\s+/u)).filter((tok) => tok.length > 0);
|
|
65
|
+
if (tokens.length === 0) return Object.freeze([]);
|
|
66
|
+
const matches = [];
|
|
67
|
+
const seen = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const skill of skillsByName.values()) {
|
|
69
|
+
const haystack = `${skill.metadata.name}\n${skill.metadata.description}`.toLowerCase();
|
|
70
|
+
if (tokens.every((tok) => haystack.includes(tok)) && !seen.has(skill.metadata.name)) {
|
|
71
|
+
seen.add(skill.metadata.name);
|
|
72
|
+
matches.push(skill);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return Object.freeze(matches);
|
|
76
|
+
}
|
|
77
|
+
function tools() {
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
const out = [];
|
|
80
|
+
const collisions = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const skill of skillsByName.values()) for (const tool of skill.tools()) {
|
|
82
|
+
if (seen.has(tool.name)) {
|
|
83
|
+
if (!collisions.has(tool.name)) {
|
|
84
|
+
collisions.add(tool.name);
|
|
85
|
+
console.warn(`[graphorin/skills] Duplicate tool name '${tool.name}' across skills; first registration wins.`);
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
seen.add(tool.name);
|
|
90
|
+
out.push(tool);
|
|
91
|
+
}
|
|
92
|
+
return Object.freeze(out);
|
|
93
|
+
}
|
|
94
|
+
function resolveTrigger(trigger) {
|
|
95
|
+
const parsed = parseActivationTrigger(trigger);
|
|
96
|
+
const skill = skillsByName.get(parsed.name);
|
|
97
|
+
if (skill === void 0) return null;
|
|
98
|
+
if (parsed.activationKind === "auto" && skill.metadata.disableModelInvocation) return null;
|
|
99
|
+
const request = {
|
|
100
|
+
skill,
|
|
101
|
+
activationKind: parsed.activationKind
|
|
102
|
+
};
|
|
103
|
+
if (parsed.args !== void 0 && parsed.args.length > 0) request.args = parsed.args;
|
|
104
|
+
return Object.freeze(request);
|
|
105
|
+
}
|
|
106
|
+
async function activate(triggers, signal) {
|
|
107
|
+
const out = [];
|
|
108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
109
|
+
for (const trigger of triggers) {
|
|
110
|
+
const request = resolveTrigger(trigger);
|
|
111
|
+
if (request === null) continue;
|
|
112
|
+
if (seen.has(request.skill.metadata.name)) continue;
|
|
113
|
+
seen.add(request.skill.metadata.name);
|
|
114
|
+
const body = strategy === "eager" ? await request.skill.body(signal) : "";
|
|
115
|
+
const resources = strategy === "eager" ? await request.skill.resources(signal) : Object.freeze([]);
|
|
116
|
+
const stampedTools = options.stampTool !== void 0 ? Object.freeze(request.skill.tools().map((tool) => options.stampTool(tool, request.skill.metadata))) : Object.freeze([]);
|
|
117
|
+
out.push(Object.freeze({
|
|
118
|
+
skill: request.skill,
|
|
119
|
+
body,
|
|
120
|
+
resources,
|
|
121
|
+
tools: stampedTools,
|
|
122
|
+
activationKind: request.activationKind,
|
|
123
|
+
activatedAt: Date.now()
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
return Object.freeze(out);
|
|
127
|
+
}
|
|
128
|
+
function toolDeclarations() {
|
|
129
|
+
const out = [];
|
|
130
|
+
for (const skill of skillsByName.values()) for (const decl of skill.toolDeclarations()) out.push(Object.freeze({
|
|
131
|
+
...decl,
|
|
132
|
+
skillName: skill.metadata.name,
|
|
133
|
+
trustLevel: skill.metadata.graphorinTrustLevel
|
|
134
|
+
}));
|
|
135
|
+
return Object.freeze(out);
|
|
136
|
+
}
|
|
137
|
+
function size() {
|
|
138
|
+
return skillsByName.size;
|
|
139
|
+
}
|
|
140
|
+
function clear() {
|
|
141
|
+
skillsByName.clear();
|
|
142
|
+
}
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
register,
|
|
145
|
+
replace,
|
|
146
|
+
unregister,
|
|
147
|
+
getSkill,
|
|
148
|
+
has,
|
|
149
|
+
list,
|
|
150
|
+
getMetadata,
|
|
151
|
+
getAutoActivationMetadata,
|
|
152
|
+
getMetadataBlock,
|
|
153
|
+
resolveTrigger,
|
|
154
|
+
activate,
|
|
155
|
+
search,
|
|
156
|
+
tools,
|
|
157
|
+
toolDeclarations,
|
|
158
|
+
size,
|
|
159
|
+
clear
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Parse a single activation trigger. Slash-command bodies
|
|
164
|
+
* (`/skill:<name>`) are routed through the slash parser; bare names
|
|
165
|
+
* are treated as auto-activation requests emitted by the model.
|
|
166
|
+
*
|
|
167
|
+
* Throws {@link SlashCommandParseError} when the body looks like a
|
|
168
|
+
* slash command but does not match the grammar (so the caller can
|
|
169
|
+
* surface the error to the user).
|
|
170
|
+
*
|
|
171
|
+
* @stable
|
|
172
|
+
*/
|
|
173
|
+
function parseActivationTrigger(raw) {
|
|
174
|
+
const trimmed = raw.trim();
|
|
175
|
+
if (trimmed.startsWith("/skill:") || /^\s*\/skill:/u.test(raw)) {
|
|
176
|
+
const parsed = parseSlashCommand(raw);
|
|
177
|
+
return Object.freeze({
|
|
178
|
+
name: parsed.name,
|
|
179
|
+
activationKind: "slash-command",
|
|
180
|
+
...parsed.args.length > 0 ? { args: parsed.args } : {}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (trimmed.length === 0) throw new SlashCommandParseError(raw, "activation trigger must be non-empty.");
|
|
184
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u.test(trimmed)) throw new SlashCommandParseError(raw, "auto-activation trigger must match /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.");
|
|
185
|
+
return Object.freeze({
|
|
186
|
+
name: trimmed,
|
|
187
|
+
activationKind: "auto"
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
export { createSkillRegistry, parseActivationTrigger, stampSkillTool, stampSkillToolFromMetadata };
|
|
193
|
+
//# sourceMappingURL=index.js.map
|