拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Mastra Agent Builder 存储技能(Stored Skills)CRUD、可见性与发布冒烟测试实战指南

Mastra Agent Builder 存储技能Stored SkillsCRUD、可见性与发布冒烟测试实战指南【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra导读本文基于 Mastra 仓库中 Agent Builder 冒烟测试技能builder-smoke-test的 Skills 参考文档references/skills.md整理而成系统讲解如何对/stored/skills这一组 REST 端点做端到端验证技能实体的增删改查、分页列表、可见性public/private过滤、基于磁盘目录的发布publish、文件系统持久化以及发布时 frontmatter 的权威性行为。读完本文你将掌握一套可直接复制的 curl 冒烟测试流程并能对照 stored-skills.ts 与 stored-skills.ts 处理器 的源码理解每个断言背后的实现原理可用于 Agent Builder 功能分支feature branch的回归验证。测试前提认证模式与角色门控在开始任何curl之前必须先厘清三组测试契约它们决定了哪些步骤可以执行、哪些步骤应预期 403。可见性只在开启认证时生效Visibility is auth-on-only.当以--auth off运行服务时服务端会在每次创建 / PATCH 时强制visibility: public且authorId: null。这并非临时约定而是 CREATE_STORED_SKILL_ROUTE 处理器 中的显式逻辑// Force authorId from the authenticated caller; ignore any body-provided value. // No owner always public (no auth / no user context). const authorId getCallerAuthorId(requestContext) ?? undefined; const visibility: private | public authorId ? (bodyVisibility ?? private) : public;也就是说有调用者身份auth on时客户端可传visibility缺省为private没有身份auth off时一律写入public且无authorId。因此所有关于可见性visibility的断言都应放在 references/auth.md 覆盖的--auth on场景下执行。分页是 0 索引的列表接口的分页语义为page0即第一页。在 common.ts 的createPagePaginationSchema中page参数默认值为0且要求非负整数z.coerce.number().int().min(0)非法输入如page-1、perPage2.5会在请求边界直接返回 400。能力门控Capability gate步骤 1、3、5、7、8 需要stored-skills:write权限。脚手架scaffold将这一权限授予 owner、admin 与 memberviewer 没有。因此以--role viewer运行时这些步骤应标记为n/a — role lacks stored-skills:write只执行只读侧步骤2、4 的 GET、6、9。对技能而言member 比 admin 权限更窄stored-skills:publish与stored-skills:delete并未授予 member只有 owner/admin 拥有。以--role member运行时任何POST /stored/skills/:id/publish或DELETE /stored/skills/:id都应返回403 Missing required permission: stored-skills:{publish,delete}——把该 403 视为 member 的预期结果不要上报为缺陷。member 对他人作者的记录执行 PATCH 时返回404 Not Found所有权过滤在处理函数之前就隐藏了该行详见 references/permissions.md。端点总览EndpointMethodPurpose/stored/skillsPOSTCreate a stored skill/stored/skillsGETList stored skills (paginated)/stored/skills/:idGETRead a single stored skill/stored/skills/:idPATCHUpdate fields on a stored skill/stored/skills/:idDELETEDelete a stored skill/stored/skills/:id/publishPOSTPublish a skill from a filesystem path/stored/skills/:id/favoritePUTFavorite (见 references/favorites.md)/stored/skills/:id/favoriteDELETEUnfavorite (见 references/favorites.md)完整的请求 / 响应 schema 定义位于 packages/server/src/server/schemas/stored-skills.ts该文件是请求与响应形状的权威来源source of truth。关键 schema 速览createStoredSkillBodySchema由snapshotConfigSchema含必填的name、description、instructions以及可选的license、compatibility、source、references、scripts、assets、files、metadata与可选的id、authorId、visibility合并而成updateStoredSkillBodySchema所有字段均.partial()支持单字段局部更新publishStoredSkillBodySchema仅含必填的skillPathlistStoredSkillsQuerySchemacreatePagePaginationSchema(100)perPage默认 100并支持orderBycreatedAt/updatedAt×ASC/DESC、statusdraft/published/archived、authorId、visibility、metadata、favoritedOnly、pinFavoritedFor过滤storedSkillSchema响应id、status、activeVersionId、authorId、visibility、favoriteCount、isFavorited、createdAt、updatedAt及全部配置字段其中files: FileNode[]为树形结构name、type: file|folder、content?、children?。九步冒烟测试完整 CRUD 与发布流程以下所有curl均假设已按 SKILL.md 的约定导出环境变量export BASEhttp://localhost:4111/api # 在 --auth on 模式下还需要先导出会话 cookie export COOKIEwos-session…所有写操作在--auth on下需追加-H Cookie: $COOKIE。步骤 1创建技能Createcurl -s -X POST $BASE/stored/skills \ -H Content-Type: application/json \ -d { name: Smoke Test Skill, description: A test skill created during smoke testing, instructions: Skill instructions for the smoke test. } | jq .验证点返回 200 与创建的技能对象name与description与请求一致id存在记录为SKILL_IDid实现细节name、description、instructions均为 schema 必填项缺任意一个都会返回400 Invalid input: expected string, received undefined。此外CREATE_STORED_SKILL_ROUTE 中id缺省时由name经toSlug(name)派生若该 id 已存在返回409 Skill with id ${id} already exists可据此设计“重复名称处理”的可选验证项。步骤 2获取技能Getcurl -s $BASE/stored/skills/$SKILL_ID | jq .返回 200name、description、instructions与创建时一致createdAt与updatedAt为 ISO 时间戳实现细节GET 走 GET_STORED_SKILL_ROUTE内部调用skillStore.getByIdResolved(storedSkillId)解析出当前activeVersionId对应的配置快照。createdAt/updatedAt在响应 schema 中由z.coerce.date()约束。步骤 3列表技能List分页curl -s $BASE/stored/skills?page0perPage50 | jq { total, page, perPage, count: (.skills | length) }total 1$SKILL_ID出现在skills数组中实现细节列表处理器 LIST_STORED_SKILLS_ROUTE 先按resolveAuthorFilter确定可见性过滤策略unrestricted/exact/ownedOrPublic/publicOnly再调用skillStore.listResolved取数最后在内存中后置过滤所有权与可见性规则因为存储适配器只能对authorId做等值过滤。若启用了 favorites 特性返回的每条记录还会带上isFavorited标注。步骤 4更新技能元数据Updatecurl -s -o /tmp/skill-patch.json -w %{http_code}\n -X PATCH $BASE/stored/skills/$SKILL_ID \ -H Content-Type: application/json \ -d { name: Updated Smoke Skill, description: A test skill created during smoke testing, instructions: Skill instructions for the smoke test. } cat /tmp/skill-patch.json | jq .返回 200name反映更新后的值description与instructions保持不变单字段局部 PATCH仅传一个字段如只传{description: …}同样返回 200且只改动该字段。如果局部 PATCH 返回非 2xx请把确切的状态码与响应体记入运行报告——这属于回归regression。实现细节UPDATE_STORED_SKILL_ROUTE 的实现有一个值得注意的细节存储层用field in updates来判断配置是否变更、是否触发新版本创建因此处理器只把客户端实际发送的字段放入 update 对象绝不转发undefined键——否则会误触发版本创建并把undefined传进数据库驱动。更新前还会调用assertWriteAccess所有权检查member 对他人记录执行 PATCH 时因所有权过滤返回 404。步骤 5创建第二个技能curl -s -X POST $BASE/stored/skills \ -H Content-Type: application/json \ -d { name: Second Smoke Skill, description: Another skill for smoke testing, instructions: Second skill instructions. } | jq { id }返回 200 与id记录为SKILL_ID_2id步骤 6列表验证两个技能都在curl -s $BASE/stored/skills?page0perPage50 | jq [.skills[].id] | map(select(. $a or . $b)) | length \ --arg a $SKILL_ID --arg b $SKILL_ID_2返回2步骤 7发布技能PublishPOST /stored/skills/:id/publish要求skillPath指向一个包含SKILL.md的服务端目录。服务端会应用路径穿越防护path-traversal guard其定义见 stored-skills.ts 中的publishStoredSkillBodySchema与发布处理器 PUBLISH_STORED_SKILL_ROUTE。# 空 body —— 应通过校验失败 curl -s -o /tmp/publish-empty.json -w %{http_code}\n -X POST $BASE/stored/skills/$SKILL_ID/publish \ -H Content-Type: application/json -d {} cat /tmp/publish-empty.json | jq .返回 400并带skillPath的校验错误# 为脚手架项目提供一个 in-tree 的 skillPath。 # 允许的根目录由服务端 cwd 解析即脚手架项目的 src/mastra/public 目录。 ALLOWED_BASE${SKILLS_BASE_DIR:-$HOME/mastra-builder-smoke-tests/builder-smoke/src/mastra/public} ls $ALLOWED_BASE 2/dev/null SKILL_PATH$ALLOWED_BASE/skills/$SKILL_ID curl -s -o /tmp/publish.json -w %{http_code}\n -X POST $BASE/stored/skills/$SKILL_ID/publish \ -H Content-Type: application/json \ -d {\skillPath\: \$SKILL_PATH\} cat /tmp/publish.json | jq .若目录存在且包含有效的SKILL.md返回 200 并持久化记录注意可能新增的activeVersionId若目录不存在记录实际状态码与消息不要臆测具体状态码实现细节值得深读发布处理器在写入前做了三层防护与校验路径穿越防护resolvedPath必须以allowedBase由SKILLS_BASE_DIR或process.cwd()解析为前缀否则返回 400skillPath must be within the allowed directory: …目录存在性校验fs.stat确认skillPath是目录非目录返回 400不存在ENOENT时返回带上下文的 400而不是裸 500SKILL.md存在性校验目录内缺少SKILL.md时返回 400skillPath is missing SKILL.md: …。校验通过后处理器通过mastra/core/workspace的LocalSkillSource与publishSkillFromSource把目录快照写入内容寻址的 blob 存储生成带树形清单tree manifest的新版本把status置为published、更新files树并让activeVersionId指向最新版本。Frontmatter 在发布时具有权威性authoritative on publish。发布流程读取磁盘SKILL.md的 frontmatter并用它重写存储记录的name/description/instructions。如果你先 PATCH 了这些字段再发布已打补丁的值会被 frontmatter 覆盖。此外随后 GET 返回的instructions是从文件内容中去掉---代码块后的正文frontmatter 已被剥离。步骤 8删除技能清理curl -s -o /dev/null -w %{http_code}\n -X DELETE $BASE/stored/skills/$SKILL_ID # → 200 curl -s -o /dev/null -w %{http_code}\n -X DELETE $BASE/stored/skills/$SKILL_ID_2 # → 200 curl -s -o /dev/null -w %{http_code}\n $BASE/stored/skills/$SKILL_ID # → 404实现细节DELETE_STORED_SKILL_ROUTE 在删除前执行assertWriteAccessaction: deletemember 会得到 403删除成功后还会级联清理该技能的所有 favorite 记录deleteFavoritesForEntity且级联失败不会中止主删除只会记录一条 warning 日志。再次 GET 已删除技能返回 404。步骤 9非所有者可见性过滤需先运行seed-multi-user.sh此步骤要求--auth on且必须先用种子脚本插入非所有者数据。seed-multi-user.sh 会向脚手架项目的 libsql 数据库直接写入两条属于user_seed_other的技能smoke-seed-public-skillpublic与smoke-seed-private-skillprivate。当前登录用户不是这两条记录的所有者。# 公开的种子技能当前用户应可见 curl -s -H $SESSION $BASE/stored/skills/smoke-seed-public-skill | jq {id, visibility, authorId} # 私有的种子技能应被过滤404 curl -s -o /dev/null -w %{http_code}\n -H $SESSION $BASE/stored/skills/smoke-seed-private-skill公开种子200visibility: publicauthorId: user_seed_other私有种子404非所有者不能读取他人的私有技能若私有种子返回 200真实缺陷以产品问题上报并附带响应体列表端点应用同样的过滤规则curl -s -H $SESSION $BASE/stored/skills?perPage100 | jq .skills | map(.id) | sortsmoke-seed-public-skill出现在列表中smoke-seed-private-skill不出现在列表中除非调用者是 owner 等价角色 / 拥有*的 admin关于种子脚本的补充脚本直接通过sqlite3CLI 操作$PROJECT_DIR/src/mastra/public/mastra.db无需 Node 依赖且幂等——重复运行会先删除再重新插入两条种子行。前提是服务至少启动过一次libsql 初始化mastra_skills表。种子数据为运行“他人所有技能”相关流程Library Copy、非所有者只读视图、私有技能可见性提供了不依赖第二个 WorkOS 账号的测试基础。文件系统持久化#16000发布流程是把SKILL.md以及可选的references/、scripts/、assets/子目录真正落到磁盘的路径。验证方式是在成功发布前后分别检查文件系统状态。GET 响应中的关键字段是files: FileNode[]每个节点含name、type、content?、children?其 schema 定义见 stored-skills.ts 中的storedSkillSchema与fileNodeSchema。F1纯创建时的 files 字段curl -s $BASE/stored/skills/$SKILL_ID | jq .files记录该值。预期纯创建plain create时files缺失或为空——发布之前不会做文件系统物化。F2发布后的 files 字段适用时对步骤 7 中成功发布的任何技能curl -s $BASE/stored/skills/$SKILL_ID | jq .filesfiles已填充且包含SKILL.md条目name: SKILL.md, type: fileinstructions不会在文件节点的content中重复出现实现细节发布处理器会把publishSkillFromSource返回的tree版本树形清单与files编辑器展示用的嵌套目录结构一并写入存储若files缺失编辑器中会渲染出空树。同时处理器会从快照中剔除undefined键存储层将“字段存在”视为“字段已变更”把undefined传下去会导致 libsql/pg 等适配器报undefined cannot be passed as argument之类错误。F3可见性翻转时的自动发布需已注册skillPath此步骤要求--auth on详见 references/auth.md。前置条件。可见性翻转只有在技能在磁盘上有源时才会自动发布——即该技能创建时带skillPath或此前某次POST /stored/skills/:id/publish已注册了路径。没有注册路径时visibility: public也会被接受200该行会以 public 出现在列表中但activeVersionId保持null、不会创建新版本。可见性与发布状态在设计上就是两个相互独立的字段。请对你在SKILLS_BASE_DIR下真实磁盘目录中创建的技能运行此步骤。如果对“纯创建”无skillPath的技能翻转可见性预期是 200 activeVersionId不变而不是新版本。对已注册skillPath的技能把visibility从private翻到public会创建新的 active 版本GET 时activeVersionId变化对未注册skillPath的技能执行同样的翻转返回 200 但activeVersionId保持null——这是预期契约不是缺陷两种情况下翻转过程都不出现 5xx设计说明这一点在 SKILL.md 的 Design decisions 中也有明确记录“Flipping a skillsvisibilityfromprivatetopublicdoes not auto-publish unless the skill has a registeredskillPath. Visibility and publication are independent fields by design.” 在仓库源码中可见性翻转只更新记录的visibility字段UPDATE_STORED_SKILL_ROUTE只有 publish 路由才会创建新版本并推进activeVersionId。Frontmatter 处理skills.sh 安装与库复制场景如果该技能是通过 skills.sh 安装的或从技能库library复制而来还应验证instructions不以---开头frontmatter 已在安装 / 复制时剥离metadata.origin.type为skills-sh或library-copy且带有sourceSkillId实现细节技能的来源信息以metadata.origin的形式持久化。stored-skills.ts 中的skillOriginSchema是判别联合discriminated unionskills-sh类型记录owner、repo、skillName、installedAtlibrary-copy类型记录sourceSkillId、sourceSkillName、可选的sourceAuthorId与copiedAt。readSkillOrigin()负责从 metadata blob 中读取并校验buildOriginMetadata()负责构造写入 create body 的 metadata 补丁。安装流程详见 references/registry.md。完整检查清单创建技能name、description、instructions均必填按 ID 获取技能以page0列表技能更新技能元数据全量 PATCH创建并列表第二个技能发布空 body 校验、in-treeskillPath行为删除返回 200随后 GET 返回 404发布前后检查files字段可选重复名称处理收尾与报告规范完成上述全部步骤后按 SKILL.md 的结果报告模板 记录日期、分支、commit、认证模式与各节状态✅/❌/⏭️。上报前请对每一条结论做二次确认形状不匹配类问题需粘贴实际 JSON 片段端点不一致类问题需在同一轮运行中重新 curl 两个端点RBAC 类问题需对照 references/permissions.md 的权限矩阵与“Design decisions”清单例如多角色共享*:read导致 infra/list/get 端点看起来“未加门禁”实属预期缺少端点类问题需先确认契约——若干流程本就是客户端在通用 CRUD 之上组合而成如技能复制 带metadata.origin的POST /stored/skills收藏 PUT/DELETE /stored/skills/:id/favorite。无法复现的结论一律丢弃确保最终报告中的每条断言都可被追溯。【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门