@onco-foundry/agent-template 0.6.2 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { defineAgent } from 'agent-lattice';
1
+ import { defineAgent, skill } from 'agent-lattice';
2
2
  import { createTypedAgentTool } from '@onco-foundry/agent-runtime';
3
3
  import { createTracer } from '@onco-foundry/trace-port';
4
4
  import { clampThinkingBudgetTokens } from './agent_template.js';
@@ -12,6 +12,9 @@ export const createAgentSpecExecutor = (declaration) => ({
12
12
  },
13
13
  task: declaration.task,
14
14
  async createSpec(input) {
15
+ if (input.skills?.some(item => Object.keys(item.files).some(path => path !== 'SKILL.md'))) {
16
+ throw new Error('通用执行器不挂载 Skill 资源文件;请使用提供只读工作区的执行器');
17
+ }
15
18
  const memberTools = await Promise.all(input.members.map(async (member) => createTypedAgentTool(await member.agent.createCapability({
16
19
  tracer: input.tracer,
17
20
  modelClient: input.modelClient,
@@ -30,6 +33,7 @@ export const createAgentSpecExecutor = (declaration) => ({
30
33
  ...(input.model.apiKey === undefined ? {} : { apiKey: input.model.apiKey }),
31
34
  modelClient: input.modelClient,
32
35
  systemPrompt: input.systemPrompt,
36
+ ...(input.skills === undefined ? {} : { skills: input.skills.map(item => skill({ name: item.name, description: item.description, instructions: item.instructions })) }),
33
37
  outputSchema: declaration.output,
34
38
  tools: [...input.tools, ...memberTools],
35
39
  ...(maxTokens === undefined ? {} : { maxTokens }),
@@ -4,9 +4,18 @@ import type { Capability } from '@onco-foundry/capability-registry';
4
4
  import type { CapabilityDelegation } from '@onco-foundry/agent-runtime';
5
5
  import type { Tracer } from '@onco-foundry/trace-port';
6
6
  import type { AgentTemplateCard, TemplateToolDefinition } from './agent_template.ts';
7
+ import type { ResolvedSkillPackage, SkillPackageReference, SkillPackageSummary } from './skill_packages.ts';
7
8
  export declare const agentResourceVersionSchema: z.ZodString;
9
+ declare const resourceReferenceSchema: z.ZodObject<{
10
+ name: z.ZodString;
11
+ resource: z.ZodString;
12
+ version: z.ZodString;
13
+ }, z.core.$strict>;
8
14
  export declare const agentVersionSchema: z.ZodObject<{
9
- schemaVersion: z.ZodLiteral<"agent-config-v1">;
15
+ schemaVersion: z.ZodEnum<{
16
+ "agent-config-v1": "agent-config-v1";
17
+ "agent-config-v2": "agent-config-v2";
18
+ }>;
10
19
  executor: z.ZodObject<{
11
20
  id: z.ZodString;
12
21
  version: z.ZodString;
@@ -44,6 +53,15 @@ export declare const agentVersionSchema: z.ZodObject<{
44
53
  id: z.ZodString;
45
54
  version: z.ZodString;
46
55
  }, z.core.$strict>>;
56
+ resources: z.ZodOptional<z.ZodArray<z.ZodObject<{
57
+ name: z.ZodString;
58
+ resource: z.ZodString;
59
+ version: z.ZodString;
60
+ }, z.core.$strict>>>;
61
+ skills: z.ZodOptional<z.ZodArray<z.ZodObject<{
62
+ id: z.ZodString;
63
+ version: z.ZodString;
64
+ }, z.core.$strict>>>;
47
65
  limits: z.ZodOptional<z.ZodObject<{
48
66
  maxTurns: z.ZodOptional<z.ZodNumber>;
49
67
  maxTokens: z.ZodOptional<z.ZodNumber>;
@@ -57,6 +75,15 @@ export type AgentVersionPromptReference = AgentVersion['prompt'];
57
75
  export type AgentVersionCommonPromptReference = AgentVersion['commonPrompts'][number];
58
76
  export type AgentVersionToolReference = AgentVersion['tools'][number];
59
77
  export type AgentVersionMemberReference = AgentVersion['members'][number];
78
+ export type AgentVersionResourceReference = z.infer<typeof resourceReferenceSchema>;
79
+ export type ResolvedAgentResource = AgentVersionResourceReference & {
80
+ checksum: string;
81
+ files: ReadonlyMap<string, string>;
82
+ };
83
+ export type AgentResourceSnapshot = AgentVersionResourceReference & {
84
+ checksum: string;
85
+ files: Record<string, string>;
86
+ };
60
87
  export type ResolvedAgentModel = AgentVersionModel & {
61
88
  apiKey?: string;
62
89
  };
@@ -74,6 +101,8 @@ export type ResolvedAgentVersion = {
74
101
  }>;
75
102
  tools: AgentVersionToolReference[];
76
103
  members: AgentVersionMemberReference[];
104
+ resources?: AgentResourceSnapshot[];
105
+ skills?: SkillPackageSummary[];
77
106
  limits?: AgentVersion['limits'];
78
107
  };
79
108
  export type ResolvedAgentSnapshot = {
@@ -102,6 +131,8 @@ export type ResolvedAgent = {
102
131
  version: string;
103
132
  };
104
133
  readSnapshot(): ResolvedAgentSnapshot;
134
+ readResources(): ResolvedAgentResource[];
135
+ readSkills(): ResolvedSkillPackage[];
105
136
  };
106
137
  export type ResolvedAgentMember = {
107
138
  id: string;
@@ -114,8 +145,10 @@ export type AgentExecutorSpecInput<C> = {
114
145
  model: ResolvedAgentModel;
115
146
  systemPrompt: string;
116
147
  tools: TemplateToolDefinition[];
148
+ resources: ResolvedAgentResource[];
117
149
  members: ResolvedAgentMember[];
118
150
  context: C;
151
+ skills?: ResolvedSkillPackage[];
119
152
  tracer?: Tracer;
120
153
  modelClient?: ModelClient;
121
154
  submitOutputEndTurn?: boolean;
@@ -140,8 +173,12 @@ export type AgentVersionPorts<C = unknown> = {
140
173
  readCommonPrompt(reference: AgentVersionCommonPromptReference, agentId: string): Promise<{
141
174
  content: string;
142
175
  }>;
176
+ readResource?(reference: AgentVersionResourceReference): Promise<{
177
+ files: ReadonlyMap<string, string>;
178
+ }>;
143
179
  createTool(reference: AgentVersionToolReference, context: C): TemplateToolDefinition | Promise<TemplateToolDefinition>;
144
180
  resolveExecutor(reference: AgentVersionExecutorReference): AgentVersionExecutor<C>;
181
+ readSkill?(reference: SkillPackageReference): Promise<ResolvedSkillPackage>;
145
182
  };
146
183
  export type ResolveAgentVersionInput<C = unknown> = {
147
184
  agentId: string;
@@ -150,3 +187,4 @@ export type ResolveAgentVersionInput<C = unknown> = {
150
187
  promptVersion?: string;
151
188
  memberPromptVersions?: Record<string, string>;
152
189
  };
190
+ export {};
@@ -26,8 +26,13 @@ const memberReferenceSchema = z.object({
26
26
  id: z.string().min(1),
27
27
  version: agentResourceVersionSchema,
28
28
  }).strict();
29
+ const resourceReferenceSchema = z.object({
30
+ name: z.string().min(1),
31
+ resource: z.string().min(1),
32
+ version: agentResourceVersionSchema,
33
+ }).strict();
29
34
  export const agentVersionSchema = z.object({
30
- schemaVersion: z.literal('agent-config-v1'),
35
+ schemaVersion: z.enum(['agent-config-v1', 'agent-config-v2']),
31
36
  executor: executorReferenceSchema,
32
37
  model: z.object({
33
38
  configVersion: z.string().min(1),
@@ -41,9 +46,16 @@ export const agentVersionSchema = z.object({
41
46
  commonPrompts: z.array(commonPromptReferenceSchema),
42
47
  tools: z.array(localToolReferenceSchema),
43
48
  members: z.array(memberReferenceSchema),
49
+ resources: z.array(resourceReferenceSchema).refine(items => new Set(items.map(item => item.name)).size === items.length, '业务资源名称不能重复').optional(),
50
+ skills: z.array(z.object({ id: z.string().regex(/^[a-z][a-z0-9-]{0,79}$/), version: agentResourceVersionSchema }).strict()).max(32).optional(),
44
51
  limits: z.object({
45
52
  maxTurns: z.number().int().positive().optional(),
46
53
  maxTokens: z.number().int().min(2).optional(),
47
54
  thinkingBudgetTokens: z.number().int().positive().optional(),
48
55
  }).strict().optional(),
49
- }).strict();
56
+ }).strict().superRefine((value, ctx) => {
57
+ if (value.schemaVersion === 'agent-config-v1' && value.skills !== undefined)
58
+ ctx.addIssue({ code: 'custom', path: ['skills'], message: 'Skill 引用需要 agent-config-v2' });
59
+ if (value.schemaVersion === 'agent-config-v2' && value.skills === undefined)
60
+ ctx.addIssue({ code: 'custom', path: ['skills'], message: 'agent-config-v2 必须声明 skills' });
61
+ });
@@ -39,8 +39,12 @@ export const createAgentVersionResolver = (ports) => {
39
39
  prompt: { ...record.prompt },
40
40
  systemPromptChecksum: record.systemPromptChecksum,
41
41
  commonPrompts: record.commonPrompts.map(({ content: _content, ...part }) => ({ ...part })),
42
+ ...(record.resources.length ? { resources: record.resources.map(({ files, ...reference }) => ({
43
+ ...reference, files: Object.fromEntries([...files].map(([name, content]) => [name, checksum(content)])),
44
+ })) } : {}),
42
45
  tools: record.config.tools.map(tool => ({ ...tool })),
43
46
  members: record.config.members.map(member => ({ ...member })),
47
+ ...(record.skills === undefined ? {} : { skills: record.skills.map(({ id, version, name, description, checksum }) => ({ id, version, name, description, checksum })) }),
44
48
  ...(record.config.limits === undefined ? {} : { limits: { ...record.config.limits } }),
45
49
  };
46
50
  for (const member of record.config.members)
@@ -85,6 +89,23 @@ export const createAgentVersionResolver = (ports) => {
85
89
  try {
86
90
  const executor = ports.resolveExecutor(loaded.config.executor);
87
91
  executor.validate?.({ agentId, config: loaded.config });
92
+ const skills = loaded.config.skills === undefined ? undefined : [];
93
+ const skillNames = new Set();
94
+ const skillIds = new Set();
95
+ for (const reference of loaded.config.skills ?? []) {
96
+ if (!ports.readSkill)
97
+ throw new AppError('当前宿主未提供 Skill 读取端口');
98
+ if (skillIds.has(reference.id))
99
+ throw new AppError(`重复的 Skill 引用:${reference.id}`);
100
+ const skill = await ports.readSkill(reference);
101
+ if (skill.id !== reference.id || skill.version !== reference.version || !/^[a-f0-9]{64}$/.test(skill.checksum))
102
+ throw new AppError(`Skill 解析结果与精确引用不符:${reference.id}`);
103
+ if (skillNames.has(skill.name))
104
+ throw new AppError(`Skill 名称冲突:${skill.name}`);
105
+ skillIds.add(reference.id);
106
+ skillNames.add(skill.name);
107
+ skills.push(structuredClone(skill));
108
+ }
88
109
  const promptVersion = agentId === input.agentId
89
110
  ? input.promptVersion
90
111
  : input.memberPromptVersions?.[agentId];
@@ -100,6 +121,15 @@ export const createAgentVersionResolver = (ports) => {
100
121
  const { content } = await ports.readCommonPrompt(reference, agentId);
101
122
  return { ...reference, content, checksum: checksum(content) };
102
123
  }));
124
+ const resources = await Promise.all((loaded.config.resources ?? []).map(async (reference) => {
125
+ if (!ports.readResource)
126
+ throw new AppError(`未配置业务资源读取端口:${reference.name}`);
127
+ const resource = await ports.readResource(reference);
128
+ const files = new Map([...resource.files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
129
+ if (!files.size)
130
+ throw new AppError(`业务资源没有文件:${reference.resource}@${reference.version}`);
131
+ return { ...reference, files, checksum: checksum(JSON.stringify([...files])) };
132
+ }));
103
133
  const ordered = [...commonPrompts].sort((a, b) => a.placement === b.placement
104
134
  ? a.order - b.order || a.id.localeCompare(b.id)
105
135
  : a.placement === 'before' ? -1 : 1);
@@ -119,8 +149,10 @@ export const createAgentVersionResolver = (ports) => {
119
149
  model: { ...loaded.config.model, ...modelCredential },
120
150
  prompt: { ...promptReference, checksum: checksum(prompt.content) },
121
151
  commonPrompts,
152
+ resources,
122
153
  systemPrompt,
123
154
  systemPromptChecksum: checksum(systemPrompt),
155
+ ...(skills === undefined ? {} : { skills }),
124
156
  };
125
157
  records.set(agentId, record);
126
158
  const createSpec = async (options = {}) => {
@@ -135,7 +167,9 @@ export const createAgentVersionResolver = (ports) => {
135
167
  config: record.config,
136
168
  model: record.model,
137
169
  systemPrompt: record.systemPrompt,
170
+ ...(record.skills === undefined ? {} : { skills: structuredClone(record.skills) }),
138
171
  tools,
172
+ resources: structuredClone(record.resources),
139
173
  members: await Promise.all(record.config.members.map(async (member) => ({
140
174
  id: member.id,
141
175
  version: member.version,
@@ -172,6 +206,8 @@ export const createAgentVersionResolver = (ports) => {
172
206
  },
173
207
  readSystemPrompt: () => ({ content: record.systemPrompt, version: record.prompt.version }),
174
208
  readSnapshot: () => structuredClone(snapshotFor(agentId)),
209
+ readResources: () => structuredClone(record.resources),
210
+ readSkills: () => structuredClone(record.skills ?? []),
175
211
  };
176
212
  record.agent = agent;
177
213
  for (const member of record.config.members)
@@ -1,5 +1,6 @@
1
1
  import type { TemplateToolDefinition } from './agent_template.ts';
2
- import type { AgentVersion, AgentVersionCommonPromptReference, AgentVersionExecutor, AgentVersionPorts, AgentVersionPromptReference } from './agent_version.ts';
2
+ import { type SkillPackageFiles, type SkillPackageReference } from './skill_packages.ts';
3
+ import type { AgentVersion, AgentVersionCommonPromptReference, AgentVersionExecutor, AgentVersionPorts, AgentVersionPromptReference, AgentVersionResourceReference } from './agent_version.ts';
3
4
  export declare const createFakeAgentVersionPorts: <C>(options: {
4
5
  executors: Record<string, AgentVersionExecutor<C>>;
5
6
  modelApiKey?: string;
@@ -7,6 +8,8 @@ export declare const createFakeAgentVersionPorts: <C>(options: {
7
8
  }) => AgentVersionPorts<C> & {
8
9
  writeAgentVersion(agentId: string, version: string, config: AgentVersion, activate?: boolean): Promise<void>;
9
10
  activateAgentVersion(agentId: string, version: string): Promise<void>;
11
+ setResource(reference: AgentVersionResourceReference, files: Record<string, string>): void;
10
12
  setPrompt(reference: AgentVersionPromptReference, content: string): void;
11
13
  setCommonPrompt(reference: AgentVersionCommonPromptReference, content: string): void;
14
+ writeSkill(reference: SkillPackageReference, files: SkillPackageFiles): Promise<void>;
12
15
  };
@@ -1,5 +1,6 @@
1
1
  import { AppError } from '@onco-foundry/errors';
2
2
  import { activateVersion, createFakeVersionedResourceStore, createVersionedResource, } from '@onco-foundry/resource-versioning';
3
+ import { createSkillPackages } from './skill_packages.js';
3
4
  const promptKey = (reference) => `${reference.resource}@${reference.version}`;
4
5
  const commonPromptKey = (reference) => `${reference.source}:${reference.id}@${reference.version}`;
5
6
  const executorKey = (reference) => `${reference.id}@${reference.version}`;
@@ -8,6 +9,13 @@ export const createFakeAgentVersionPorts = (options) => {
8
9
  const stores = new Map();
9
10
  const prompts = new Map();
10
11
  const commonPrompts = new Map();
12
+ const resources = new Map();
13
+ const skillStores = new Map();
14
+ const skills = createSkillPackages({ storeFor: id => {
15
+ if (!skillStores.has(id))
16
+ skillStores.set(id, createFakeVersionedResourceStore());
17
+ return skillStores.get(id);
18
+ } });
11
19
  const ports = {
12
20
  storeFor(agentId) {
13
21
  let store = stores.get(agentId);
@@ -17,6 +25,8 @@ export const createFakeAgentVersionPorts = (options) => {
17
25
  }
18
26
  return store;
19
27
  },
28
+ readSkill: reference => skills.read(reference),
29
+ async writeSkill(reference, files) { await skills.publish({ ...reference, files }); },
20
30
  resolveModel: async () => options.modelApiKey === undefined ? {} : { apiKey: options.modelApiKey },
21
31
  readPrompt: async (reference) => {
22
32
  const content = prompts.get(promptKey(reference));
@@ -30,6 +40,13 @@ export const createFakeAgentVersionPorts = (options) => {
30
40
  throw new AppError(`fake 共同 prompt 不存在:${commonPromptKey(reference)}`);
31
41
  return { content };
32
42
  },
43
+ readResource: async (reference) => {
44
+ const files = resources.get(promptKey(reference));
45
+ if (!files)
46
+ throw new AppError(`fake 业务资源不存在:${promptKey(reference)}`);
47
+ return { files: new Map(Object.entries(files)) };
48
+ },
49
+ setResource(reference, files) { resources.set(promptKey(reference), { ...files }); },
33
50
  createTool(reference, context) {
34
51
  const create = options.tools?.[toolKey(reference)];
35
52
  if (create === undefined)
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { clampThinkingBudgetTokens, defineAgentTemplate, promptManifestSchema, type AgentTemplate, type AgentTemplateCard, type AgentTemplateDeclaration, type CreateTemplateAgentOptions, type PromptManifestAdapter, type TemplateToolDefinition, } from './agent_template.ts';
2
2
  export type { CapabilityDelegation } from '@onco-foundry/agent-runtime';
3
3
  export { createAgentSpecExecutor, type AgentSpecExecutorDeclaration } from './agent_spec_executor.ts';
4
- export { agentResourceVersionSchema, agentVersionSchema, type AgentVersion, type AgentVersionCommonPromptReference, type AgentVersionExecutor, type AgentVersionExecutorReference, type AgentVersionMemberReference, type AgentVersionModel, type AgentVersionPorts, type AgentVersionPromptReference, type AgentVersionToolReference, type CreateResolvedAgentSpecOptions, type ResolveAgentVersionInput, type ResolvedAgent, type ResolvedAgentMember, type ResolvedAgentModel, type ResolvedAgentSnapshot, type ResolvedAgentVersion, } from './agent_version.ts';
4
+ export { agentResourceVersionSchema, agentVersionSchema, type AgentVersion, type AgentVersionResourceReference, type AgentResourceSnapshot, type ResolvedAgentResource, type AgentVersionCommonPromptReference, type AgentVersionExecutor, type AgentVersionExecutorReference, type AgentVersionMemberReference, type AgentVersionModel, type AgentVersionPorts, type AgentVersionPromptReference, type AgentVersionToolReference, type CreateResolvedAgentSpecOptions, type ResolveAgentVersionInput, type ResolvedAgent, type ResolvedAgentMember, type ResolvedAgentModel, type ResolvedAgentSnapshot, type ResolvedAgentVersion, } from './agent_version.ts';
5
5
  export { createAgentVersionResolver } from './agent_version_resolver.ts';
6
6
  export { createFakeAgentVersionPorts } from './fake_agent_version_ports.ts';
7
+ export { createSkillPackages, type SkillPackageFiles, type SkillPackageReference, type SkillPackageSummary, type ResolvedSkillPackage } from './skill_packages.ts';
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { createAgentSpecExecutor } from './agent_spec_executor.js';
3
3
  export { agentResourceVersionSchema, agentVersionSchema, } from './agent_version.js';
4
4
  export { createAgentVersionResolver } from './agent_version_resolver.js';
5
5
  export { createFakeAgentVersionPorts } from './fake_agent_version_ports.js';
6
+ export { createSkillPackages } from './skill_packages.js';
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+ import { type VersionedResourceStore } from '@onco-foundry/resource-versioning';
3
+ declare const filesSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
4
+ encoding: z.ZodEnum<{
5
+ base64: "base64";
6
+ utf8: "utf8";
7
+ }>;
8
+ content: z.ZodString;
9
+ }, z.core.$strict>>;
10
+ export type SkillPackageFiles = z.infer<typeof filesSchema>;
11
+ export type SkillPackageReference = {
12
+ id: string;
13
+ version: string;
14
+ };
15
+ export type SkillPackageSummary = SkillPackageReference & {
16
+ name: string;
17
+ description: string;
18
+ checksum: string;
19
+ };
20
+ export type ResolvedSkillPackage = SkillPackageSummary & {
21
+ instructions: string;
22
+ files: SkillPackageFiles;
23
+ };
24
+ /** 文件是数据,不接收文件系统路径或链接;实际只读挂载由宿主运行器负责。 */
25
+ export declare const createSkillPackages: (deps: {
26
+ storeFor(id: string): VersionedResourceStore;
27
+ }) => {
28
+ publish(input: SkillPackageReference & {
29
+ files: SkillPackageFiles;
30
+ }): Promise<ResolvedSkillPackage>;
31
+ read: (reference: SkillPackageReference) => Promise<ResolvedSkillPackage>;
32
+ };
33
+ export {};
@@ -0,0 +1,74 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { parse } from 'yaml';
3
+ import { z } from 'zod';
4
+ import { createVersionedResource, loadActiveVersionedResource } from '@onco-foundry/resource-versioning';
5
+ import { AppError } from '@onco-foundry/errors';
6
+ const idSchema = z.string().regex(/^[a-z][a-z0-9-]{0,79}$/);
7
+ const versionSchema = z.string().regex(/^v[1-9]\d{0,8}$/);
8
+ const fileSchema = z.object({ encoding: z.enum(['utf8', 'base64']), content: z.string().max(24 * 1024 * 1024) }).strict();
9
+ const filesSchema = z.record(z.string(), fileSchema);
10
+ const hash = (value) => createHash('sha256').update(value).digest('hex');
11
+ /** 文件是数据,不接收文件系统路径或链接;实际只读挂载由宿主运行器负责。 */
12
+ export const createSkillPackages = (deps) => {
13
+ const inspect = (input) => {
14
+ const files = filesSchema.parse(input);
15
+ const paths = Object.keys(files).sort();
16
+ if (paths.length === 0 || paths.length > 128)
17
+ throw new AppError('技能包文件数必须为 1 到 128');
18
+ let size = 0;
19
+ for (const path of paths) {
20
+ const parts = path.split('/');
21
+ if (path.length > 240 || parts.some(part => !part || part === '.' || part === '..' || /[\\:\x00-\x1f\x7f]/.test(part))) {
22
+ throw new AppError(`技能包文件路径非法:${path}`);
23
+ }
24
+ if (parts.at(-1) === 'SKILL.md' && path !== 'SKILL.md')
25
+ throw new AppError('技能包只能在根目录包含一个 SKILL.md');
26
+ for (let count = 1; count < parts.length; count++) {
27
+ if (Object.hasOwn(files, parts.slice(0, count).join('/')))
28
+ throw new AppError(`技能包文件与目录冲突:${path}`);
29
+ }
30
+ const file = files[path];
31
+ const bytes = Buffer.from(file.content, file.encoding === 'utf8' ? 'utf8' : 'base64');
32
+ if (file.encoding === 'base64' && bytes.toString('base64') !== file.content)
33
+ throw new AppError(`技能包 base64 非法:${path}`);
34
+ size += bytes.byteLength;
35
+ if (size > 16 * 1024 * 1024)
36
+ throw new AppError('技能包解码后不能超过 16 MiB');
37
+ }
38
+ const skill = files['SKILL.md'];
39
+ if (!skill || skill.encoding !== 'utf8' || Buffer.byteLength(skill.content) > 100_000)
40
+ throw new AppError('技能包必须包含 UTF-8 SKILL.md,且不能超过 100000 字节');
41
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]+)$/.exec(skill.content);
42
+ if (!match)
43
+ throw new AppError('SKILL.md 必须包含 YAML frontmatter 和正文');
44
+ const metadata = z.object({ name: idSchema, description: z.string().trim().min(1).max(2048) }).passthrough().parse(parse(match[1], { maxAliasCount: 0, uniqueKeys: true }));
45
+ const instructions = match[2].trim();
46
+ if (!instructions)
47
+ throw new AppError('SKILL.md 正文不能为空');
48
+ return { name: metadata.name, description: metadata.description, instructions, files: Object.fromEntries(paths.map(path => [path, files[path]])) };
49
+ };
50
+ const read = async (reference) => {
51
+ idSchema.parse(reference.id);
52
+ versionSchema.parse(reference.version);
53
+ const loaded = await loadActiveVersionedResource({ store: deps.storeFor(reference.id), versionOverride: reference.version });
54
+ const content = loaded.files.get('skill.json');
55
+ if (content === undefined || Buffer.byteLength(content) > 24 * 1024 * 1024)
56
+ throw new AppError('技能包缺少 skill.json 或内容过大');
57
+ const bundle = z.object({ schemaVersion: z.literal('skill-package-v1'), files: filesSchema }).strict().parse(JSON.parse(content));
58
+ return { ...reference, ...inspect(bundle.files), checksum: hash(content) };
59
+ };
60
+ return {
61
+ async publish(input) {
62
+ idSchema.parse(input.id);
63
+ versionSchema.parse(input.version);
64
+ const inspected = inspect(input.files);
65
+ const content = JSON.stringify({ schemaVersion: 'skill-package-v1', files: inspected.files });
66
+ if (Buffer.byteLength(content) > 24 * 1024 * 1024)
67
+ throw new AppError('技能包编码后不能超过 24 MiB');
68
+ await createVersionedResource({ store: deps.storeFor(input.id), version: input.version,
69
+ files: { 'skill.json': content } });
70
+ return await read({ id: input.id, version: input.version });
71
+ },
72
+ read,
73
+ };
74
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onco-foundry/agent-template",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -13,12 +13,13 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "agent-lattice": "0.26.0",
16
+ "yaml": "^2.9.1",
16
17
  "zod": "^4.4.3",
17
18
  "@onco-foundry/agent-runtime": "0.3.0",
18
- "@onco-foundry/capability-registry": "0.1.1",
19
- "@onco-foundry/resource-versioning": "0.3.0",
20
19
  "@onco-foundry/trace-port": "0.3.3",
21
- "@onco-foundry/errors": "0.1.1"
20
+ "@onco-foundry/resource-versioning": "0.3.0",
21
+ "@onco-foundry/errors": "0.1.1",
22
+ "@onco-foundry/capability-registry": "0.1.1"
22
23
  },
23
24
  "publishConfig": {
24
25
  "access": "public"