@lark-apaas/coding-steering 0.1.6-alpha.0 → 0.1.6-alpha.10

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.
Files changed (21) hide show
  1. package/README.md +11 -2
  2. package/package.json +1 -1
  3. package/steering/design-stack/skills/.gitkeep +0 -0
  4. package/steering/nestjs-react-fullstack/skills/.gitkeep +0 -0
  5. package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +174 -0
  6. package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +621 -0
  7. package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +505 -0
  8. package/steering/nestjs-react-fullstack/skills/authz-guide/references/runtime-role-controller-spec.md +203 -0
  9. package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +90 -0
  10. package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +216 -0
  11. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +405 -0
  12. package/steering/nestjs-react-fullstack/skills/devops-guide/SKILL.md +119 -0
  13. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +582 -0
  14. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +357 -0
  15. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +513 -0
  16. package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +118 -0
  17. package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +177 -0
  18. package/steering/nestjs-react-fullstack/skills/user-management-best-practices/SKILL.md +142 -0
  19. package/steering/nestjs-react-fullstack/{skills → skills_local}/code-fix/SKILL.md +5 -19
  20. package/steering/nestjs-react-fullstack/{skills → skills_local}/coding-guide/SKILL.md +26 -148
  21. package/steering/nestjs-react-fullstack/tech.md +21 -0
@@ -0,0 +1,582 @@
1
+ ---
2
+ name: plugin-guide
3
+ description: 飞书/AI 新版插件集成规范与 Plugin 链路指南。支持飞书多维表格/Base操作、发送飞书消息、创建飞书群组、AI智能生文、AI智能生图、AI图片理解等。Use when 需要:(1) 创建或管理 PluginInstance 插件实例,(2) 调用 capabilityClient/CapabilityService 生成插件调用代码,(3) 理解 Plugin、PluginInstance、PluginInstanceAIJson 三层关系,(4) 使用 get_plugin_ai_json 或 plugin_instance 工具。触发词:插件, plugin, 飞书消息, 飞书群组, 多维表格, AI生文, AI生图, 图片理解, capabilityClient, CapabilityService, pluginInstance
4
+ steering: true
5
+ steering-topic: plugin_guide
6
+ match-template-name: nestjs-react-fullstack
7
+ ---
8
+
9
+ # Plugin 集成指南
10
+
11
+ 飞书/AI 新版插件集成规范与 Plugin 链路指南。支持的能力包括:飞书多维表格/Base操作(插入记录、更新记录、删除记录、查询记录)、发送飞书消息、创建飞书群组、AI智能生文、AI智能生图、AI图片理解等。本文档介绍插件实例配置、运行时投影(get_plugin_ai_json)的用法,以及 Server/Client 侧的统一调用入口;具体业务能力需以实际 available_plugin_instances 与对应 plugin.ai.json 为准。
12
+
13
+ ## Quick Reference
14
+
15
+ | 操作 | 方式 |
16
+ |------|------|
17
+ | 创建/更新 PluginInstance | 调用 `plugin_instance` 工具(禁止手动改文件) |
18
+ | 获取运行时投影 | 调用 `get_plugin_ai_json(pluginInstanceId)` |
19
+ | Client 侧非流式调用 | `capabilityClient.load(id).call(actionKey, input)` |
20
+ | Client 侧流式调用 | `capabilityClient.load(id).callStream(actionKey, input)` |
21
+ | Server 侧调用(仅兜底) | `capabilityService.load(id).call(actionKey, input)` |
22
+ | capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
23
+ | CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
24
+ | CapabilityService 注入 | `@Inject() private readonly capabilityService: CapabilityService` |
25
+ | 配置存储位置 | `server/capabilities/<plugin_instance_id>.json` |
26
+
27
+ > **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。错误写法:`const dataloom = await getDataloom(); (dataloom as any).capability` — 这样写不会工作。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit'`。
28
+
29
+ ## Plugin 代码编写指南
30
+
31
+ 以下场景**必须**先读取 `references/plugin-coding-guide.md` 再编写代码:
32
+
33
+ - 编写 `capabilityClient` 或 `CapabilityService` 调用代码时(含导入路径、调用方式)
34
+ - 需要判断 Client 侧还是 Server 侧调用时
35
+ - 处理流式输出(`outputMode = stream`),包括多插件并行流式、单插件 JSON 流式解析
36
+ - 在 Server 侧进行 NestJS 注入或编排多个插件调用时
37
+ - 需要根据 `outputMode` 选择 `call()` 或 `callStream()` 时
38
+
39
+ ## 飞书多维表格(feishu-bitable)编码指南
40
+
41
+ 以下场景**必须**先读取 `references/table.md` 再编写代码:
42
+
43
+ - 编写飞书多维表格 CRUD 代码时(searchRecords / getRecord / batchAddRecords / batchUpdateRecords / deleteRecords)
44
+ - 需要了解 BizType 字段类型、读写格式差异时
45
+ - 构建搜索过滤条件(filter / conditions)时
46
+ - 使用多维表格相关 UI 组件(UserDisplay、Hyperlink、Select 等)时
47
+
48
+ ## Plugin 链式调用(Plugin Chain)
49
+
50
+ 很多业务场景需要多个插件串联完成,**禁止用正则/字符串解析替代 AI 插件做结构化输出处理**(包括提取和生成场景)。
51
+
52
+ ### 插件能力分类
53
+
54
+ | 类别 | 插件 | 输入→输出 |
55
+ |------|------|----------|
56
+ | **内容提取** | `ai-doc-parser` | 文档(PDF/DOC/PPTX/XLSX/CSV等9种)→纯文本 |
57
+ | **内容提取** | `ai-speech-to-text` | 音频→纯文本 |
58
+ | **内容提取** | `ai-image-understanding` | 图片→文本描述(流式,适合理解/问答) |
59
+ | **结构化提取** | `ai-text-to-json` | 文本→结构化 JSON(最多20字段) |
60
+ | **结构化提取** | `ai-image-to-json` | 图片→结构化 JSON(最多20字段,**单步直达**) |
61
+ | **结构化提取** | `ai-categorization` | 文本→分类标签 |
62
+ | **内容生成** | `ai-text-generate` | 提示词→文本(流式) |
63
+ | **内容生成** | `ai-text-to-image` | 文字描述→图片 |
64
+ | **内容生成** | `ai-text-summary` | 长文本→摘要(流式) |
65
+ | **内容生成** | `ai-translate` | 文本→翻译(12种语言,流式) |
66
+ | **内容生成** | `ai-search-summary` | 搜索词→网页摘要(流式) |
67
+ | **内容生成** | `ai-speech-synthesis` | 文本→语音(44+音色) |
68
+ | **图片处理** | `ai-image-matting` | 图片→抠图/去背景/去水印 |
69
+ | **图片处理** | `ai-background-replace` | 主体图+背景→合成图 |
70
+ | **图片处理** | `ai-image-to-image` | 参考图(1-5张)+描述→编辑/风格转换 |
71
+ | **图片处理** | `ai-image-compare` | 两张图→对比分析(流式) |
72
+ | **外部服务** | `feishu-bitable` | CRUD 飞书多维表格(5个Action) |
73
+ | **外部服务** | `send-feishu-message` | 发送飞书卡片消息 |
74
+ | **外部服务** | `feishu-group-create` | 创建飞书群组 |
75
+
76
+ ### 决策树:选择单步还是链式
77
+
78
+ ```
79
+ 输入是什么?
80
+ ├── 文档文件 → 必须先用 ai-doc-parser 提取文本,再根据目标选择下游插件:
81
+ │ ├── 需要结构化数据 → ai-doc-parser → ai-text-to-json (2步链)
82
+ │ ├── 需要摘要 → ai-doc-parser → ai-text-summary
83
+ │ ├── 需要翻译 → ai-doc-parser → ai-translate
84
+ │ ├── 需要分类 → ai-doc-parser → ai-categorization
85
+ │ └── 仅需原文 → ai-doc-parser(单步)
86
+ ├── 图片 → ⚠️ 注意选择正确的插件:
87
+ │ ├── 提取结构化数据(发票/名片/证件等)→ ai-image-to-json(⭐ 单步直达!)
88
+ │ ├── 理解内容后提取结构化数据 → ai-image-understanding → ai-text-to-json(2步链)
89
+ │ ├── 抠图后换背景 → ai-image-matting → ai-background-replace(2步链)
90
+ │ └── 理解/问答/编辑/对比 → 对应单插件即可
91
+ ├── 音频
92
+ │ ├── 需要结构化数据 → ai-speech-to-text → ai-text-to-json(2步链)
93
+ │ └── 仅需文字 → ai-speech-to-text(单步)
94
+ └── 纯文本
95
+ ├── 需要结构化数据 → ai-text-to-json(⭐ 单步直达!)
96
+ └── 摘要/翻译/分类/生成
97
+ ├── 输出包含多个独立字段(标题+正文+评分等)
98
+ │ → 拆成多个独立插件并行调用(⭐ 优先)或用 ai-text-to-json
99
+ │ → ⛔ 禁止用 ai-text-generate + 正则/split 解析多字段
100
+ └── 输出为单一文本(仅展示,不需解析)→ ai-text-generate
101
+ ```
102
+
103
+ ### 常见 Plugin Chain 组合
104
+
105
+ | 链路 | 插件组合 | 场景举例 |
106
+ |------|---------|---------|
107
+ | 文档→结构化数据 | `ai-doc-parser` → `ai-text-to-json` | 简历PDF→员工档案、合同→结构化条款 |
108
+ | 文档→摘要 | `ai-doc-parser` → `ai-text-summary` | 研报PDF→摘要、长文档→概要 |
109
+ | 文档→翻译 | `ai-doc-parser` → `ai-translate` | 英文论文→中文翻译 |
110
+ | 文档→分类 | `ai-doc-parser` → `ai-categorization` | 工单文档→类型标签 |
111
+ | 图片→结构化数据 | `ai-image-to-json`(**单步**) | 发票→金额/日期、名片→联系人 |
112
+ | 图片理解→结构化 | `ai-image-understanding` → `ai-text-to-json` | 复杂图表→数据、截图→字段 |
113
+ | 音频→结构化数据 | `ai-speech-to-text` → `ai-text-to-json` | 会议录音→待办事项 |
114
+ | 音频→翻译 | `ai-speech-to-text` → `ai-translate` | 外语录音→中文 |
115
+ | 抠图→换背景 | `ai-image-matting` → `ai-background-replace` | 商品图→电商主图 |
116
+ | 生成内容→通知 | `ai-text-generate` → `send-feishu-message` | AI生成报告→发送飞书通知 |
117
+ | 提取数据→入库 | `ai-text-to-json` → `feishu-bitable` | 文本→结构化→写入多维表格 |
118
+
119
+ ### Plugin Chain 调用模式
120
+
121
+ ```typescript
122
+ // 示例:文档 → 结构化数据(2步链)
123
+ // Step 1: 内容提取(注意:fileUrl 通常为数组类型,必须按 inputSchema 传入)
124
+ const rawResult = await capabilityClient
125
+ .load('doc_parser_instance')
126
+ .call('parseDocToMarkdown', { fileUrl: [docUrl] });
127
+
128
+ // Step 2: AI 结构化提取
129
+ const structured = await capabilityClient
130
+ .load('text_to_json_instance')
131
+ .call('textToJson', { text: rawResult.content });
132
+
133
+ // Step 3: 使用结果(填入表单 / 写入数据库 / 写入多维表格等)
134
+ ```
135
+
136
+ > **Client 侧提示**:`capabilityClient` 支持直接传 File/Blob 对象作为文件参数,无需先上传到 dataloom 获取 URL:
137
+ > ```typescript
138
+ > // Client 侧:直接传 File 对象,SDK 自动处理上传
139
+ > const rawResult = await capabilityClient
140
+ > .load('doc_parser_instance')
141
+ > .call('parseDocToMarkdown', { fileUrl: [file] }); // file 为 File/Blob 对象
142
+ > ```
143
+ > ⚠️ 仅 `capabilityClient`(Client 侧)支持此能力,Server 侧 `CapabilityService` 仅支持 URL 字符串。
144
+
145
+ ### 创建结构化提取 PluginInstance 的关键要求
146
+
147
+ 创建 `ai-text-to-json` 或 `ai-image-to-json` 类型的 PluginInstance 时:
148
+ 1. **必须一次性定义所有需要提取的字段**(参考数据库 schema / 表单定义 / UI 设计),宁多勿漏
149
+ 2. 字段类型仅支持 String / Number / Boolean,最多 20 个字段
150
+ 3. 先调用 `get_plugin_ai_json` 确认上游插件的 `outputSchema`,确保输入格式正确
151
+ 4. 图片→结构化数据场景,优先使用 `ai-image-to-json`(单步),避免不必要的链式调用
152
+
153
+ ## 外部服务插件 fallback 规则(飞书/外部 IO 类必读)
154
+
155
+ 以下 3 条规则适用于 **外部服务类插件**:`feishu-bitable` / `send-feishu-message` / `feishu-group-create` / `send-feishu-message-card` 等 plugin.ai.json `category=external-service` 的 PluginInstance。
156
+
157
+ ### 规则 1:飞书插件已内置 OAuth,禁止前端手写 OAuth URL
158
+
159
+ 飞书 `feishu-*` 系列插件在 `capabilityClient.call()` 内部自动完成 OAuth 授权(基于服务端注册的 OAuth `FEISHU_APP_ID`/`FEISHU_APP_SECRET` env),**前端不需要、也禁止**手写以下任何字面量:
160
+
161
+ - ❌ `https://open.feishu.cn/open-apis/authen/v1/index?app_id=...`
162
+ - ❌ `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=...`
163
+ - ❌ 把多维表格 `appToken`(如 `ZeHhbA4McaXT34s6sGLjiWLqpOd`,来源于用户填的 `/base/<appToken>` URL)当成 OAuth `app_id` 拼跳转 URL
164
+
165
+ > **命名陷阱**:飞书 OAuth `app_id` 是飞书自建应用 ID(`cli_xxxxxxxxxx`,由开发者在飞书开放平台创建后填到后端 env);多维表格 `appToken` 是 Bitable 文件 ID,来源是用户的多维表格 URL。两者完全不是同一概念,混淆会拼出空 / 错值导致用户看到"请求非法"。详见 `~/.claude/skills/miaoda-skills/feishu/references/oauth.md`。
166
+
167
+ ### 规则 2:`plugin_instance` 工具创建失败时的标准 fallback
168
+
169
+ 调用 `plugin_instance` 工具 CREATE 外部服务插件时,工具可能返回的失败语义包括:
170
+
171
+ | 工具返回 content | 含义 | 正确响应 |
172
+ |---|---|---|
173
+ | "缺少必填配置(appToken、tableID)..." | 当前用户配置不全,平台引导用户去 UI 配置 | (a) 在应用 UI 给配置入口(自建配置表单,回写 plugin instance 配置);(b) 代码保留 `capabilityClient.load(<pluginInstanceId>).call(...)` 引用;(c) 未配置态 UI 给清晰报错(`toast.error` + 配置入口跳转) |
174
+ | "当前不支持集成该插件实例" | 该 plugin 当前未对接 / 未上架 | 在 UI 明确告知用户该能力暂不可用,**禁止**降级到手写 SDK / 手写 OAuth |
175
+ | 其他 `status != "completed"` | 平台异常 | 重试一次后仍失败 → 同上:"禁止降级到手写实现",明示用户能力暂时不可用 |
176
+
177
+ **铁律:`plugin_instance` 工具返回任何失败状态时,禁止 agent 在前端代码中实现替代方案(手写 OAuth / 手写 fetch / 手写 SDK 调用)**。这条规则的意义是:外部服务插件的鉴权、签名、token 刷新、错误处理由插件统一封装,agent 自己写一遍既不安全(token 暴露给前端)也不稳定(鉴权流程频繁变更)。
178
+
179
+ ### 规则 3:业务功能依赖外部服务插件时,禁止用 `setTimeout` 模拟成功
180
+
181
+ 当业务功能(如"数据同步到飞书多维表格"、"发送飞书消息通知")依赖 `feishu-bitable` 等外部服务插件时,**禁止**写如下"模拟做事"代码:
182
+
183
+ - ❌ `await new Promise(resolve => setTimeout(resolve, 300))` + 直接设 `successCount = totalRows`
184
+ - ❌ `await fakeSyncToFeishu()` + 假装返回 `{status: 'success'}`
185
+ - ❌ 任何不调 `capabilityClient.load(<id>).call(...)` 也不调真实后端 API 的"假同步"路径
186
+
187
+ 正确做法(二选一):
188
+
189
+ 1. **调真 plugin**:`await capabilityClient.load(pluginInstanceId).call('batchAddRecords', { ... })`,捕获错误后给清晰 UI 反馈
190
+ 2. **明确报错**:plugin 未配置/未就绪时直接 `toast.error('未配置飞书多维表格,请前往设置页配置')` + 跳转配置页,**不**编一个假的"同步成功"
191
+
192
+ ## 核心概念
193
+ 新版链路中,**Plugin(插件)**、**PluginInstance(插件实例配置)**、**PluginInstanceAIJson(运行时投影:pluginInstance.ai.json)** 的关系如下:
194
+
195
+ - **Plugin(插件)**:底层承载单元,包含插件元信息与表单定义(form.schema)。模型侧只感知插件及其表单字段,不感知插件内部实现细节。
196
+ - **PluginInstance(插件实例配置)**:基于某个 Plugin 的表单做"业务封装",以 **单文件 JSON** 的形式存储(每个插件实例一个文件,语义化 id)。
197
+ - 通过 `paramsSchema` 暴露业务入参
198
+ - 通过 `formValue` 将业务入参映射到插件表单字段(可常量或引用 `{{input.xxx}}`)
199
+ - **PluginInstanceAIJson(pluginInstance.ai.json)**:工程转化层产物,是 pluginInstance 的**运行时投影 / 调用合同(Runtime Spec)**。
200
+ - 包含插件定位信息、actions 入口列表、input/output schema、outputMode、readme 等
201
+ - Code Agent 在生成**调用代码**前,必须读取它作为权威依据(Server 侧用 `CapabilityService`,Client 侧用 `capabilityClient`)
202
+
203
+ ### 插件 Plugin
204
+ 插件是插件实例的承载单元,包含:
205
+ • 插件元信息(tags/name/description/version/...)
206
+ • 插件表单定义(form.schema),用于描述"这个插件需要哪些表单字段"。
207
+ 重要:模型侧只感知插件与其表单 schema,不感知插件内部实现(如 Action的实现、API 细节等)。
208
+
209
+ Plugin 的具体内容以JSON格式给出,例如:
210
+
211
+ ```json
212
+ {
213
+ "name": "plugin-key", // 插件唯一标识
214
+ "displayName": "飞书群组创建", // 展示名称
215
+ "version": "1.0.0",
216
+ "form": {
217
+ "schema": { // 插件表单定义(PluginInstance 的 formValue 映射到此)
218
+ "type": "object",
219
+ "properties": {
220
+ "group_name": { "type": "string", "description": "群组名称" },
221
+ "members": { "type": "array", "description": "群成员id列表", "items": { "type": "string" } }
222
+ },
223
+ "required": ["group_name", "members"]
224
+ }
225
+ }
226
+ }
227
+ ```
228
+
229
+ ### 插件实例 PluginInstance
230
+ 开发框架内置 `plugin` 工具,用于创建和管理基于 **Plugin(插件表单)** 的业务插件实例(PluginInstance)。
231
+
232
+ **重要说明**:
233
+ - PluginInstance 的配置以"单文件 JSON"形式存储在 `server/capabilities/`(每个插件实例一个文件,逻辑上对应 server/capabilities/<id>.json)。
234
+ - 运行时调用前,Code Agent 需要通过 get_plugin_ai_json 获取对应插件实例的 pluginInstance.ai.json,再基于其中的 actions/schema/outputMode 生成调用代码。
235
+ - 运行时调用入口统一走 SDK/Service:
236
+ - Server 侧:CapabilityService.load(pluginInstanceId).call(actionKey, input)
237
+ - Client 侧:capabilityClient.load(pluginInstanceId).call(actionKey, input)(流式用 callStream)
238
+
239
+
240
+ PluginInstance 的配置以 JSON 形式输出,例如:
241
+ ```json
242
+ {
243
+ "id": "create_feishu_group", // 全局唯一语义化 ID
244
+ "pluginKey": "@xxx/feishu-group", // 绑定的 Plugin name
245
+ "pluginVersion": "1.0.0",
246
+ "name": "任务创建时自动创建飞书群组",
247
+ "description": "根据任务名称自动生成飞书群组并设置初始成员",
248
+ "paramsSchema": { // 对外暴露的业务入参(仅 string / array<string> / picture / file)
249
+ "type": "object",
250
+ "properties": {
251
+ "group_name": { "type": "string", "description": "群组名称" },
252
+ "members": { "type": "array", "items": { "type": "string" }, "description": "成员ID列表" }
253
+ },
254
+ "required": ["group_name"]
255
+ },
256
+ "formValue": { // 映射到 Plugin form.schema 字段(常量或 {{input.xxx}})
257
+ "group_name": "{{input.group_name}}",
258
+ "members": "{{input.members}}"
259
+ }
260
+ }
261
+ ```
262
+
263
+ **注意**paramsSchema 支持以下 4 种参数类型,需要按下面规定的格式进行填充:
264
+
265
+ 1. **文本** - 单行或多行文本输入
266
+ ```json
267
+ {
268
+ "type": "string",
269
+ "description": "文本参数描述"
270
+ }
271
+ ```
272
+
273
+ 2. **数组** - 字符串数组(如 ID 列表、标签列表等)
274
+ ```json
275
+ {
276
+ "type": "array",
277
+ "description": "数组参数描述",
278
+ "items": {
279
+ "type": "string",
280
+ "description": "数组元素描述"
281
+ }
282
+ }
283
+ ```
284
+
285
+ 3. **图片** - 图片资源(需指定 format 为 picture)
286
+ ```json
287
+ {
288
+ "type": "string",
289
+ "format": "picture",
290
+ "description": "图片参数描述"
291
+ }
292
+ ```
293
+
294
+ 4. **文件** - 文件资源(需指定 format 为 file)
295
+ ```json
296
+ {
297
+ "type": "string",
298
+ "format": "file",
299
+ "description": "文件参数描述"
300
+ }
301
+ ```
302
+
303
+ > **注意**:`format` 为 `file`、`picture` 或 `plugin-file-url` 的字段在 Client 侧调用时均支持直接传入 File/Blob 对象,`capabilityClient` SDK 会自动处理上传;Server 侧 `CapabilityService` 仅支持 URL 字符串。**禁止**Client 侧先通过 dataloom 上传文件拿 URL 再传给插件——dataloom 的 `download_url` 是内部存储路径,插件服务端可能无法访问。
304
+ #### PluginInstanceAIJson(运行时投影 / 工程转化层产物:pluginInstance.ai.json)
305
+ pluginInstance.ai.json 是从 PluginInstance 配置派生出的运行时插件实例说明(Runtime Spec),用于 Code Agent 动态生成调用代码。
306
+ 它包含:
307
+ • 插件实例元数据(id/pluginKey/pluginVersion/name/description)
308
+ • 可执行入口列表 actions[](每个入口包含 key/inputSchema/outputSchema/outputMode)
309
+ • 详细说明 readme
310
+ • type:单入口/多入口(single_action | multi_action)
311
+
312
+ **重要**:模型不能自行猜测某个插件实例有哪些 action、入参/出参结构;在生成调用代码前必须通过工具读取该 pluginInstance.ai.json。
313
+
314
+ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
315
+ ```json
316
+ {
317
+ "type": "multi_action", // 插件实例类型:single_action 表示仅 1 个 action;multi_action 表示多个 action(调用前需选择 actionKey)
318
+ "id": "********", // PluginInstance 的唯一标识(插件实例ID),用于后续通过 get_plugin_ai_json 获取详情、以及运行时调用定位插件实例
319
+ "pluginKey": "@*******", // 该插件实例绑定的插件ID(运行时用于定位具体插件实现)
320
+ "pluginVersion": "1.0.0", // 插件版本号(用于版本锁定/兼容性,避免插件升级导致 schema 或行为变化)
321
+ "name": "******", // 插件实例名称(面向人/模型展示,用于检索与选择插件实例)
322
+ "description": "...", // 插件实例描述(说明该插件实例解决什么业务问题、适用场景,用于帮助模型理解意图)
323
+ "actions": [ // 可执行入口列表
324
+ {
325
+ "key": "insertRecords", // action 标识(调用时作为 actionKey 使用):插入记录/新增数据
326
+ "inputSchema": {}, // 该 action 的入参 JSON Schema(生成调用参数时必须严格遵循)
327
+ "outputSchema": {}, // 该 action 的出参 JSON Schema(解析返回值时按此结构读取字段)
328
+ "outputMode": "unary" // 输出模式:unary=一次性返回;stream=流式返回(决定代码生成的处理方式)
329
+ },
330
+ {
331
+ "key": "updateRecords", // action 标识:更新记录/修改数据
332
+ "inputSchema": {}, // 更新操作需要的入参结构定义(JSON Schema)
333
+ "outputSchema": {}, // 更新操作返回结构定义(JSON Schema)
334
+ "outputMode": "unary" // 输出模式(同上)
335
+ },
336
+ {
337
+ "key": "deleteRecord", // action 标识:删除记录(注意:有的插件实例会是 deleteRecords 表示批量删除)
338
+ "inputSchema": {}, // 删除操作需要的入参结构定义(JSON Schema)
339
+ "outputSchema": {}, // 删除操作返回结构定义(JSON Schema)
340
+ "outputMode": "unary" // 输出模式(同上)
341
+ }
342
+ ],
343
+ "readme": "", // 插件实例使用说明文档(可能包含特殊字段解释、限制、示例代码;优先参考它来生成调用逻辑)
344
+ "createdAt": 1764234360374, // 插件实例创建时间戳(毫秒),用于变更追踪/缓存刷新
345
+ "updatedAt": 1764234360374 // 插件实例更新时间戳(毫秒),用于变更追踪/缓存刷新
346
+ }
347
+ ```
348
+
349
+ ## 可用的 Plugin
350
+ ```
351
+ {{available_plugins}}
352
+ ```
353
+ 说明:先从可用的 PluginInstance 进行选择,如果无法满足需求,看可用的 Plugin,如果有满足的插件,调用插件生成工具进行生成,如果没有,直接拒答。
354
+
355
+ ## 可用的 PluginInstance
356
+ ```
357
+ {{available_plugin_instances}}
358
+ ```
359
+
360
+ ### 使用方式
361
+ 1. **创建/修改 PluginInstance(配置层)**:调用 `plugin_instance` 工具生成/更新单文件 PluginInstance JSON(基于插件表单封装)。
362
+ 2. **查询已有 PluginInstance(配置层)**:优先使用可用的 PluginInstance;如仍需核对细节,再读取对应插件实例配置(调用'get_plugin_ai_json')。
363
+ 3. **生成运行时代码(调用层)**:
364
+ - 已确定使用某个 PluginInstance 后,先调用 `get_plugin_ai_json` 获取该插件实例的运行时投影(pluginInstance.ai.json)
365
+ - 基于返回的 `actions[].key/inputSchema/outputSchema/outputMode` 动态生成调用代码(Server 用 CapabilityService,Client 用 capabilityClient)
366
+ - 仔细阅读返回的 readme,必须严格遵循里面制定的规则
367
+
368
+ ### 典型场景示例
369
+ - **消息通知类**:封装"发送飞书消息"相关插件为业务插件实例
370
+ - **群组管理类**:封装"创建飞书群组"相关插件为业务插件实例
371
+ - **AI 生成类**:封装"AI 生文/生图/图片理解"相关插件为业务插件实例
372
+
373
+ ### 使用限制
374
+ - PluginInstance 必须通过 `plugin_instance` 工具创建/更新,不支持 agent 直接手改 `server/capabilities/` 下的配置文件
375
+ - 调用前必须通过 `get_plugin_ai_json` 获取权威 schema,禁止猜测入参/出参结构
376
+ - PluginInstance 配置信息存储在 `server/capabilities/` 目录
377
+ - 运行时调用统一走 SDK/Service,不再为每个插件实例预生成固定的 call 文件
378
+ - Server 侧:CapabilityService.load(capabilityId).call(actionKey, input)
379
+ - Client 侧:capabilityClient.load(capabilityId).call(actionKey, input)(流式用 callStream)
380
+
381
+ ## PluginInstance 生成约束
382
+
383
+ 1. 非常**注意**,必须通过调用 `plugin_instance`工具 来创建、更新 PluginInstance,**绝对禁止**使用 `multi_edit`工具 和 `write`工具 来直接修改 `server/capabilities/` 目录下的内容。
384
+ 2. PluginInstance 配置以单文件形式存储在 server/capabilities/ 目录下(逻辑上为 server/capabilities/<plugin_instance_id>.json),不再维护集中式的 capabilities.json。
385
+ 3. 用户可以手动修改 `server/capabilities/<plugin_instance_id>.json` 文件中的配置,且用户修改完配置后一定会通知你(例如告诉你'刚刚更新了PluginInstance配置'),此时你需要根据用户修改后的配置,通过调用(必须,禁止使用其他工具来操作 PluginInstance)`plugin_instance`工具 来更新PluginInstance。
386
+ 4. 调用侧在生成调用代码前,**必须**通过 get_plugin_ai_json 获取该插件实例的运行时投影(plugin_Instance.ai.json),并以其作为入参/出参 schema 与 action 列表的唯一权威依据,禁止自行猜测。
387
+ 5. 若插件表单字段中包含 card_content 且其语义为飞书消息卡片,则生成/更新 PluginInstance 配置时,formValue.card_content 必须是 JSON Object,不能是转义字符串;并且卡片 DSL 的最后两个元素必须固定。
388
+
389
+ ## 严格的调用规则 - 禁止 Mock
390
+
391
+ **当用户场景需要用到插件实例时,禁止 Mock,必须走真实插件实例调用链路。**
392
+
393
+ - 禁止 Mock `capabilityClient` / `CapabilityService` 的返回值
394
+ - 禁止使用假数据创建 mock 版本的 PluginInstance 调用
395
+ - 禁止跳过实际方法调用而直接构造返回结果
396
+ - 必须基于 `get_plugin_ai_json` 返回的 schema 生成真实调用代码
397
+ - 如果调用失败或参数不明确,必须反馈给用户并补齐关键参数
398
+
399
+ ---
400
+
401
+ ## GetPluginInstanceAIJson 工具使用指南
402
+
403
+ `get_plugin_ai_json(pluginInstanceID)` — 读取插件实例的运行时投影,返回结构参见上文 PluginInstanceAIJson 示例。
404
+
405
+ ### 何时调用
406
+
407
+ | 场景 | 是否调用 |
408
+ |------|---------|
409
+ | 已选定插件实例,需生成调用代码 | **必须调用** |
410
+ | 需确认 actions / inputSchema / outputSchema / outputMode | **必须调用** |
411
+ | 创建或修改 PluginInstance 配置 | 不需要(用 `plugin_instance` 工具) |
412
+ | 只需插件实例列表概览 | 不需要(已在上下文中提供) |
413
+
414
+ ### 消费返回数据要点
415
+
416
+ 1. 根据 `actions[].key` 选择 actionKey,严格按 `inputSchema` 构造入参、按 `outputSchema` 解析出参
417
+ 2. `type = single_action` 时只有一个 action;`multi_action` 时需选择合适的 actionKey
418
+ 3. **务必阅读 `readme` 字段**,可能包含特殊参数说明、使用限制和示例代码
419
+ 4. 注意 `inputSchema` 中的类型定义(特别是 `type: array` 的字段,必须传数组而非字符串)
420
+ 5. **流式输出必须按 `outputSchema` 解构 chunk**:`callStream` 返回的每个 chunk 是**对象**(结构与 `outputSchema` 一致),不是原始字符串。必须通过字段访问提取内容(如 `chunk.content`),禁止将 chunk 当作字符串直接拼接
421
+ 6. **非流式输出同样按 `outputSchema` 解析**:`call()` 的返回值是对象,必须按 `outputSchema` 的字段名读取(如 `result.content`、`result.images`),禁止假设返回值结构
422
+ 7. **先产出 Schema 摘录卡再编码**:未完成摘录卡(pluginInstanceId/actionKey/outputMode/required/output fields/readme约束)前,禁止进入代码编辑
423
+
424
+ ### 编码前闸门(必须通过)
425
+
426
+ 调用代码落盘前,先输出 **Schema 摘录卡**(来自 `get_plugin_ai_json`):
427
+
428
+ ```markdown
429
+ [Schema 摘录卡]
430
+ - pluginInstanceId / actionKey / outputMode
431
+ - input.required: [字段名: 类型, ...]
432
+ - output.fields: [字段名: 类型, ...](每个字段必须在代码中被消费,未消费需注释说明原因)
433
+ - readme.constraints
434
+ - 调用侧决策: Client | Server
435
+ ```
436
+
437
+ 字段缺失时禁止编码。摘录卡中的 output.fields **必须完整列出**,编码时每个输出字段都必须被消费(持久化或展示)。
438
+
439
+ ## 开发流程要求
440
+
441
+ ### 第一步:检查现有 PluginInstance(复用优先)
442
+
443
+ 1) 用户描述需求后,优先基于上下文提供的插件实例列表检索是否已存在可复用插件实例。
444
+ 2) 若存在候选插件实例但你无法确认其是否满足需求,必须调用 `get_plugin_ai_json` 获取该插件实例的运行时投影(pluginInstance.ai.json),根据其中的:
445
+ - `actions[].key`
446
+ - `actions[].inputSchema / outputSchema`
447
+ - `actions[].outputMode`
448
+ 来判断是否可复用以及如何调用。
449
+ 3) 禁止按旧链路去读取/维护 `server/capabilities/capabilities.json` 来做复用判断。
450
+
451
+ > 结论:**复用判断以插件实例列表 + get_plugin_ai_json 为准**,禁止猜测 action、入参/出参、输出模式。
452
+
453
+ ### 第二步:决策
454
+
455
+ - 如果找到匹配的插件实例:直接进入「第三步:代码调用」,严禁为了"更贴合需求"而随意新建重复的插件实例。
456
+ - 如果不存在合适插件实例,则基于上下文提供的插件列表进行判断:
457
+ - 需要创建:如果插件列表有更贴合需求的插件,调用 `plugin_instance` 工具(operType=CREATE)生成新的单文件 PluginInstance 配置。
458
+ - 需要调整已有插件实例:调用 `plugin_instance` 工具(operType=UPDATE)更新该插件实例配置。
459
+ - 其他情况:告知用户该需求目前无法满足
460
+
461
+ **强制约束**:
462
+ - 创建/更新 PluginInstance **必须**通过 `plugin_instance` 工具完成,绝对禁止直接修改 `server/capabilities/` 下的配置文件。
463
+ - UPDATE 场景严禁修改保护字段:`id / pluginKey / pluginVersion / createdAt `。
464
+
465
+ ### 第三步:生成调用代码
466
+
467
+ 1. **必须**先调用 `get_plugin_ai_json(pluginInstanceId)`
468
+ 2. **必须**先输出 Schema 摘录卡(见上方“编码前闸门”),完成后才能写代码
469
+ 3. 根据 `actions[].key` 选择正确的 actionKey
470
+ 4. 严格按 `inputSchema` 构造入参,严格按 `outputSchema` 解析出参
471
+ 5. **优先选择 Client 侧调用**:
472
+ - `outputMode = unary` → `capabilityClient.load(id).call()`
473
+ - `outputMode = stream` → `capabilityClient.load(id).callStream()`
474
+ 6. **仅在必要时使用 Server 侧**(触发器、敏感凭证、强事务等场景)
475
+
476
+ ### 第五步:真实调用冒烟验证(完成前必须)
477
+
478
+ 代码修改后,必须完成最小冒烟并记录:
479
+
480
+ 1. `unary` 场景:至少成功调用一个 `call()`
481
+ 2. `stream` 场景:至少成功调用一个 `callStream()`,并按 `outputSchema` 读取 chunk
482
+ 3. 失败日志最小字段:`pluginInstanceId` `actionKey` `outputMode` `inputKeys` `resultKeys` `firstChunkKeys` `error.message`
483
+ 4. 无冒烟结果,不得宣告完成
484
+
485
+ ### 第四步:代码放置位置
486
+
487
+ | 调用侧 | 代码位置 | 适用场景 |
488
+ |-------|---------|---------|
489
+ | Client(默认) | `client/` 目录下的组件/hooks | 用户交互触发的调用 |
490
+ | Server(兜底) | `server/` 目录下的 Service | 触发器、定时任务、敏感操作 |
491
+
492
+ ## 常见错误(必须避免)
493
+
494
+ | 错误做法 | 正确做法 |
495
+ |---------|---------|
496
+ | 不涉及持久化时仍在 Server 侧写调用代码 | 不需要存储的场景(即时展示、发消息等),优先用 Client 侧 |
497
+ | 为不涉及数据存储的插件调用创建后端 API 中转 | 纯展示场景前端直接调用 `capabilityClient`;涉及持久化时可在 Server 侧调用 |
498
+ | 未调用 `get_plugin_ai_json` 就猜测参数 | 先获取 runtime spec,再生成代码 |
499
+ | Mock `capabilityClient` / `CapabilityService` 返回值 | 必须真实调用 |
500
+ | 猜测 actionKey 或参数结构 | 严格按 `get_plugin_ai_json` 返回的 schema |
501
+ | `call()` 调用签名错误:`plugin.call(JSON.stringify({...}))` 或 `plugin.call({...})` | `call()` 第一个参数必须是 actionKey 字符串,第二个参数才是 input 对象:`plugin.call('actionKey', {...})` |
502
+ | 用正则/字符串解析处理 AI 输出(提取或生成场景) | 提取用 `ai-text-to-json` / `ai-image-to-json`;多字段生成拆多插件或用 `ai-text-to-json` |
503
+ | 创建 `ai-text-to-json` / `ai-image-to-json` PluginInstance 时只定义部分字段 | 分析需求中**全部**字段后一次性定义完整(最多20字段) |
504
+ | 认为 `ai-doc-parser` 能直接输出结构化 JSON | `ai-doc-parser` 只输出纯文本,需链式调用 `ai-text-to-json` 做结构化 |
505
+ | 图片提取结构化数据时用 `ai-image-understanding` → `ai-text-to-json` 两步链 | 优先用 `ai-image-to-json` 单步直达(发票/名片/证件等场景) |
506
+ | 未读 `inputSchema` 就假设参数类型(如 string vs array) | 先调用 `get_plugin_ai_json` 查看 `inputSchema`,注意 `type: array` 字段 |
507
+ | 流式调用时将 chunk 当作字符串直接拼接(如 `text += chunk`) | chunk 是对象,必须按 `outputSchema` 解构字段:`text += chunk.content \|\| ''` |
508
+ | 未按 `outputSchema` 解析返回值,猜测返回结构 | 严格按 `get_plugin_ai_json` 返回的 `outputSchema` 读取字段,流式和非流式均适用 |
509
+ | 未输出 Schema 摘录卡就直接写调用代码 | 先完成“编码前闸门”中的摘录卡,再开始编码 |
510
+ | 改完未做真实调用冒烟就宣告完成 | 至少完成一次 unary/stream 真实调用验证,并附最小日志字段 |
511
+ | formValue 中用 `["{{input.xxx}}"]` 包装已经是 `type: array` 的 paramsSchema 参数 | 当 paramsSchema 定义为 array 时,formValue 应透传 `"{{input.xxx}}"`,不要再包一层数组 |
512
+ | 通过 `getDataloom().capability` 或 `(dataloom as any).capability` 调用插件 | `capabilityClient` 是独立导入,不通过 dataloom 访问。dataloom 仅提供 storage 和 service |
513
+ | Client 侧调用插件时,先通过 dataloom 上传文件拿 URL 再传给插件 | Client 侧可直接传 File/Blob 对象给 `capabilityClient`,SDK 自动处理上传。适用于所有文件类型字段(`format` 为 `file`/`picture`/`plugin-file-url`)。Server 侧仍需传 URL |
514
+ | 前端调用插件后不保存结果到数据库,导致页面刷新后数据丢失 | 需要持久化时:优先在 Server 侧调用并直接落库(方案A);若在 Client 侧调用,必须通过已有 CRUD 接口立即保存结果(方案B) |
515
+ | 为保存插件结果单独新建 API 端点(如 `PATCH /api/xxx/ai-analysis`) | 优先复用已有的业务 CRUD 接口(create/update)扩展字段,或在 Server 侧 Service 中调用插件并直接落库 |
516
+ | 创建了 PluginInstance 但未生成调用代码(只建不调) | CREATE 后**必须**接着调用 get_plugin_ai_json → 生成调用代码 → 集成到业务逻辑 |
517
+ | 插件返回值用 `as any` 直接取字段,无类型保护 | 根据 outputSchema 生成 TypeScript interface,用类型断言替代 `as any` |
518
+ | 插件调用失败后 `console.error` 静默吞异常 | 必须向用户展示错误或触发补偿机制(见"错误处理规范"章节) |
519
+ | 向插件输入参数注入 UUID/时间戳来"绕缓存" | 禁止污染业务参数,AI 插件无请求级缓存(见"缓存与幂等性"章节) |
520
+ | 通知类插件的接收人 ID 硬编码在**代码**中(如 `const userId = '185410...'`) | 固定接收人 → 在 plugin_instance CREATE 的 `formValue.receiverUserList` 中直接配置;动态接收人 → 从配置/平台 API/DB 获取(见"参数来源规范"章节) |
521
+
522
+ ## 业务语义映射约定
523
+
524
+ 用户会用「AI 生文」「AI 生图」「发送飞书消息」等**业务语言**描述需求;这些关键词必须被识别为**待使用或待创建的 PluginInstance**。
525
+
526
+ 开发流程:
527
+ 1. 收到需求后,先看可用的 PluginInstance 是否有可以直接使用的插件实例
528
+ 2. 若有候选但不确定是否满足,调用 `get_plugin_ai_json` 查看其 actions/schema/outputMode 再决策
529
+ 3. 若无,立即调用 `plugin_instance` 工具新建
530
+ 4. **在 Client 侧**生成调用代码(默认),仅在必要场景使用 Server 侧
531
+
532
+ ## 插件调用错误处理规范
533
+
534
+ ### 错误分类与应对
535
+
536
+ | 错误类型 | 含义 | 应对策略 |
537
+ |----------|------|---------|
538
+ | `InputValidationError` | 入参不符合 schema | 修复参数后重试,不应出现在生产环境 |
539
+ | `RateLimitError` | 触发限流 | 指数退避重试(1s/2s/4s),最多 3 次 |
540
+ | `ExecutionError` | 插件执行失败 | 记录日志 + 降级方案(如规则计算)+ 通知用户 |
541
+ | `OutputValidationError` | 返回值不符合 schema | 记录异常返回 + 使用默认值或降级 |
542
+ | 网络超时 | 请求超时 | 重试 + 超时后降级 |
543
+
544
+ ### 必须遵守的规则
545
+
546
+ 1. **禁止静默吞异常**: 每个 `catch` 块必须满足以下至少一项:
547
+ - 向用户展示错误提示(前端 toast / 页面状态标记)
548
+ - 触发补偿机制(重试 / 降级 / 记录待处理列表)
549
+ 2. **异步操作必须有终态**: 如果插件调用是异步的(不阻塞主流程),必须在 DB 中维护状态(pending → success / failed),前端必须展示 failed 状态,不能永远停在 pending/loading。
550
+ 3. **通知类插件失败必须有补偿**: 如 `send-feishu-message` 失败,至少记录到"待发送"列表,或在 UI 中提示"通知发送失败,请手动联系"。
551
+
552
+ ## 缓存与幂等性
553
+
554
+ - AI 类插件(text-generate/text-to-json 等)**没有请求级缓存**。同样的输入可能返回相似结果,这是 LLM 在低 temperature 下的正常行为,不是缓存。
555
+ - **禁止**通过修改业务参数(如在 job_description 中注入 UUID)来"绕缓存"。这会污染 AI 输入,导致输出包含垃圾文本。
556
+ - 如果确实需要不同结果,正确做法:
557
+ 1. 调整插件实例的 `formValue.modelParams.temperature`(提高随机性)
558
+ 2. 在 prompt 中增加明确的变化要求(如"请生成与之前不同的版本")
559
+ 3. 使用不同的业务上下文(如不同的候选人简历)
560
+
561
+ ## 插件参数来源规范
562
+
563
+ 插件的 inputSchema 参数分为两类:
564
+
565
+ | 类型 | 来源 | 示例 |
566
+ |------|------|------|
567
+ | 业务数据 | 从 DB 查询或前端传入 | 候选人姓名、简历内容、职位描述 |
568
+ | 运行时配置 | 从配置/环境变量/平台 API 获取 | 接收人 user_id、通知模板、阈值 |
569
+
570
+ **禁止**在业务代码中硬编码运行时配置值(如 `const userId = '185410...'`)。正确做法取决于值是否固定:
571
+
572
+ | 场景 | 正确做法 | 示例 |
573
+ |------|---------|------|
574
+ | 需求明确的**固定**接收人/配置 | 在 `plugin_instance CREATE` 的 `formValue` 中直接写死 | `formValue.receiverUserList: ["1854102143505690"]` |
575
+ | **动态**接收人/配置(按角色/条件变化) | 从配置/平台 API/DB 获取,传入 `input` 参数 | `formValue.receiverUserList: "{{input.receiverIds}}"` |
576
+
577
+ > **关键区分**:`formValue` 中配置固定值 ≠ 代码中硬编码。`formValue` 是插件实例的声明式配置,修改不需要改代码;而代码中硬编码的值散落在业务逻辑中,难以维护。
578
+
579
+ 当接收人/配置值是动态的,获取途径:
580
+ 1. 通过平台角色 API 获取(如"所有 admin_hr 角色的用户")
581
+ 2. 存入应用配置表,通过 API 读取
582
+ 3. 通过环境变量注入