@lark-apaas/coding-steering 0.1.51 → 0.1.52-alpha.20260912115620
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 +6 -6
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +0 -2
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +15 -39
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +5 -37
- package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +0 -2
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +22 -37
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +1 -1
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +5 -37
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/coding-steering",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52-alpha.20260912115620",
|
|
4
4
|
"description": "Stack-specific steering content for miaoda-coding templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"steering"
|
|
8
8
|
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"lint:md": "markdownlint 'steering/**/*.md' --ignore 'steering/**/skills/**' --ignore 'steering/**/skills_common/**' --ignore 'steering/**/skills_local/**'"
|
|
11
|
+
},
|
|
9
12
|
"devDependencies": {
|
|
10
13
|
"markdownlint-cli": "^0.47.0"
|
|
11
14
|
},
|
|
@@ -17,8 +20,5 @@
|
|
|
17
20
|
"miaoda",
|
|
18
21
|
"coding-steering"
|
|
19
22
|
],
|
|
20
|
-
"license": "MIT"
|
|
21
|
-
|
|
22
|
-
"lint:md": "markdownlint 'steering/**/*.md' --ignore 'steering/**/skills/**' --ignore 'steering/**/skills_common/**' --ignore 'steering/**/skills_local/**'"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|
package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md
CHANGED
|
@@ -21,7 +21,6 @@ match-template-name: nestjs-react-fullstack
|
|
|
21
21
|
## 使用注意
|
|
22
22
|
- **入口边界**:本 SDK 是**应用前端代码**读写应用存储的唯一入口。Agent 自己在对话 / 开发中上传、灌数据或调试文件,先加载应用文件存储操作 skill(按「应用文件存储 / 文件上传 / 文件下载」召回),用其 CLI 命令操作,具体命令一律以该 skill 为准——二者操作同一个应用存储桶,但**该 CLI 命令只供 Agent 在沙箱终端使用,禁止写进页面代码**,页面里一律用本 SDK。
|
|
23
23
|
- @lark-apaas/client-toolkit/dataloom 这个SDK只适用于前端调用,禁止在服务端调用
|
|
24
|
-
- **沙箱 dev 限制**:`uploadFile` 打的 `/app/<appId>/__runtime__/api/v1/storage/object/<bucket>/pre_upload` **在沙箱 dev 下恒 404**,只有发布态可用。这是环境限制不是代码缺陷,按本 skill 写法即为正确,不要为它改代码;开发期要验证上传链路改走接口测试直接打后端接口,或复用库里已有的文件 URL。详见 coding-guide「沙箱 dev 不提供平台 runtime 接口」。
|
|
25
24
|
- 上传成功后,最重要的返回值是 `data.download_url`。需要将此URL保存到你的业务数据库中
|
|
26
25
|
- **⚠️ 场景区分(重要)**:`dataloom.storage` 仅适用于需要持久化存储文件或获取 `download_url` 保存到数据库的场景。如果文件仅作为插件输入(传给 `capabilityClient`),**必须直接传 File/Blob 对象,禁止先走 dataloom 上传再传 URL**;插件调用(capability)不属于 dataloom,详见 plugin-guide
|
|
27
26
|
- **download_url 格式说明**:`download_url` 返回的可能是相对路径(如 `/spark/app/.../storage/object/...`),这是正常行为。**禁止**在前面拼接 `window.location.origin` 或其他域名前缀,平台会自动解析相对路径。直接使用原始值即可。
|
|
@@ -14,8 +14,6 @@ match-template-name: nestjs-react-fullstack
|
|
|
14
14
|
|
|
15
15
|
> **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
|
|
16
16
|
>
|
|
17
|
-
> **沙箱 dev 限制**:本 skill 的能力底层打 `/app/<appId>/__runtime__/api/v1/account/*`,这类端点**在沙箱 dev 下打不通**(POST 404;GET 落 SPA fallback 返回 200 + index.html),只有发布态可用。`useCurrentUserProfile()` 打的是 `account/login/user`;UserSelect / DepartmentSelect / UserDisplay 打 `search_user` / `list_users` / `user_profile` / `search_department`。看到 404 或选人列表为空时,禁止改 `business-ui` 组件、禁止加兜底请求或换自研控件绕过;开发期要验证「按当前用户过滤 / 展示姓名」改由服务端出数据(`req.userContext` / `AuthNPaasService`)。详见 coding-guide「沙箱 dev 不提供平台 runtime 接口」。
|
|
18
|
-
>
|
|
19
17
|
> **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
|
|
20
18
|
|
|
21
19
|
## 怎么选(决策指引)
|
|
@@ -155,7 +155,7 @@ shared/ # 前后端共享的目录
|
|
|
155
155
|
|
|
156
156
|
## 依赖使用规范
|
|
157
157
|
|
|
158
|
-
1. **子包完整性检查**:部分库有多个子包(如 `@
|
|
158
|
+
1. **子包完整性检查**:部分库有多个子包(如 `@dnd-kit/core` + `@dnd-kit/sortable` + `@dnd-kit/utilities`),添加 import 后必须确认 package.json 中包含所有需要的子包
|
|
159
159
|
2. **禁止安装时需现场编译的 native addon**(`node-gyp rebuild`、`binding.gyp`、需要系统头文件或编译器)。依赖按平台内置能力 / 浏览器能力 → 纯 JS 或 WASM → 有预编译二进制的 native addon 的顺序选型
|
|
160
160
|
3. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
|
|
161
161
|
|
|
@@ -180,36 +180,6 @@ shared/ # 前后端共享的目录
|
|
|
180
180
|
- `.eslintrc.js`、`.prettierrc`
|
|
181
181
|
- `node_modules/`、`.git/`、`dist/`、`build/`
|
|
182
182
|
|
|
183
|
-
### 沙箱 dev 不提供平台 runtime 接口(`__runtime__`)
|
|
184
|
-
|
|
185
|
-
dev server 只代理 `/api`、`/openapi`、`/__innerapi__`(外加 legacy 的 `/af/p/:appId/api`、`/af/p/:appId/__innerapi__`)。`/app/<appId>/__runtime__/*` 这一整类**平台 runtime 接口在沙箱 dev 下打不通**,只有发布态才由网关接管。
|
|
186
|
-
|
|
187
|
-
**症状分两种,按请求方法区分**:POST 直接 404;**GET 会落到 Vite 的 SPA fallback,拿回 200 + `index.html`**,业务侧表现为「返回了一坨 HTML,JSON 解析失败」——见下方「问题排查指引」里「API 测试返回 HTML 内容时」那条,两者是同一回事。
|
|
188
|
-
|
|
189
|
-
已知受影响端点:
|
|
190
|
-
|
|
191
|
-
| 端点 | 方法 | 谁会打到它 |
|
|
192
|
-
|------|------|-----------|
|
|
193
|
-
| `api/v1/permissions/roles` | GET | `AppContainer` **每次加载应用自动打**,不需要你写任何代码 |
|
|
194
|
-
| `api/v1/observability/{logs,traces,metrics}/collect`、`current_server_timestamp` | POST/GET | 平台埋点上报,**自动打** |
|
|
195
|
-
| `api/v1/account/login/user` | POST | `useCurrentUserProfile()` → `authClient.session.getUserInfo()` |
|
|
196
|
-
| `api/v1/account/search_user`、`list_users`、`user_profile`、`search_department`、`convert_lark_user` | POST / `user_profile` 是 GET | `business-ui` 的 UserSelect / DepartmentSelect / UserDisplay |
|
|
197
|
-
| `api/v1/account/search`、`api/v1/account/chat/list_chats` | POST | ChatSelect 选群组件 |
|
|
198
|
-
| `api/v1/studio/user/profile` | GET | `getUserProfile()` |
|
|
199
|
-
| `api/v1/storage/object/<bucket>/pre_upload` | POST | `dataloom.storage.uploadFile()` 前端上传 |
|
|
200
|
-
| `api/v1/studio/plugins/tmp_files/acquire_upload_url`、`acquire_download_url` | POST | `capabilityClient` 的文件类入参 |
|
|
201
|
-
|
|
202
|
-
> `useCurrentUserProfile()` 打的是 `account/login/user`,**不是** `user_profile`/`search_user` 那一排;它另外还打一个 `GET /api/authnpaas/lark-user-id`,那条走 `/api`、被代理、正常。
|
|
203
|
-
|
|
204
|
-
**这是环境限制,不是应用缺陷,也不是组件坏了。** 看到这些 404 / HTML 响应时:
|
|
205
|
-
|
|
206
|
-
- **禁止**改 `client/src/components/business-ui/**`(平台保护文件)、**禁止**给组件加兜底请求或改用自研选人控件绕过——发布后这些端点是正常的,绕过写法反而是错的
|
|
207
|
-
- 需要在开发期验证「按当前用户过滤」「展示用户姓名」这类逻辑:改由服务端出数据。服务端的 `req.userContext` 和 `AuthNPaasService` 由网关注入,**不受此限制**,始终可用(见 `user-identity` / `contacts-service` skill)。`useCurrentUserProfile()` 在这里长期停在加载态就是这个原因
|
|
208
|
-
- 需要在开发期验证上传链路:改用接口测试直接打后端接口,或复用库里已有的文件 URL,不要在浏览器里点上传
|
|
209
|
-
- 依赖上述端点的 E2E 用例不要写成浏览器交互 Case,改走接口层往返验证
|
|
210
|
-
|
|
211
|
-
另外,`UserSelect` 弹层是**搜索驱动**的:不输关键词时列表为空属预期行为,不是接口挂了。
|
|
212
|
-
|
|
213
183
|
## 质量保障流程
|
|
214
184
|
|
|
215
185
|
通用提交前检查(代码检查 / 接口测试 / 读日志确认无错误)见系统任务流程约束;本栈具体落点:
|
|
@@ -590,9 +560,7 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
590
560
|
|
|
591
561
|
1. 使用 API 测试工具测试不依赖用户信息的接口
|
|
592
562
|
2. 积极使用 `read_logs` 工具,如无有效日志可增加 logger 打印
|
|
593
|
-
3. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController
|
|
594
|
-
4. 请求路径含 `__runtime__` 的 404(POST)或 200 + HTML(GET):属沙箱 dev 环境限制,不是应用缺陷,见「沙箱 dev 不提供平台 runtime 接口」
|
|
595
|
-
5. 服务端启动即崩、`/api` 全部 502 且报 `UnknownDependenciesException ... argument Function at index [0]`:查构造函数有没有空参 `@Inject()`(见 plugin-guide),不要先去改 Module 的 imports / providers
|
|
563
|
+
3. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController
|
|
596
564
|
|
|
597
565
|
---
|
|
598
566
|
|
|
@@ -613,8 +581,6 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
613
581
|
{% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
|
|
614
582
|
- **富文本**: `business-ui/tiptap-editor`(阅读 README.md)
|
|
615
583
|
- **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
|
|
616
|
-
{% else %}
|
|
617
|
-
- **Markdown 渲染**: `components/ui/markdown`(react-markdown + remark-gfm,内置 prose 排版)
|
|
618
584
|
{% endif %}
|
|
619
585
|
|
|
620
586
|
## API 请求
|
|
@@ -652,8 +618,6 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
652
618
|
{% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
|
|
653
619
|
| TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
|
|
654
620
|
| Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
|
|
655
|
-
{% else %}
|
|
656
|
-
| Markdown | `components/ui/markdown` | Markdown 渲染(react-markdown + remark-gfm) |
|
|
657
621
|
{% endif %}
|
|
658
622
|
|
|
659
623
|
### 组件 Skill 召回规则(强制执行)
|
|
@@ -803,6 +767,11 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
803
767
|
- 用户上传的媒体要在页面展示 → 用 dataloom storage URL(`generateDownloadUrlFromFilePath`,见 client-builtins-file-storage-service),不要先下载进 `public/` 再硬编码路径。
|
|
804
768
|
- 项目自带的静态资产 → 放 `client/src/assets/` 用 ES import(`import logoUrl from '@/assets/x.svg'`),构建管线自动带前缀;新增 `.svg`/`.mp4` 等类型须在 `client/src/types/global.d.ts` 补 `declare module` 声明。
|
|
805
769
|
|
|
770
|
+
### 文件下载与受限二进制后缀
|
|
771
|
+
|
|
772
|
+
- 不要为了占位下载伪造最终 `.pdf` / `.xlsx` / `.docx` / `.zip` 等受限二进制后缀文件,也不要让 Worker 用 Write/Edit 直接创建或修改这些最终文件。
|
|
773
|
+
- 遇到受限二进制后缀、又没有 Bash Worker 时,不要直接生成最终文件。优先改成前端运行时下载(如 Blob / jsPDF / xlsx)、服务端生成下载内容、平台存储,或让 Worker 只写 `tmp/<name>.asset.txt` 这类可编辑的中间文本/字节源,再以 PARTIAL 交给父级用 Bash 复制、转换并校验。
|
|
774
|
+
|
|
806
775
|
### 排查资源 404
|
|
807
776
|
|
|
808
777
|
排查 HTTP 404 时**必须读响应体**(用 `curl` 而非 `curl -I`,HEAD 只返回头)——dev server 的 404 响应体通常已给出正确地址指引(如 did-you-mean 提示),据此一步纠偏,不要凭猜测改动。
|
|
@@ -817,8 +786,15 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
817
786
|
| 验证 | zod |
|
|
818
787
|
| 工具函数 | lodash |
|
|
819
788
|
| 样式 | clsx |
|
|
789
|
+
| Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
|
|
790
|
+
| PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
|
|
791
|
+
| 文件上传 | react-dropzone |
|
|
792
|
+
| 二维码 | qrcode.react |
|
|
820
793
|
| 用户反馈 | sonner |
|
|
821
|
-
|
|
794
|
+
| 拖拽 | @dnd-kit/core |
|
|
795
|
+
| 数字动画 | react-countup |
|
|
796
|
+
| Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
|
|
797
|
+
| 3D 场景 | cobe |
|
|
822
798
|
|
|
823
799
|
## 滚动分页最佳实践
|
|
824
800
|
|
|
@@ -23,7 +23,6 @@ gate-tools:
|
|
|
23
23
|
| 长耗时 AI 结果 | 大体量/多字段/多份/多语言/文件或多模态串联等结构信号命中时,优先前端 `callStream` 渐进展示;需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时使用后端任务记录 + 状态查询 + 结果读取;禁止单个 HTTP 请求等待完整结果后才返回 |
|
|
24
24
|
| capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
|
|
25
25
|
| CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
|
|
26
|
-
| CapabilityService 注入 | `@Inject(CapabilityService) private readonly capabilityService: CapabilityService`(**禁止空参 `@Inject()`**,会导致启动崩溃 502;业务 Module 无需 imports) |
|
|
27
26
|
|
|
28
27
|
## 必读 references
|
|
29
28
|
|
package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md
CHANGED
|
@@ -87,18 +87,12 @@
|
|
|
87
87
|
#### call / callStream 函数签名
|
|
88
88
|
|
|
89
89
|
```typescript
|
|
90
|
-
//
|
|
91
|
-
.
|
|
92
|
-
.callStream<T = unknown>(actionKey: string, params?: Record<string, unknown>): AsyncIterable<T> // 流式
|
|
90
|
+
.call(actionKey: string, input: object) // 非流式,返回 Promise<output>
|
|
91
|
+
.callStream(actionKey: string, input: object) // 流式,返回 AsyncIterable<chunk>
|
|
93
92
|
```
|
|
94
93
|
|
|
95
94
|
- **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
|
|
96
|
-
- **第二个参数 `
|
|
97
|
-
- 上面是**前端**签名,带泛型。**服务端 `CapabilityService` 的 `call` / `callStream` / `callStreamWithEvents` 都没有泛型**,两者不通用,详见「Server 侧调用方式」
|
|
98
|
-
|
|
99
|
-
> **`Record<string, unknown>` 不接 `interface` 声明的对象**(interface 没有隐式索引签名),直接传会撞
|
|
100
|
-
> `Argument of type 'XxxInput' is not assignable to parameter of type 'Record<string, unknown>'. Index signature for type 'string' is missing in type 'XxxInput'.`
|
|
101
|
-
> 三种正确写法:① 直接传内联对象字面量;② 入参类型用 `type XxxInput = { ... }` 而非 `interface`(type 别名有隐式索引签名);③ 已有 interface 时在调用点显式 `input as unknown as Record<string, unknown>`。**不要为此改用 `as any`。**
|
|
95
|
+
- **第二个参数 `input`**:必须是对象,结构符合 `actions[].inputSchema`
|
|
102
96
|
|
|
103
97
|
```typescript
|
|
104
98
|
// ❌ 错误:把参数 JSON.stringify 后当作 actionKey
|
|
@@ -312,49 +306,23 @@ import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';
|
|
|
312
306
|
export class XxxService {
|
|
313
307
|
private readonly logger = new Logger(XxxService.name);
|
|
314
308
|
|
|
315
|
-
// 注入 token 必须显式写成 @Inject(CapabilityService)
|
|
316
309
|
constructor(
|
|
317
|
-
@Inject(
|
|
310
|
+
@Inject() private readonly capabilityService: CapabilityService,
|
|
318
311
|
) {}
|
|
319
312
|
}
|
|
320
313
|
```
|
|
321
314
|
|
|
322
|
-
**禁止空参 `@Inject()`**。本 stack 的 `nest-cli.json` 用 `builder: swc`,空参 `@Inject()` 会把注入 token 置为 undefined,Nest 回退读 `design:type` 拿到 `Function`,服务端启动即崩溃、所有 `/api` 请求返回 502:
|
|
323
|
-
|
|
324
|
-
```
|
|
325
|
-
UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?).
|
|
326
|
-
Please make sure that the argument Function at index [0] is available in the XxxModule context.
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
见到这段报错先查构造函数有没有空参 `@Inject()`,不要去改 Module 的 `imports` / `providers`。
|
|
330
|
-
|
|
331
|
-
**业务 Module 不需要注册 CapabilityModule**。`PlatformModule` 是 `@Global()` 且已把 `CapabilityModule` 放进 `exports`,`CapabilityService` 全局可注入。不要在业务 Module 里写 `imports: [CapabilityModule.forRoot(...)]` 或 `imports: [PlatformModule.forRoot()]`。
|
|
332
|
-
|
|
333
|
-
同一规则适用于从 `@lark-apaas/fullstack-nestjs-core` 注入的其他平台服务(`AuthNPaasService`、`FileService` 等)。
|
|
334
|
-
|
|
335
315
|
#### 3. 调用示例
|
|
336
316
|
|
|
337
|
-
服务端 `CapabilityExecutor` 的**三个方法全都没有泛型参数**(`@lark-apaas/nestjs-capability`):
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
call(actionName: string, input: unknown, context?: Partial<PluginActionContext>): Promise<unknown>;
|
|
341
|
-
callStream(actionName: string, input: unknown, context?): AsyncIterable<unknown>;
|
|
342
|
-
callStreamWithEvents(actionName: string, input: unknown, context?): AsyncIterable<StreamEvent<unknown>>;
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
写 `.call<T>(...)` / `.callStream<T>(...)` / `.callStreamWithEvents<T>(...)` 都会报「应有 0 个类型参数,但获得 1 个」。泛型只存在于前端 `capabilityClient`。注意服务端 `input` 是**必传**(没有 `?`),且类型是 `unknown` 而非前端的 `Record<string, unknown>`。服务端接 `unknown` 后在运行时收窄,禁止 `as any`:
|
|
346
|
-
|
|
347
317
|
```typescript
|
|
348
318
|
const inputParams = {
|
|
349
319
|
// 严格按 get_plugin_ai_json.actions[].inputSchema 构造
|
|
350
320
|
};
|
|
351
321
|
|
|
352
322
|
try {
|
|
353
|
-
const output
|
|
323
|
+
const output = await this.capabilityService
|
|
354
324
|
.load('')
|
|
355
325
|
.call('', inputParams);
|
|
356
|
-
// 运行时收窄后再取字段,例如:
|
|
357
|
-
// if (typeof output === 'object' && output !== null && 'content' in output) { ... }
|
|
358
326
|
return output;
|
|
359
327
|
} catch (error) {
|
|
360
328
|
this.logger.error('pluginInstance call failed', {
|
|
@@ -14,8 +14,6 @@ match-template-name: nestjs-react-fullstack
|
|
|
14
14
|
|
|
15
15
|
> **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
|
|
16
16
|
>
|
|
17
|
-
> **本地 dev 限制**:本 skill 的能力底层打 `/app/<appId>/__runtime__/api/v1/account/*`。这类端点只有在 `npm run dev:local` 成功 `env pull` 到 `MIAODA_DEV_PLATFORM_BASE`(或 `SANDBOX_PUBLIC_URL`)时才由 dev-proxy 反代到远端沙箱;**env pull 失败或未装 lark-cli 时该反代不挂**(启动脚本只 warn 不中断),表现为 POST 404 / GET 返回 200 + index.html。`useCurrentUserProfile()` 打的是 `account/login/user`;UserSelect / DepartmentSelect / UserDisplay 打 `search_user` / `list_users` / `user_profile` / `search_department`。先确认 `.env.local` 里有 `MIAODA_DEV_PLATFORM_BASE`;确实缺失时禁止改 `business-ui` 组件或换自研控件绕过,改由服务端出数据(`req.userContext` / `AuthNPaasService`)。
|
|
18
|
-
>
|
|
19
17
|
> **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
|
|
20
18
|
|
|
21
19
|
## 怎么选(决策指引)
|
|
@@ -129,7 +129,7 @@ shared/ # 前后端共享的目录
|
|
|
129
129
|
|
|
130
130
|
1. 优先使用项目已有依赖,仅在无法实现时安装新依赖
|
|
131
131
|
2. 使用前先查看 `package.json` 确保依赖已存在
|
|
132
|
-
3. **子包完整性检查**:部分库有多个子包(如 `@
|
|
132
|
+
3. **子包完整性检查**:部分库有多个子包(如 `@dnd-kit/core` + `@dnd-kit/sortable` + `@dnd-kit/utilities`),添加 import 后必须确认 package.json 中包含所有需要的子包
|
|
133
133
|
4. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
|
|
134
134
|
|
|
135
135
|
## 文件命名约定
|
|
@@ -147,39 +147,6 @@ shared/ # 前后端共享的目录
|
|
|
147
147
|
- `/api` 和 `/openapi` 代理已配置,前端 TS 严格模式已禁用,后端已启用
|
|
148
148
|
- Rspack 用于构建,已配置好,**禁止修改**
|
|
149
149
|
|
|
150
|
-
### 平台 runtime 接口(`__runtime__`)在本地要靠 env pull 才通
|
|
151
|
-
|
|
152
|
-
dev server 自己只代理 `/api`、`/openapi`、`/__innerapi__`。`/app/<appId>/__runtime__/*` 这一整类平台 runtime 接口走的是另一条路:`npm run dev:local` 会先跑 `lark-cli apps +env-pull` 把沙箱身份/凭证拉进 `.env.local`,其中的 `MIAODA_DEV_PLATFORM_BASE`(或 legacy `SANDBOX_PUBLIC_URL`)才让 dev-proxy 把 `__runtime__` 反代到远端沙箱。
|
|
153
|
-
|
|
154
|
-
**env pull 失败、未装 lark-cli、或 `.spark/meta.json` 缺 app_id 时,启动脚本只 warn 不中断**,反代就不会挂上。此时这些端点的症状是:**POST 返回 404;GET 落到 Vite 的 SPA fallback,返回 200 + `index.html`**(业务侧表现为「返回一坨 HTML,JSON 解析失败」)。
|
|
155
|
-
|
|
156
|
-
常踩到的端点:
|
|
157
|
-
|
|
158
|
-
| 端点 | 方法 | 谁会打到它 |
|
|
159
|
-
|------|------|-----------|
|
|
160
|
-
| `api/v1/permissions/roles` | GET | `AppContainer` 每次加载应用自动打 |
|
|
161
|
-
| `api/v1/observability/{logs,traces,metrics}/collect`、`current_server_timestamp` | POST/GET | 平台埋点上报,自动打 |
|
|
162
|
-
| `api/v1/account/login/user` | POST | `useCurrentUserProfile()` → `authClient.session.getUserInfo()` |
|
|
163
|
-
| `api/v1/account/search_user`、`list_users`、`user_profile`、`search_department`、`convert_lark_user` | POST / `user_profile` 是 GET | `business-ui` 的 UserSelect / DepartmentSelect / UserDisplay |
|
|
164
|
-
| `api/v1/account/search`、`api/v1/account/chat/list_chats` | POST | ChatSelect 选群组件 |
|
|
165
|
-
| `api/v1/studio/user/profile` | GET | `getUserProfile()` |
|
|
166
|
-
| `api/v1/storage/object/<bucket>/pre_upload` | POST | `dataloom.storage.uploadFile()` 前端上传 |
|
|
167
|
-
| `api/v1/studio/plugins/tmp_files/acquire_upload_url`、`acquire_download_url` | POST | `capabilityClient` 的文件类入参 |
|
|
168
|
-
|
|
169
|
-
> `useCurrentUserProfile()` 打的是 `account/login/user`,**不是** `user_profile`/`search_user` 那一排;它另外还打一个 `GET /api/authnpaas/lark-user-id`,那条走 `/api`、被代理、正常。
|
|
170
|
-
|
|
171
|
-
排查顺序:先看 `.env.local` 里有没有 `MIAODA_DEV_PLATFORM_BASE`,没有就重跑 `env pull`。确认拉不到时:
|
|
172
|
-
|
|
173
|
-
- **禁止**改 `client/src/components/business-ui/**`(平台保护文件)、**禁止**给组件加兜底请求或改用自研选人控件绕过——发布后这些端点是正常的,绕过写法反而是错的
|
|
174
|
-
- 需要验证「按当前用户过滤」「展示用户姓名」这类逻辑:改由服务端出数据。服务端的 `req.userContext` 和 `AuthNPaasService` 由网关注入,不受此限制(见 `user-identity` / `contacts-service` skill)。`useCurrentUserProfile()` 长期停在加载态就是这个原因
|
|
175
|
-
- 需要验证上传链路:改用接口测试直接打后端接口,或复用库里已有的文件 URL
|
|
176
|
-
|
|
177
|
-
另外,`UserSelect` 弹层是**搜索驱动**的:不输关键词时列表为空属预期行为,不是接口挂了。
|
|
178
|
-
|
|
179
|
-
### 服务端启动即崩、`/api` 全部 502
|
|
180
|
-
|
|
181
|
-
报错形如 `UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?)... argument Function at index [0]` 时,先查构造函数有没有空参 `@Inject()`(见 plugin-guide),不要先去改 Module 的 `imports` / `providers`。
|
|
182
|
-
|
|
183
150
|
## 质量保障流程
|
|
184
151
|
|
|
185
152
|
修改代码后:
|
|
@@ -491,7 +458,8 @@ NestJS 自己不读 env。直连 NestJS 端口 → header 缺失 → `req.userCo
|
|
|
491
458
|
- **图标**: Lucide React(唯一图标库,禁止 Emoji 和其他图标库)
|
|
492
459
|
- **表格/表单/图表**: 见下方"组件 Skill 召回规则",开发前必须先调用对应 Skill
|
|
493
460
|
- **用户**: 用户信息展示/选择必须用 `business-ui` 组件(阅读 README.md),禁止直接展示 userId
|
|
494
|
-
-
|
|
461
|
+
- **富文本**: `business-ui/tiptap-editor`(阅读 README.md)
|
|
462
|
+
- **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
|
|
495
463
|
|
|
496
464
|
## API 请求
|
|
497
465
|
|
|
@@ -524,7 +492,8 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
524
492
|
| --------------------------------------------------- | --------------------------------------- | --------------------------------- |
|
|
525
493
|
| Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
|
|
526
494
|
| UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
|
|
527
|
-
|
|
|
495
|
+
| TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
|
|
496
|
+
| Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
|
|
528
497
|
|
|
529
498
|
### 组件 Skill 召回规则(强制执行)
|
|
530
499
|
|
|
@@ -659,6 +628,15 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
659
628
|
|
|
660
629
|
> 本地开发态不接 AI 图片生成,需要 AI 生图请走云端发布后由平台插件链路承接
|
|
661
630
|
|
|
631
|
+
### 静态资产与文件下载
|
|
632
|
+
|
|
633
|
+
应用部署在 `/app/<appId>/` 路径前缀下,组件内禁止用根绝对路径引用静态资产(如 `src="/x.svg"`、`href="/resources/a.pdf"`),否则预览与线上可能 404。
|
|
634
|
+
|
|
635
|
+
- 项目自带图片、视频、字体和可下载样例资源 → 放 `client/src/assets/` 并用 ES import 引用;构建会自动处理路径前缀。新增资源类型时同步补 `client/src/types/global.d.ts` 的 `declare module`。
|
|
636
|
+
- 用户上传或业务运行期文件 → 走 dataloom storage 或平台文件存储,不要先下载进 `public/` 再硬编码路径。
|
|
637
|
+
- 不要为了占位下载伪造最终 `.pdf` / `.xlsx` / `.docx` / `.zip` 等受限二进制后缀文件,也不要让 Worker 用 Write/Edit 直接创建或修改这些最终文件。
|
|
638
|
+
- 遇到受限二进制后缀、又没有 Bash Worker 时,不要直接生成最终文件。优先改成前端运行时下载(如 Blob / jsPDF / xlsx)、服务端生成下载内容、平台存储,或让 Worker 只写 `tmp/<name>.asset.txt` 这类可编辑的中间文本/字节源,再以 PARTIAL 交给父级用 Bash 复制、转换并校验。
|
|
639
|
+
|
|
662
640
|
## 核心功能依赖
|
|
663
641
|
|
|
664
642
|
| 类别 | 库 |
|
|
@@ -669,8 +647,15 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
669
647
|
| 验证 | zod |
|
|
670
648
|
| 工具函数 | lodash |
|
|
671
649
|
| 样式 | clsx |
|
|
650
|
+
| Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
|
|
651
|
+
| PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
|
|
652
|
+
| 文件上传 | react-dropzone |
|
|
653
|
+
| 二维码 | qrcode.react |
|
|
672
654
|
| 用户反馈 | sonner |
|
|
673
|
-
|
|
655
|
+
| 拖拽 | @dnd-kit/core |
|
|
656
|
+
| 数字动画 | react-countup |
|
|
657
|
+
| Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
|
|
658
|
+
| 3D 场景 | cobe |
|
|
674
659
|
|
|
675
660
|
## 滚动分页最佳实践
|
|
676
661
|
|
|
@@ -38,7 +38,7 @@ lark-cli apps --help 2>&1 | grep -q '+plugin-install' && echo "READY" || echo "M
|
|
|
38
38
|
| 长耗时 AI 结果 | 大体量/多字段/多份/多语言/文件或多模态串联等结构信号命中时,优先前端 `callStream` 渐进展示;需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时使用后端任务记录 + 状态查询 + 结果读取;禁止单个 HTTP 请求等待完整结果后才返回 |
|
|
39
39
|
| capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
|
|
40
40
|
| CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
|
|
41
|
-
| CapabilityService 注入 | `@Inject(
|
|
41
|
+
| CapabilityService 注入 | `@Inject() private readonly capabilityService: CapabilityService` |
|
|
42
42
|
| 插件实例配置位置 | `server/capabilities/<instance_id>.json`(默认全栈应用)。见「配置目录」段 |
|
|
43
43
|
|
|
44
44
|
> **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit'`。
|
package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md
CHANGED
|
@@ -76,18 +76,12 @@
|
|
|
76
76
|
#### call / callStream 函数签名
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
|
-
//
|
|
80
|
-
.
|
|
81
|
-
.callStream<T = unknown>(actionKey: string, params?: Record<string, unknown>): AsyncIterable<T> // 流式
|
|
79
|
+
.call(actionKey: string, input: object) // 非流式,返回 Promise<output>
|
|
80
|
+
.callStream(actionKey: string, input: object) // 流式,返回 AsyncIterable<chunk>
|
|
82
81
|
```
|
|
83
82
|
|
|
84
83
|
- **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
|
|
85
|
-
- **第二个参数 `
|
|
86
|
-
- 上面是**前端**签名,带泛型。**服务端 `CapabilityService` 的 `call` / `callStream` / `callStreamWithEvents` 都没有泛型**,两者不通用,详见「Server 侧调用方式」
|
|
87
|
-
|
|
88
|
-
> **`Record<string, unknown>` 不接 `interface` 声明的对象**(interface 没有隐式索引签名),直接传会撞
|
|
89
|
-
> `Argument of type 'XxxInput' is not assignable to parameter of type 'Record<string, unknown>'. Index signature for type 'string' is missing in type 'XxxInput'.`
|
|
90
|
-
> 三种正确写法:① 直接传内联对象字面量;② 入参类型用 `type XxxInput = { ... }` 而非 `interface`(type 别名有隐式索引签名);③ 已有 interface 时在调用点显式 `input as unknown as Record<string, unknown>`。**不要为此改用 `as any`。**
|
|
84
|
+
- **第二个参数 `input`**:必须是对象,结构符合 `actions[].inputSchema`
|
|
91
85
|
|
|
92
86
|
```typescript
|
|
93
87
|
// ❌ 错误:把参数 JSON.stringify 后当作 actionKey
|
|
@@ -274,49 +268,23 @@ import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';
|
|
|
274
268
|
export class XxxService {
|
|
275
269
|
private readonly logger = new Logger(XxxService.name);
|
|
276
270
|
|
|
277
|
-
// 注入 token 必须显式写成 @Inject(CapabilityService)
|
|
278
271
|
constructor(
|
|
279
|
-
@Inject(
|
|
272
|
+
@Inject() private readonly capabilityService: CapabilityService,
|
|
280
273
|
) {}
|
|
281
274
|
}
|
|
282
275
|
```
|
|
283
276
|
|
|
284
|
-
**禁止空参 `@Inject()`**。本 stack 的 `nest-cli.json` 用 `builder: swc`,空参 `@Inject()` 会把注入 token 置为 undefined,Nest 回退读 `design:type` 拿到 `Function`,服务端启动即崩溃、所有 `/api` 请求返回 502:
|
|
285
|
-
|
|
286
|
-
```
|
|
287
|
-
UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?).
|
|
288
|
-
Please make sure that the argument Function at index [0] is available in the XxxModule context.
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
见到这段报错先查构造函数有没有空参 `@Inject()`,不要去改 Module 的 `imports` / `providers`。
|
|
292
|
-
|
|
293
|
-
**业务 Module 不需要注册 CapabilityModule**。`PlatformModule` 是 `@Global()` 且已把 `CapabilityModule` 放进 `exports`,`CapabilityService` 全局可注入。不要在业务 Module 里写 `imports: [CapabilityModule.forRoot(...)]` 或 `imports: [PlatformModule.forRoot()]`。
|
|
294
|
-
|
|
295
|
-
同一规则适用于从 `@lark-apaas/fullstack-nestjs-core` 注入的其他平台服务(`AuthNPaasService`、`FileService` 等)。
|
|
296
|
-
|
|
297
277
|
#### 3. 调用示例
|
|
298
278
|
|
|
299
|
-
服务端 `CapabilityExecutor` 的**三个方法全都没有泛型参数**(`@lark-apaas/nestjs-capability`):
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
call(actionName: string, input: unknown, context?: Partial<PluginActionContext>): Promise<unknown>;
|
|
303
|
-
callStream(actionName: string, input: unknown, context?): AsyncIterable<unknown>;
|
|
304
|
-
callStreamWithEvents(actionName: string, input: unknown, context?): AsyncIterable<StreamEvent<unknown>>;
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
写 `.call<T>(...)` / `.callStream<T>(...)` / `.callStreamWithEvents<T>(...)` 都会报「应有 0 个类型参数,但获得 1 个」。泛型只存在于前端 `capabilityClient`。注意服务端 `input` 是**必传**(没有 `?`),且类型是 `unknown` 而非前端的 `Record<string, unknown>`。服务端接 `unknown` 后在运行时收窄,禁止 `as any`:
|
|
308
|
-
|
|
309
279
|
```typescript
|
|
310
280
|
const inputParams = {
|
|
311
281
|
// 严格按 get_plugin_ai_json.actions[].inputSchema 构造
|
|
312
282
|
};
|
|
313
283
|
|
|
314
284
|
try {
|
|
315
|
-
const output
|
|
285
|
+
const output = await this.capabilityService
|
|
316
286
|
.load('')
|
|
317
287
|
.call('', inputParams);
|
|
318
|
-
// 运行时收窄后再取字段,例如:
|
|
319
|
-
// if (typeof output === 'object' && output !== null && 'content' in output) { ... }
|
|
320
288
|
return output;
|
|
321
289
|
} catch (error) {
|
|
322
290
|
this.logger.error('pluginInstance call failed', {
|