@lark-apaas/coding-steering 0.1.32-beta.0 → 0.1.32-dev.3598eb3

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