@lark-apaas/coding-steering 0.1.6-alpha.11 → 0.1.6-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +1 -1
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +6 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +4 -1
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +1 -1
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +30 -4
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +39 -127
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +3 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +2 -2
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +3 -2
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +36 -17
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +4 -1
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +4 -2
- package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +4 -4
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +17 -10
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +15 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 日历与会议室 (Calendar)
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/server-docs/calendar-v4/overview.md>
|
|
4
4
|
|
|
5
5
|
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中管理日程、预约会议室和查询忙闲状态。
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 通讯录 (Contacts)
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/server-docs/contact-v3/resources.md>
|
|
4
4
|
|
|
5
5
|
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中查询飞书通讯录用户和部门信息。
|
|
6
6
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# 云空间 (Drive)
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/server-docs/docs/drive-v1/introduction>
|
|
4
|
+
.md
|
|
4
5
|
|
|
5
6
|
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中管理飞书云空间文件和文件夹。
|
|
6
7
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# 事件订阅 (Events)
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/server-docs/event-subscription-guide/overview>
|
|
4
|
+
.md
|
|
4
5
|
|
|
5
6
|
通过 WebSocket 长连接接收飞书事件推送,无需公网 IP / 域名,无需加解密。
|
|
6
7
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# 妙搭与飞书开放平台 — ID 识别与转换
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/uAjLw4CM/ukTMukTMukTM/spark-v1/overview.md>
|
|
4
4
|
>
|
|
5
|
-
>
|
|
5
|
+
> 飞书用户身份体系官方说明:<https://open.larkoffice.com/document/platform-overveiw/basic-concepts/user-identity-introduction/introduction>
|
|
6
6
|
>
|
|
7
7
|
> **承接 `user-identity` skill 决策树**:如果只需 妙搭 userId → 飞书 user_id(employee_id),在 nestjs-react-fullstack 项目内优先用 `AuthNPaasService`。本文覆盖 `AuthNPaasService` 不支持的场景:需要 `open_id` / `union_id`、或需要任何方向的反查、或不在该模板内的项目。
|
|
8
8
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# OAuth 用户授权
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM>
|
|
4
|
+
.md
|
|
4
5
|
|
|
5
6
|
默认的 tenant_access_token 以应用身份调用 API。
|
|
6
7
|
部分场景(个人日历、搜索联系人)需要 user_access_token 代表用户操作。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# 权限管理 (Permission)
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/server-docs/docs/permission/overview>
|
|
4
|
+
.md
|
|
4
5
|
|
|
5
6
|
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中管理飞书云文档的协作者权限。
|
|
6
7
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# 知识库 (Wiki)
|
|
2
2
|
|
|
3
|
-
> 开放平台文档(Markdown
|
|
3
|
+
> 开放平台文档(Markdown 版):<https://open.larkoffice.com/document/server-docs/docs/wiki-v2/wiki-overview>
|
|
4
|
+
.md
|
|
4
5
|
|
|
5
6
|
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中操作飞书知识库。
|
|
6
7
|
|
|
@@ -151,7 +152,7 @@ Wiki API 不提供搜索功能。获取内容需通过以下方式:
|
|
|
151
152
|
|
|
152
153
|
1. 打开知识空间 → 设置 → 成员管理
|
|
153
154
|
2. 添加机器人应用
|
|
154
|
-
3.
|
|
155
|
+
3. 参考:<https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa>
|
|
155
156
|
|
|
156
157
|
## Common Mistakes
|
|
157
158
|
|
|
@@ -134,17 +134,20 @@ const structured = await capabilityClient
|
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
> **Client 侧提示**:`capabilityClient` 支持直接传 File/Blob 对象作为文件参数,无需先上传到 dataloom 获取 URL:
|
|
137
|
+
>
|
|
137
138
|
> ```typescript
|
|
138
139
|
> // Client 侧:直接传 File 对象,SDK 自动处理上传
|
|
139
140
|
> const rawResult = await capabilityClient
|
|
140
141
|
> .load('doc_parser_instance')
|
|
141
142
|
> .call('parseDocToMarkdown', { fileUrl: [file] }); // file 为 File/Blob 对象
|
|
142
143
|
> ```
|
|
144
|
+
>
|
|
143
145
|
> ⚠️ 仅 `capabilityClient`(Client 侧)支持此能力,Server 侧 `CapabilityService` 仅支持 URL 字符串。
|
|
144
146
|
|
|
145
147
|
### 创建结构化提取 PluginInstance 的关键要求
|
|
146
148
|
|
|
147
149
|
创建 `ai-text-to-json` 或 `ai-image-to-json` 类型的 PluginInstance 时:
|
|
150
|
+
|
|
148
151
|
1. **必须一次性定义所有需要提取的字段**(参考数据库 schema / 表单定义 / UI 设计),宁多勿漏
|
|
149
152
|
2. 字段类型仅支持 String / Number / Boolean,最多 20 个字段
|
|
150
153
|
3. 先调用 `get_plugin_ai_json` 确认上游插件的 `outputSchema`,确保输入格式正确
|
|
@@ -190,6 +193,7 @@ const structured = await capabilityClient
|
|
|
190
193
|
2. **明确报错**:plugin 未配置/未就绪时直接 `toast.error('未配置飞书多维表格,请前往设置页配置')` + 跳转配置页,**不**编一个假的"同步成功"
|
|
191
194
|
|
|
192
195
|
## 核心概念
|
|
196
|
+
|
|
193
197
|
新版链路中,**Plugin(插件)**、**PluginInstance(插件实例配置)**、**PluginInstanceAIJson(运行时投影:pluginInstance.ai.json)** 的关系如下:
|
|
194
198
|
|
|
195
199
|
- **Plugin(插件)**:底层承载单元,包含插件元信息与表单定义(form.schema)。模型侧只感知插件及其表单字段,不感知插件内部实现细节。
|
|
@@ -201,9 +205,10 @@ const structured = await capabilityClient
|
|
|
201
205
|
- Code Agent 在生成**调用代码**前,必须读取它作为权威依据(Server 侧用 `CapabilityService`,Client 侧用 `capabilityClient`)
|
|
202
206
|
|
|
203
207
|
### 插件 Plugin
|
|
208
|
+
|
|
204
209
|
插件是插件实例的承载单元,包含:
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
• 插件元信息(tags/name/description/version/...)
|
|
211
|
+
• 插件表单定义(form.schema),用于描述"这个插件需要哪些表单字段"。
|
|
207
212
|
重要:模型侧只感知插件与其表单 schema,不感知插件内部实现(如 Action的实现、API 细节等)。
|
|
208
213
|
|
|
209
214
|
Plugin 的具体内容以JSON格式给出,例如:
|
|
@@ -227,17 +232,19 @@ Plugin 的具体内容以JSON格式给出,例如:
|
|
|
227
232
|
```
|
|
228
233
|
|
|
229
234
|
### 插件实例 PluginInstance
|
|
235
|
+
|
|
230
236
|
开发框架内置 `plugin` 工具,用于创建和管理基于 **Plugin(插件表单)** 的业务插件实例(PluginInstance)。
|
|
231
237
|
|
|
232
238
|
**重要说明**:
|
|
239
|
+
|
|
233
240
|
- PluginInstance 的配置以"单文件 JSON"形式存储在 `server/capabilities/`(每个插件实例一个文件,逻辑上对应 server/capabilities/<id>.json)。
|
|
234
241
|
- 运行时调用前,Code Agent 需要通过 get_plugin_ai_json 获取对应插件实例的 pluginInstance.ai.json,再基于其中的 actions/schema/outputMode 生成调用代码。
|
|
235
242
|
- 运行时调用入口统一走 SDK/Service:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
243
|
+
- Server 侧:CapabilityService.load(pluginInstanceId).call(actionKey, input)
|
|
244
|
+
- Client 侧:capabilityClient.load(pluginInstanceId).call(actionKey, input)(流式用 callStream)
|
|
239
245
|
|
|
240
246
|
PluginInstance 的配置以 JSON 形式输出,例如:
|
|
247
|
+
|
|
241
248
|
```json
|
|
242
249
|
{
|
|
243
250
|
"id": "create_feishu_group", // 全局唯一语义化 ID
|
|
@@ -263,6 +270,7 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
263
270
|
**注意**paramsSchema 支持以下 4 种参数类型,需要按下面规定的格式进行填充:
|
|
264
271
|
|
|
265
272
|
1. **文本** - 单行或多行文本输入
|
|
273
|
+
|
|
266
274
|
```json
|
|
267
275
|
{
|
|
268
276
|
"type": "string",
|
|
@@ -271,6 +279,7 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
271
279
|
```
|
|
272
280
|
|
|
273
281
|
2. **数组** - 字符串数组(如 ID 列表、标签列表等)
|
|
282
|
+
|
|
274
283
|
```json
|
|
275
284
|
{
|
|
276
285
|
"type": "array",
|
|
@@ -283,6 +292,7 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
283
292
|
```
|
|
284
293
|
|
|
285
294
|
3. **图片** - 图片资源(需指定 format 为 picture)
|
|
295
|
+
|
|
286
296
|
```json
|
|
287
297
|
{
|
|
288
298
|
"type": "string",
|
|
@@ -292,6 +302,7 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
292
302
|
```
|
|
293
303
|
|
|
294
304
|
4. **文件** - 文件资源(需指定 format 为 file)
|
|
305
|
+
|
|
295
306
|
```json
|
|
296
307
|
{
|
|
297
308
|
"type": "string",
|
|
@@ -301,17 +312,20 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
301
312
|
```
|
|
302
313
|
|
|
303
314
|
> **注意**:`format` 为 `file`、`picture` 或 `plugin-file-url` 的字段在 Client 侧调用时均支持直接传入 File/Blob 对象,`capabilityClient` SDK 会自动处理上传;Server 侧 `CapabilityService` 仅支持 URL 字符串。**禁止**Client 侧先通过 dataloom 上传文件拿 URL 再传给插件——dataloom 的 `download_url` 是内部存储路径,插件服务端可能无法访问。
|
|
315
|
+
>
|
|
304
316
|
#### PluginInstanceAIJson(运行时投影 / 工程转化层产物:pluginInstance.ai.json)
|
|
317
|
+
|
|
305
318
|
pluginInstance.ai.json 是从 PluginInstance 配置派生出的运行时插件实例说明(Runtime Spec),用于 Code Agent 动态生成调用代码。
|
|
306
319
|
它包含:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
• 插件实例元数据(id/pluginKey/pluginVersion/name/description)
|
|
321
|
+
• 可执行入口列表 actions[](每个入口包含 key/inputSchema/outputSchema/outputMode)
|
|
322
|
+
• 详细说明 readme
|
|
323
|
+
• type:单入口/多入口(single_action | multi_action)
|
|
311
324
|
|
|
312
325
|
**重要**:模型不能自行猜测某个插件实例有哪些 action、入参/出参结构;在生成调用代码前必须通过工具读取该 pluginInstance.ai.json。
|
|
313
326
|
|
|
314
327
|
PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
328
|
+
|
|
315
329
|
```json
|
|
316
330
|
{
|
|
317
331
|
"type": "multi_action", // 插件实例类型:single_action 表示仅 1 个 action;multi_action 表示多个 action(调用前需选择 actionKey)
|
|
@@ -347,17 +361,21 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
347
361
|
```
|
|
348
362
|
|
|
349
363
|
## 可用的 Plugin
|
|
364
|
+
|
|
350
365
|
```
|
|
351
366
|
{{available_plugins}}
|
|
352
367
|
```
|
|
368
|
+
|
|
353
369
|
说明:先从可用的 PluginInstance 进行选择,如果无法满足需求,看可用的 Plugin,如果有满足的插件,调用插件生成工具进行生成,如果没有,直接拒答。
|
|
354
370
|
|
|
355
371
|
## 可用的 PluginInstance
|
|
372
|
+
|
|
356
373
|
```
|
|
357
374
|
{{available_plugin_instances}}
|
|
358
375
|
```
|
|
359
376
|
|
|
360
377
|
### 使用方式
|
|
378
|
+
|
|
361
379
|
1. **创建/修改 PluginInstance(配置层)**:调用 `plugin_instance` 工具生成/更新单文件 PluginInstance JSON(基于插件表单封装)。
|
|
362
380
|
2. **查询已有 PluginInstance(配置层)**:优先使用可用的 PluginInstance;如仍需核对细节,再读取对应插件实例配置(调用'get_plugin_ai_json')。
|
|
363
381
|
3. **生成运行时代码(调用层)**:
|
|
@@ -366,17 +384,19 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
366
384
|
- 仔细阅读返回的 readme,必须严格遵循里面制定的规则
|
|
367
385
|
|
|
368
386
|
### 典型场景示例
|
|
387
|
+
|
|
369
388
|
- **消息通知类**:封装"发送飞书消息"相关插件为业务插件实例
|
|
370
389
|
- **群组管理类**:封装"创建飞书群组"相关插件为业务插件实例
|
|
371
390
|
- **AI 生成类**:封装"AI 生文/生图/图片理解"相关插件为业务插件实例
|
|
372
391
|
|
|
373
392
|
### 使用限制
|
|
393
|
+
|
|
374
394
|
- PluginInstance 必须通过 `plugin_instance` 工具创建/更新,不支持 agent 直接手改 `server/capabilities/` 下的配置文件
|
|
375
395
|
- 调用前必须通过 `get_plugin_ai_json` 获取权威 schema,禁止猜测入参/出参结构
|
|
376
396
|
- PluginInstance 配置信息存储在 `server/capabilities/` 目录
|
|
377
397
|
- 运行时调用统一走 SDK/Service,不再为每个插件实例预生成固定的 call 文件
|
|
378
|
-
-
|
|
379
|
-
-
|
|
398
|
+
- Server 侧:CapabilityService.load(capabilityId).call(actionKey, input)
|
|
399
|
+
- Client 侧:capabilityClient.load(capabilityId).call(actionKey, input)(流式用 callStream)
|
|
380
400
|
|
|
381
401
|
## PluginInstance 生成约束
|
|
382
402
|
|
|
@@ -441,11 +461,7 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
441
461
|
### 第一步:检查现有 PluginInstance(复用优先)
|
|
442
462
|
|
|
443
463
|
1) 用户描述需求后,优先基于上下文提供的插件实例列表检索是否已存在可复用插件实例。
|
|
444
|
-
2) 若存在候选插件实例但你无法确认其是否满足需求,必须调用 `get_plugin_ai_json` 获取该插件实例的运行时投影(pluginInstance.ai.json
|
|
445
|
-
- `actions[].key`
|
|
446
|
-
- `actions[].inputSchema / outputSchema`
|
|
447
|
-
- `actions[].outputMode`
|
|
448
|
-
来判断是否可复用以及如何调用。
|
|
464
|
+
2) 若存在候选插件实例但你无法确认其是否满足需求,必须调用 `get_plugin_ai_json` 获取该插件实例的运行时投影(pluginInstance.ai.json),根据其中的 `actions[].key`、`actions[].inputSchema / outputSchema`、`actions[].outputMode` 判断是否可复用以及如何调用。
|
|
449
465
|
3) 禁止按旧链路去读取/维护 `server/capabilities/capabilities.json` 来做复用判断。
|
|
450
466
|
|
|
451
467
|
> 结论:**复用判断以插件实例列表 + get_plugin_ai_json 为准**,禁止猜测 action、入参/出参、输出模式。
|
|
@@ -459,8 +475,9 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
459
475
|
- 其他情况:告知用户该需求目前无法满足
|
|
460
476
|
|
|
461
477
|
**强制约束**:
|
|
478
|
+
|
|
462
479
|
- 创建/更新 PluginInstance **必须**通过 `plugin_instance` 工具完成,绝对禁止直接修改 `server/capabilities/` 下的配置文件。
|
|
463
|
-
- UPDATE 场景严禁修改保护字段:`id / pluginKey / pluginVersion / createdAt
|
|
480
|
+
- UPDATE 场景严禁修改保护字段:`id / pluginKey / pluginVersion / createdAt`。
|
|
464
481
|
|
|
465
482
|
### 第三步:生成调用代码
|
|
466
483
|
|
|
@@ -524,6 +541,7 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
524
541
|
用户会用「AI 生文」「AI 生图」「发送飞书消息」等**业务语言**描述需求;这些关键词必须被识别为**待使用或待创建的 PluginInstance**。
|
|
525
542
|
|
|
526
543
|
开发流程:
|
|
544
|
+
|
|
527
545
|
1. 收到需求后,先看可用的 PluginInstance 是否有可以直接使用的插件实例
|
|
528
546
|
2. 若有候选但不确定是否满足,调用 `get_plugin_ai_json` 查看其 actions/schema/outputMode 再决策
|
|
529
547
|
3. 若无,立即调用 `plugin_instance` 工具新建
|
|
@@ -577,6 +595,7 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
577
595
|
> **关键区分**:`formValue` 中配置固定值 ≠ 代码中硬编码。`formValue` 是插件实例的声明式配置,修改不需要改代码;而代码中硬编码的值散落在业务逻辑中,难以维护。
|
|
578
596
|
|
|
579
597
|
当接收人/配置值是动态的,获取途径:
|
|
598
|
+
|
|
580
599
|
1. 通过平台角色 API 获取(如"所有 admin_hr 角色的用户")
|
|
581
600
|
2. 存入应用配置表,通过 API 读取
|
|
582
601
|
3. 通过环境变量注入
|
package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md
CHANGED
|
@@ -58,7 +58,9 @@
|
|
|
58
58
|
### Client 侧调用方式(默认首选)
|
|
59
59
|
|
|
60
60
|
#### 1. 调用前获取权威依据
|
|
61
|
+
|
|
61
62
|
在为某个插件实例生成调用代码前,必须先通过 `get_plugin_ai_json` 工具获取该插件实例的运行时投影(plugin_Instance.ai.json),并以其中信息为准:
|
|
63
|
+
|
|
62
64
|
- `actions[].key`:调用时要传的 `actionKey`
|
|
63
65
|
- `actions[].inputSchema / outputSchema`:入参/出参结构
|
|
64
66
|
- `actions[].outputMode`:`unary | stream`(决定调用与结果处理方式)
|
|
@@ -175,7 +177,7 @@ function readFirstStringField(
|
|
|
175
177
|
|
|
176
178
|
**核心原则**:在插件设计阶段按「原子化拆解」拆分,避免单插件返回多字段 JSON。
|
|
177
179
|
|
|
178
|
-
#####
|
|
180
|
+
##### 推荐:多插件并行流式
|
|
179
181
|
|
|
180
182
|
适用于需求涉及多种输出(标题、正文、图片等),各输出相对独立。
|
|
181
183
|
|
|
@@ -350,6 +352,7 @@ this.somePluginInstanceSideEffect(input).catch(error => {
|
|
|
350
352
|
| 任意(兜底场景) | Server 侧 | `capabilityService.load(id).call(actionKey, input)` |
|
|
351
353
|
|
|
352
354
|
**选择原则**:
|
|
355
|
+
|
|
353
356
|
- 不涉及持久化时,优先在 Client 侧直接调用
|
|
354
357
|
- `outputMode = stream` 时,Client 侧使用 `callStream` 做渐进式渲染
|
|
355
358
|
- 涉及持久化、触发器、敏感凭证、事务编排等场景时,使用 Server 侧
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
在查看本插件的 Action 时,你将同时获得两部分信息:
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
15
|
+
1. **本文档 (README)**: 提供高级指引、业务逻辑、使用场景、重要约束。
|
|
16
|
+
2. **Action 的 `inputSchema` 和 `outputSchema`**: 提供精确的 JSON Schema 格式的输入/输出结构。
|
|
17
17
|
|
|
18
18
|
**请遵循以下原则:**
|
|
19
19
|
|
|
@@ -464,6 +464,7 @@ const columns = [
|
|
|
464
464
|
**2. Formula 字段不支持 filter 和 sum/avg 聚合**
|
|
465
465
|
|
|
466
466
|
同一数据常有两个字段:原始 Number 字段(如 `金额`,单位元)和计算 Formula 字段(如 `金额(万元)`)。聚合和过滤**必须用 Number 字段**,在代码里做单位转换:
|
|
467
|
+
|
|
467
468
|
```typescript
|
|
468
469
|
// ❌ 错误:金额(万元) 是 Formula,不能聚合也不能过滤
|
|
469
470
|
{ fieldName: '金额(万元)', aggregation: 'sum' }
|
|
@@ -479,6 +480,7 @@ const columns = [
|
|
|
479
480
|
与 searchRecords 不同,aggregateQuery 中 `isNot` 会把字段值为空的记录也排除。如果很多记录的该字段为空,结果会远少于预期甚至为 0。
|
|
480
481
|
|
|
481
482
|
解决方案:不在 filter 里用 `isNot` 排除 Text 值,改为在 dimensions 里加上该字段,在结果侧用 if 跳过不要的分组:
|
|
483
|
+
|
|
482
484
|
```typescript
|
|
483
485
|
// ❌ 错误:isNot 会把「目前进展」为空的记录也排掉
|
|
484
486
|
filter: { conditions: [{ fieldName: '目前进展', operator: 'isNot', value: ['已完成'] }] }
|
|
@@ -10,10 +10,10 @@ match-template-name: nestjs-react-fullstack
|
|
|
10
10
|
|
|
11
11
|
## ⚡️ 核心原则 (TL;DR)
|
|
12
12
|
|
|
13
|
-
1.
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
16
|
-
4.
|
|
13
|
+
1. **优先 React 19 新特性**: 用 `use()` 读取异步数据,用 `useActionState` 管理表单,替代繁琐的 `useEffect` + `useState`。
|
|
14
|
+
2. **拒绝冗余 State**: 能计算得到的变量(派生状态),绝不存入 State,直接计算 or `useMemo`。
|
|
15
|
+
3. **事件驱动 > Effects**: 用户交互(点击、提交)产生的逻辑写在事件处理函数中,`useEffect` 仅用于同步外部系统(订阅、DOM)。
|
|
16
|
+
4. **依赖诚实**: `useEffect/useCallback/useMemo` 的依赖数组必须包含所有引用的响应式变量,禁止欺骗 Linter。
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -208,6 +208,7 @@ const UserInfoPanel = () => {
|
|
|
208
208
|
```
|
|
209
209
|
|
|
210
210
|
**注意**:
|
|
211
|
+
|
|
211
212
|
- `lark_user_id` 通过额外异步请求获取,可能晚于 `user_id` 等基础字段就绪
|
|
212
213
|
- 请求失败或用户无对应飞书账号时值为 `undefined`,**必须用条件渲染**
|
|
213
214
|
- `useCurrentUserProfile()` 的返回值里**不存在** `open_id`、`feishu_id`、`openId` 字段——在这个 Hook 的消费代码里飞书 ID 唯一字段名是 `lark_user_id`(仅约束本 Hook;项目其他场景调 spark `id_convert` / 通讯录 API 出现 `open_id` 是正常的)
|
|
@@ -79,30 +79,38 @@ import { ListPlus, Cake, Home, Building, Twitter } from "lucide-react";
|
|
|
79
79
|
2. 预防规则(跨子组件常量前缀化、行内/桶导出二选一)见 `coding-guide` 的「TypeScript 规范 · 命名约定」与「文件命名约定 · 导入导出」
|
|
80
80
|
|
|
81
81
|
### 前端 Dto 类型使用错误
|
|
82
|
+
|
|
82
83
|
**问题描述**:代码中使用的 Dto 类型属性与实际定义不一致,导致 TypeScript 类型检查报错
|
|
83
84
|
|
|
84
85
|
**核心原则**:遇到类型错误,先查 `@client/src/api/gen/types.gen.ts` 确认定义,再修改代码
|
|
85
86
|
|
|
86
87
|
**错误示例**:
|
|
88
|
+
|
|
87
89
|
- 示例1: 赋值时缺失必需属性
|
|
90
|
+
|
|
88
91
|
```
|
|
89
92
|
Property 'dueDate' is missing in type {...} but required in type 'CreateBorrowRecordDto'
|
|
90
93
|
```
|
|
91
94
|
|
|
92
95
|
- 示例2:访问不存在的属性
|
|
96
|
+
|
|
93
97
|
```
|
|
94
98
|
Property 'avatar' does not exist on type 'LotteryParticipantResponseDto'
|
|
95
99
|
```
|
|
96
100
|
|
|
97
101
|
- 示例3:导入不存在的类型
|
|
102
|
+
|
|
98
103
|
```
|
|
99
104
|
Module '"@client/src/api/gen"' has no exported member named 'DiscrepancyResponseDto'
|
|
100
105
|
```
|
|
106
|
+
|
|
101
107
|
**解决步骤**:
|
|
108
|
+
|
|
102
109
|
1. 在 `@client/src/api/gen/types.gen.ts` 中搜索目标Dto实际定义
|
|
103
110
|
2. 确认类型名称是否正确,明确完整定义
|
|
104
111
|
|
|
105
112
|
**检查清单**:
|
|
113
|
+
|
|
106
114
|
- [ ] 已查看 `@client/src/api/gen/types.gen.ts` 中的类型定义
|
|
107
115
|
- [ ] 已对比代码使用与实际定义的差异
|
|
108
116
|
- [ ] 已根据实际定义修正代码
|
|
@@ -181,20 +189,20 @@ Module '"@client/src/api/gen"' has no exported member named 'DiscrepancyResponse
|
|
|
181
189
|
错误信息:服务内部错误
|
|
182
190
|
错误堆栈(可选):Error: 这是测试异常:HelloController.getConfig方法故意抛出的错误\n at HelloController.getConfig (/home/gem/workspace/dist/server/modules/hello/hello.controller.js:17:15)\n at /home/gem/workspace/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
|
|
183
191
|
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
1. 如果错误堆栈存在,优先按照错误堆栈中的相关文件与行列号找到对应文件,读取内容并启发式分析
|
|
193
|
+
2. 如果错误堆栈不存在,按照请求的 URL 找到对应的 controller,检查其中的逻辑,启发式的分析依赖。如发现问题可以直接处理。若仍未发现问题,可以在 controller 抛出的错误对象上增加 `message` 属性,改变 message 内容方便 debug。你可以在增加完之后让用户重新请求触发问题。
|
|
186
194
|
|
|
187
195
|
```typescript
|
|
188
196
|
try {
|
|
189
|
-
|
|
197
|
+
// some logic
|
|
190
198
|
} catch (err) {
|
|
191
199
|
// 后端异常必须在后端打印日志
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
this.logger.error('...')
|
|
201
|
+
// 后端异常同时需要抛出到前端,方便修复
|
|
202
|
+
// 构造为 http-errors compatible 的对象
|
|
203
|
+
err.statusCode = 500;
|
|
204
|
+
err.message = err.stack; // 抛出 stack 信息,保障有足够的错误内容透出
|
|
205
|
+
throw err;
|
|
198
206
|
}
|
|
199
207
|
```
|
|
200
208
|
|
|
@@ -214,7 +222,6 @@ PostgresError: connection verification failed for endpoint "<pg-endpoint>": expi
|
|
|
214
222
|
|
|
215
223
|
**修复方案**:**重新运行 `npm run dev`** 即可。启动脚本会重新拉取并注入最新的 PG connection string 到 `.env.local`,新进程读取到的就是有效连接串。不要去改后端代码或 ORM 配置。
|
|
216
224
|
|
|
217
|
-
|
|
218
225
|
**注意**:
|
|
219
226
|
|
|
220
227
|
- 不要尝试手工编辑 `.env.local` 里的 PG connection string,连接串由启动流程统一管理
|
|
@@ -17,7 +17,7 @@ description: "项目全局编码规范,必须在任何代码编写、阅读、
|
|
|
17
17
|
|
|
18
18
|
## 后端结构 (`server/`)
|
|
19
19
|
|
|
20
|
-
基于 MVCS 架构,**禁止新增一级目录**。
|
|
20
|
+
基于 MVCS 架构,**禁止新增一级目录**。
|
|
21
21
|
|
|
22
22
|
```
|
|
23
23
|
server/ # 符合 NestJS 项目基本规范
|
|
@@ -31,7 +31,7 @@ server/ # 符合 NestJS 项目基本规范
|
|
|
31
31
|
│ ├── hello.controller.ts # controller 示例
|
|
32
32
|
│ ├── hello.module.ts # module 示例
|
|
33
33
|
│ └── hello.service.ts # service 示例(可选)
|
|
34
|
-
├── database/ # Drizzle ORM 数据库相关。表结构变更需走
|
|
34
|
+
├── database/ # Drizzle ORM 数据库相关。表结构变更需走 npm run gen:db-schema 流程,不要手写 SQL DDL。
|
|
35
35
|
│ ├── schema.ts # Drizzle ORM 数据库 Schema 定义。必须从该文件导入数据库类型,禁止自行编写或修改 schema 文件 — 应通过 drizzle migration 生成。
|
|
36
36
|
└── common/ # 共享工具和接口
|
|
37
37
|
│ ├── filters/ # 通用错误处理。
|
|
@@ -121,7 +121,7 @@ shared/ # 前后端共享的目录
|
|
|
121
121
|
- **将风格问题视为编码错误**:项目使用 `@eslint/js` + `typescript-eslint` 推荐配置
|
|
122
122
|
- **ESLint 规范**(`@eslint/js` + `typescript-eslint`):
|
|
123
123
|
- 正则控制字符用 unicode 标志:`/\x1b\[/u`
|
|
124
|
-
- 避免无意义转义:字符串 `"'"` 非 `"\'"`, 正则 `[.]` 非 `[\.]
|
|
124
|
+
- 避免无意义转义:字符串 `"'"` 非 `"\'"`, 正则 `[.]` 非 `[\.]`
|
|
125
125
|
- switch-case 中声明变量用 `{}` 包裹作用域
|
|
126
126
|
|
|
127
127
|
## 依赖使用规范
|
|
@@ -149,11 +149,13 @@ shared/ # 前后端共享的目录
|
|
|
149
149
|
## 质量保障流程
|
|
150
150
|
|
|
151
151
|
修改代码后:
|
|
152
|
+
|
|
152
153
|
1. 运行代码检查工具检查语法错误
|
|
153
154
|
2. 改动服务端代码 → 进行接口测试,确保新增接口测试通过
|
|
154
155
|
3. 提交前 **必须** 读取相关日志确认无错误(服务端: server/server-devserver 日志;客户端: client-devserver 日志)
|
|
155
156
|
|
|
156
157
|
如果用户反馈编译失败、服务无法启动:
|
|
158
|
+
|
|
157
159
|
1. 跑 `tsc --noEmit` / `eslint` 等代码检查
|
|
158
160
|
2. 查看 `logs/server.log` / `logs/server.std.log` / `logs/dev.log` 找具体错误
|
|
159
161
|
3. dev 服务无响应:在跑 `npm run dev` 的终端 Ctrl+C 后重新启动即可
|
|
@@ -238,6 +240,7 @@ const query = conditions.length > 0
|
|
|
238
240
|
```
|
|
239
241
|
|
|
240
242
|
- Count 查询:
|
|
243
|
+
|
|
241
244
|
```typescript
|
|
242
245
|
// 简单 count
|
|
243
246
|
const result = await this.db.select({ count: count() }).from(users).where(eq(users.status, "active"));
|
|
@@ -278,8 +281,8 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
278
281
|
### 数据库使用强约束
|
|
279
282
|
|
|
280
283
|
- 仅通过 schema.ts 暴露的客户端和类型读写,禁止手写 SQL/临时类型
|
|
281
|
-
-
|
|
282
|
-
- 连接失败/SSL 错误:先检查 `.env.local` 里 `SUDA_DATABASE_URL` 是否正确(走过 `lark-cli apps env
|
|
284
|
+
- **schema变更流程(CRITICAL)**:变更数据表结构后,运行 npm run gen:db-schema 重新生成 schema.ts → **立即重新读取 schema.ts** → 再编写业务代码。跳过重新读取直接编码是 TS2339 批量错误的主要根因
|
|
285
|
+
- 连接失败/SSL 错误:先检查 `.env.local` 里 `SUDA_DATABASE_URL` 是否正确(走过 `lark-cli apps +env-pull`),再排查网络/代理
|
|
283
286
|
|
|
284
287
|
## API 规范
|
|
285
288
|
|
|
@@ -297,12 +300,14 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
297
300
|
5. **路由注册验证**:创建新 Controller 后必须用接口测试工具验证路由是否生效。**遇到 404 排查路径**:① 检查 `@Controller(...)` 是否以 `api/` 或 `openapi/` 开头 ② 检查 Module 是否在 `app.module.ts` 注册 ③ 检查静态路由是否在动态路由 `/:id` 之前
|
|
298
301
|
6. **@Query/@Param 类型转换**:默认 string,必须在 controller 层手动转换(如 `parseInt(limit, 10)`)
|
|
299
302
|
7. **写操作加 @NeedLogin**:POST/PUT/PATCH/DELETE 接口显式加 `@NeedLogin()` 装饰器:
|
|
303
|
+
|
|
300
304
|
```typescript
|
|
301
305
|
import { NeedLogin } from "@lark-apaas/fullstack-nestjs-core";
|
|
302
306
|
@NeedLogin()
|
|
303
307
|
@Post()
|
|
304
308
|
async createItem(@Req() req, @Body() dto) { ... }
|
|
305
309
|
```
|
|
310
|
+
|
|
306
311
|
8. **OpenAPI 文档同步**:改动 `*.openapi.controller.ts` 或其引用的 interface / service 返回值 / schema 字段时,加载 `openapi-guide` skill,同步更新 `docs/openapi.json`
|
|
307
312
|
|
|
308
313
|
## 异常处理
|
|
@@ -336,6 +341,7 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
336
341
|
本地开发态**暂不支持** PluginInstance / capability 类插件能力的创建与调用 — 这套链路依赖云端 agent 工具(`plugin_instance` / `get_plugin_ai_json`)与平台下发的 `server/capabilities/` 配置,本地没有等价物。
|
|
337
342
|
|
|
338
343
|
如果业务用到飞书多维表格、AI 生文/翻译/分类、消息推送等"插件能力"覆盖的场景:
|
|
344
|
+
|
|
339
345
|
- **数据存储/查询类**(多维表格 CRUD、列表分页、聚合统计):优先用本应用自带的 Postgres + Drizzle,数据可在云端运行时再同步飞书 Base
|
|
340
346
|
- **AI / 飞书消息类**:在云端发布后由平台插件链路承接;本地开发时可先用接口 mock 桩占位
|
|
341
347
|
|
|
@@ -346,11 +352,13 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
346
352
|
## 分页最佳实践
|
|
347
353
|
|
|
348
354
|
### 传统分页(后台管理表格、跳页场景)
|
|
355
|
+
|
|
349
356
|
- 1-indexed page,`@Max()` 限制 pageSize
|
|
350
357
|
- 响应含 items/total/page/pageSize,page 超出返回空数组
|
|
351
358
|
- 深分页性能差,大数据集考虑游标分页
|
|
352
359
|
|
|
353
360
|
### 游标分页(移动端/无限滚动,优先推荐)
|
|
361
|
+
|
|
354
362
|
- 使用「唯一 + 可排序」字段作为游标排序依据,默认 创建时间+id 降序
|
|
355
363
|
- cursor(首次空) + limit(默认12, @Max(50))
|
|
356
364
|
- 响应仅 items/nextCursor/hasMore,不返回 total
|
|
@@ -494,6 +502,7 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
494
502
|
- **禁止 React.lazy**:严禁异步加载路由组件
|
|
495
503
|
- **页面跳转**:必须用 `NavLink`/`Link`/`useNavigate`,**严禁 `window.location.href`**
|
|
496
504
|
- **分享链接/二维码**(CRITICAL):应用部署后 URL 与本地路由不同,**必须**用 `resolveAppUrl` 转换:
|
|
505
|
+
|
|
497
506
|
```typescript
|
|
498
507
|
import { resolveAppUrl } from '@lark-apaas/client-toolkit/utils/resolveAppUrl';
|
|
499
508
|
const shareUrl = resolveAppUrl(`/detail/${id}`); // ✅ 路由路径→完整 URL
|
|
@@ -501,6 +510,7 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
501
510
|
resolveAppUrl('https://other-site.com/page'); // ✅ 外部链接原样返回
|
|
502
511
|
// ❌ 严禁自行拼接:`${window.location.origin}/detail/${id}`
|
|
503
512
|
```
|
|
513
|
+
|
|
504
514
|
- **首页路由**:必须含指向 `/` 的 index 路由
|
|
505
515
|
- **路由唯一性**:每页一个唯一路由
|
|
506
516
|
- **导航**:NavLink + active 高亮,路径必须与 `app.tsx` 路由精确对应。全局 Layout 中使用 `useAppInfo`/`useCurrentUserProfile` 获取站点和用户信息
|