@lark-apaas/coding-steering 0.1.49 → 0.1.50
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/client-builtins-file-storage-service/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +2 -0
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +39 -10
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +37 -5
- package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +2 -0
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +37 -13
- 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 +37 -5
package/package.json
CHANGED
package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md
CHANGED
|
@@ -21,6 +21,7 @@ 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 接口」。
|
|
24
25
|
- 上传成功后,最重要的返回值是 `data.download_url`。需要将此URL保存到你的业务数据库中
|
|
25
26
|
- **⚠️ 场景区分(重要)**:`dataloom.storage` 仅适用于需要持久化存储文件或获取 `download_url` 保存到数据库的场景。如果文件仅作为插件输入(传给 `capabilityClient`),**必须直接传 File/Blob 对象,禁止先走 dataloom 上传再传 URL**;插件调用(capability)不属于 dataloom,详见 plugin-guide
|
|
26
27
|
- **download_url 格式说明**:`download_url` 返回的可能是相对路径(如 `/spark/app/.../storage/object/...`),这是正常行为。**禁止**在前面拼接 `window.location.origin` 或其他域名前缀,平台会自动解析相对路径。直接使用原始值即可。
|
|
@@ -14,6 +14,8 @@ 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
|
+
>
|
|
17
19
|
> **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
|
|
18
20
|
|
|
19
21
|
## 怎么选(决策指引)
|
|
@@ -155,7 +155,7 @@ shared/ # 前后端共享的目录
|
|
|
155
155
|
|
|
156
156
|
## 依赖使用规范
|
|
157
157
|
|
|
158
|
-
1. **子包完整性检查**:部分库有多个子包(如 `@
|
|
158
|
+
1. **子包完整性检查**:部分库有多个子包(如 `@radix-ui/react-*` 系列每个组件都是独立子包),添加 import 后必须确认 package.json 中包含所有需要的子包
|
|
159
159
|
2. **禁止安装时需现场编译的 native addon**(`node-gyp rebuild`、`binding.gyp`、需要系统头文件或编译器)。依赖按平台内置能力 / 浏览器能力 → 纯 JS 或 WASM → 有预编译二进制的 native addon 的顺序选型
|
|
160
160
|
3. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
|
|
161
161
|
|
|
@@ -180,6 +180,36 @@ 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
|
+
|
|
183
213
|
## 质量保障流程
|
|
184
214
|
|
|
185
215
|
通用提交前检查(代码检查 / 接口测试 / 读日志确认无错误)见系统任务流程约束;本栈具体落点:
|
|
@@ -560,7 +590,9 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
560
590
|
|
|
561
591
|
1. 使用 API 测试工具测试不依赖用户信息的接口
|
|
562
592
|
2. 积极使用 `read_logs` 工具,如无有效日志可增加 logger 打印
|
|
563
|
-
3. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController
|
|
593
|
+
3. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController。若路径含 `__runtime__`,那就是下一条——GET 未被代理时会落 SPA fallback 返回 index.html
|
|
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
|
|
564
596
|
|
|
565
597
|
---
|
|
566
598
|
|
|
@@ -581,6 +613,8 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
581
613
|
{% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
|
|
582
614
|
- **富文本**: `business-ui/tiptap-editor`(阅读 README.md)
|
|
583
615
|
- **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
|
|
616
|
+
{% else %}
|
|
617
|
+
- **Markdown 渲染**: `components/ui/markdown`(react-markdown + remark-gfm,内置 prose 排版)
|
|
584
618
|
{% endif %}
|
|
585
619
|
|
|
586
620
|
## API 请求
|
|
@@ -618,6 +652,8 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
618
652
|
{% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
|
|
619
653
|
| TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
|
|
620
654
|
| Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
|
|
655
|
+
{% else %}
|
|
656
|
+
| Markdown | `components/ui/markdown` | Markdown 渲染(react-markdown + remark-gfm) |
|
|
621
657
|
{% endif %}
|
|
622
658
|
|
|
623
659
|
### 组件 Skill 召回规则(强制执行)
|
|
@@ -781,15 +817,8 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
781
817
|
| 验证 | zod |
|
|
782
818
|
| 工具函数 | lodash |
|
|
783
819
|
| 样式 | clsx |
|
|
784
|
-
| Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
|
|
785
|
-
| PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
|
|
786
|
-
| 文件上传 | react-dropzone |
|
|
787
|
-
| 二维码 | qrcode.react |
|
|
788
820
|
| 用户反馈 | sonner |
|
|
789
|
-
|
|
790
|
-
| 数字动画 | react-countup |
|
|
791
|
-
| Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
|
|
792
|
-
| 3D 场景 | cobe |
|
|
821
|
+
|
|
793
822
|
|
|
794
823
|
## 滚动分页最佳实践
|
|
795
824
|
|
|
@@ -23,6 +23,7 @@ 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) |
|
|
26
27
|
|
|
27
28
|
## 必读 references
|
|
28
29
|
|
package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md
CHANGED
|
@@ -87,12 +87,18 @@
|
|
|
87
87
|
#### call / callStream 函数签名
|
|
88
88
|
|
|
89
89
|
```typescript
|
|
90
|
-
|
|
91
|
-
.
|
|
90
|
+
// 前端 capabilityClient(@lark-apaas/client-toolkit,实际类型来自 @lark-apaas/client-capability)
|
|
91
|
+
.call<T = unknown>(actionKey: string, params?: Record<string, unknown>): Promise<T> // 非流式
|
|
92
|
+
.callStream<T = unknown>(actionKey: string, params?: Record<string, unknown>): AsyncIterable<T> // 流式
|
|
92
93
|
```
|
|
93
94
|
|
|
94
95
|
- **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
|
|
95
|
-
- **第二个参数 `
|
|
96
|
+
- **第二个参数 `params`**:类型是 `Record<string, unknown>`,结构符合 `actions[].inputSchema`
|
|
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`。**
|
|
96
102
|
|
|
97
103
|
```typescript
|
|
98
104
|
// ❌ 错误:把参数 JSON.stringify 后当作 actionKey
|
|
@@ -306,23 +312,49 @@ import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';
|
|
|
306
312
|
export class XxxService {
|
|
307
313
|
private readonly logger = new Logger(XxxService.name);
|
|
308
314
|
|
|
315
|
+
// 注入 token 必须显式写成 @Inject(CapabilityService)
|
|
309
316
|
constructor(
|
|
310
|
-
@Inject() private readonly capabilityService: CapabilityService,
|
|
317
|
+
@Inject(CapabilityService) private readonly capabilityService: CapabilityService,
|
|
311
318
|
) {}
|
|
312
319
|
}
|
|
313
320
|
```
|
|
314
321
|
|
|
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
|
+
|
|
315
335
|
#### 3. 调用示例
|
|
316
336
|
|
|
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
|
+
|
|
317
347
|
```typescript
|
|
318
348
|
const inputParams = {
|
|
319
349
|
// 严格按 get_plugin_ai_json.actions[].inputSchema 构造
|
|
320
350
|
};
|
|
321
351
|
|
|
322
352
|
try {
|
|
323
|
-
const output = await this.capabilityService
|
|
353
|
+
const output: unknown = await this.capabilityService
|
|
324
354
|
.load('')
|
|
325
355
|
.call('', inputParams);
|
|
356
|
+
// 运行时收窄后再取字段,例如:
|
|
357
|
+
// if (typeof output === 'object' && output !== null && 'content' in output) { ... }
|
|
326
358
|
return output;
|
|
327
359
|
} catch (error) {
|
|
328
360
|
this.logger.error('pluginInstance call failed', {
|
|
@@ -14,6 +14,8 @@ 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
|
+
>
|
|
17
19
|
> **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
|
|
18
20
|
|
|
19
21
|
## 怎么选(决策指引)
|
|
@@ -129,7 +129,7 @@ shared/ # 前后端共享的目录
|
|
|
129
129
|
|
|
130
130
|
1. 优先使用项目已有依赖,仅在无法实现时安装新依赖
|
|
131
131
|
2. 使用前先查看 `package.json` 确保依赖已存在
|
|
132
|
-
3. **子包完整性检查**:部分库有多个子包(如 `@
|
|
132
|
+
3. **子包完整性检查**:部分库有多个子包(如 `@radix-ui/react-*` 系列每个组件都是独立子包),添加 import 后必须确认 package.json 中包含所有需要的子包
|
|
133
133
|
4. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
|
|
134
134
|
|
|
135
135
|
## 文件命名约定
|
|
@@ -147,6 +147,39 @@ 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
|
+
|
|
150
183
|
## 质量保障流程
|
|
151
184
|
|
|
152
185
|
修改代码后:
|
|
@@ -458,8 +491,7 @@ NestJS 自己不读 env。直连 NestJS 端口 → header 缺失 → `req.userCo
|
|
|
458
491
|
- **图标**: Lucide React(唯一图标库,禁止 Emoji 和其他图标库)
|
|
459
492
|
- **表格/表单/图表**: 见下方"组件 Skill 召回规则",开发前必须先调用对应 Skill
|
|
460
493
|
- **用户**: 用户信息展示/选择必须用 `business-ui` 组件(阅读 README.md),禁止直接展示 userId
|
|
461
|
-
-
|
|
462
|
-
- **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
|
|
494
|
+
- **Markdown 渲染**: `components/ui/markdown`(react-markdown + remark-gfm,内置 prose 排版)
|
|
463
495
|
|
|
464
496
|
## API 请求
|
|
465
497
|
|
|
@@ -492,8 +524,7 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
492
524
|
| --------------------------------------------------- | --------------------------------------- | --------------------------------- |
|
|
493
525
|
| Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
|
|
494
526
|
| UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
|
|
495
|
-
|
|
|
496
|
-
| Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
|
|
527
|
+
| Markdown | `components/ui/markdown` | Markdown 渲染(react-markdown + remark-gfm) |
|
|
497
528
|
|
|
498
529
|
### 组件 Skill 召回规则(强制执行)
|
|
499
530
|
|
|
@@ -638,15 +669,8 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
638
669
|
| 验证 | zod |
|
|
639
670
|
| 工具函数 | lodash |
|
|
640
671
|
| 样式 | clsx |
|
|
641
|
-
| Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
|
|
642
|
-
| PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
|
|
643
|
-
| 文件上传 | react-dropzone |
|
|
644
|
-
| 二维码 | qrcode.react |
|
|
645
672
|
| 用户反馈 | sonner |
|
|
646
|
-
|
|
647
|
-
| 数字动画 | react-countup |
|
|
648
|
-
| Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
|
|
649
|
-
| 3D 场景 | cobe |
|
|
673
|
+
|
|
650
674
|
|
|
651
675
|
## 滚动分页最佳实践
|
|
652
676
|
|
|
@@ -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() private readonly capabilityService: CapabilityService
|
|
41
|
+
| CapabilityService 注入 | `@Inject(CapabilityService) private readonly capabilityService: CapabilityService`(**禁止空参 `@Inject()`**,会导致启动崩溃 502;业务 Module 无需 imports) |
|
|
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,12 +76,18 @@
|
|
|
76
76
|
#### call / callStream 函数签名
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
|
-
|
|
80
|
-
.
|
|
79
|
+
// 前端 capabilityClient(@lark-apaas/client-toolkit,实际类型来自 @lark-apaas/client-capability)
|
|
80
|
+
.call<T = unknown>(actionKey: string, params?: Record<string, unknown>): Promise<T> // 非流式
|
|
81
|
+
.callStream<T = unknown>(actionKey: string, params?: Record<string, unknown>): AsyncIterable<T> // 流式
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
- **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
|
|
84
|
-
- **第二个参数 `
|
|
85
|
+
- **第二个参数 `params`**:类型是 `Record<string, unknown>`,结构符合 `actions[].inputSchema`
|
|
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`。**
|
|
85
91
|
|
|
86
92
|
```typescript
|
|
87
93
|
// ❌ 错误:把参数 JSON.stringify 后当作 actionKey
|
|
@@ -268,23 +274,49 @@ import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';
|
|
|
268
274
|
export class XxxService {
|
|
269
275
|
private readonly logger = new Logger(XxxService.name);
|
|
270
276
|
|
|
277
|
+
// 注入 token 必须显式写成 @Inject(CapabilityService)
|
|
271
278
|
constructor(
|
|
272
|
-
@Inject() private readonly capabilityService: CapabilityService,
|
|
279
|
+
@Inject(CapabilityService) private readonly capabilityService: CapabilityService,
|
|
273
280
|
) {}
|
|
274
281
|
}
|
|
275
282
|
```
|
|
276
283
|
|
|
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
|
+
|
|
277
297
|
#### 3. 调用示例
|
|
278
298
|
|
|
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
|
+
|
|
279
309
|
```typescript
|
|
280
310
|
const inputParams = {
|
|
281
311
|
// 严格按 get_plugin_ai_json.actions[].inputSchema 构造
|
|
282
312
|
};
|
|
283
313
|
|
|
284
314
|
try {
|
|
285
|
-
const output = await this.capabilityService
|
|
315
|
+
const output: unknown = await this.capabilityService
|
|
286
316
|
.load('')
|
|
287
317
|
.call('', inputParams);
|
|
318
|
+
// 运行时收窄后再取字段,例如:
|
|
319
|
+
// if (typeof output === 'object' && output !== null && 'content' in output) { ... }
|
|
288
320
|
return output;
|
|
289
321
|
} catch (error) {
|
|
290
322
|
this.logger.error('pluginInstance call failed', {
|