@jesonliu/lark-claudecode-bridge 0.20.0 → 0.20.1
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/README.md +77 -192
- package/assets/web/css/main.css +2 -0
- package/assets/web/js/pages/mcp.js +1 -1
- package/assets/web/js/pages/skills.js +2 -2
- package/dist/gateway/card-builder.d.ts +23 -7
- package/dist/gateway/card-builder.js +46 -25
- package/dist/gateway/card-builder.js.map +1 -1
- package/dist/gateway/progress-card.d.ts +10 -5
- package/dist/gateway/progress-card.js +74 -23
- package/dist/gateway/progress-card.js.map +1 -1
- package/dist/session/commands.js +1 -1
- package/dist/session/commands.js.map +1 -1
- package/dist/web/server.js +50 -10
- package/dist/web/server.js.map +1 -1
- package/dist/web/skills-mcp-api.d.ts +20 -4
- package/dist/web/skills-mcp-api.js +62 -7
- package/dist/web/skills-mcp-api.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,37 +4,18 @@
|
|
|
4
4
|
写操作以卡片按钮确认(长连接回调),结果文本与产出文件回传飞书。
|
|
5
5
|
**无需公网 IP、无需内网穿透;无需预装 Claude Code CLI,一键安装 + 网页配置即可使用。**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **飞书端直接装插件**:聊天里 `/plugin install xxx@marketplace`(仅管理员)即可安装 / 启停 Claude Code 插件,下一条消息自动加载;配置页同样可管
|
|
13
|
-
- 私聊 / 群聊 @机器人 触发;群聊多人可用(配对 + 白名单访问控制)
|
|
14
|
-
- **直接使用本机 Claude Code 全套配置**(inherit 模式):模型设置(`~/.claude/settings.json`)、登录态、user 级 MCP、skills、marketplace 插件自动继承,无须二次配置;已启用插件自动加载
|
|
15
|
-
- **斜杠透传**:飞书里直接发 `/superpowers:brainstorming` 等斜杠命令,原文透传给 Claude Code 展开(user skills / 插件命令均可触发)
|
|
16
|
-
- **多机器人**:一个进程同时跑 N 个飞书机器人,各自独立会话池、独立并发、独立人格(`append_system_prompt`);共享同一套 Claude 配置
|
|
17
|
-
- 写操作确认嵌在计时进度卡底部(允许 / 拒绝 / 本次会话不再询问,仅任务发起人可点;Write/Edit 直接展示红绿 diff;等待确认时正文自动收敛,决策后按钮消失、状态行显示结果)
|
|
18
|
-
- **读操作免确认**:读工具(Read/Grep/Glob 等)与 Bash 读命令默认直通,危险命令黑名单兜底;白名单可通过 `permissions.allow_tools` 自定义(配置页可增删,新建配置默认预置完整默认值)
|
|
19
|
-
- **计划模式(/plan 命令)**:飞书里发 `/plan` 按通道切换——开启后每个任务先出计划 → 飞书卡片批准/按意见修改/放弃 → 批准后自动执行;git 仓库工作区任务收尾发汇总 diff 卡片(红绿着色),不再整文件刷屏
|
|
20
|
-
- 流式进度卡片(打字机效果 + 工具调用 + 运行心跳,静默不等于卡死)
|
|
21
|
-
- **接收图片与富文本**:直接给机器人发图片(下载到 `~/.lark-claudecode-bridge/inbox/`,Claude 用 Read 工具识图);粘贴的多行/带格式内容(post 富文本)自动拍平为多行文本;不支持的类型(语音等)私聊会回复提示;入站消息按 message_id 去重(WS 重投不会导致任务跑两遍)
|
|
22
|
-
- 结果文本 + 产出文件回传(图片预览、>10 文件自动 zip)
|
|
23
|
-
- 多工作区切换(/ws)、会话管理(/new /resume)、/stop 打断、模型切换(/model)、厂商档案切换(/model-profile)、加载清单查看(/skills /plugins /mcp)、插件管理(/plugin)
|
|
24
|
-
- **后台子代理续跑**:主 Agent 派发的后台子代理在主回复结束后继续执行,完成后自动唤醒主循环汇总结果(进度卡可见「等待后台任务」与子代理输出)
|
|
25
|
-
- **对话内容落盘**:用户消息与 Claude 回复全文存为 JSONL(`transcripts/`,为后续知识库挖掘打底;可选保留期)
|
|
26
|
-
- **回复链上游自动拼入 prompt**:在飞书里「回复」某条消息再发新指令,上游最多 3 层消息文本会作为引用附在新消息前部一起发给 Claude(需 `im:message` 读取权限;失败降级只发当前消息)
|
|
27
|
-
- **Skills / MCP 可视化管理页**:配置页新增「Skills」「MCP」两页——Skills 三来源(用户级·本机 / 用户级·bridge / 项目级·工作区) + zip 导入;MCP 三来源 + 命令方式(`claude mcp add`)/ JSON 配置 + 状态探测 + 抽屉查看 env 引用展开当前值。页面添加的 MCP 存 `~/.lark-claudecode-bridge/mcp/servers.json`,任务级热生效(无须重启)
|
|
28
|
-
- 通道并发(默认 3),通道内串行
|
|
29
|
-
|
|
30
|
-
## 前置条件
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 快速开始
|
|
10
|
+
|
|
11
|
+
### 前置条件
|
|
31
12
|
|
|
32
13
|
1. Node.js ≥ 20
|
|
33
|
-
2. Claude
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
2. Claude 认证(二选一,详见[认证双模式](#认证双模式inherit--managed)):
|
|
15
|
+
- **bridge 托管(推荐,免本机登录)**:准备 `ANTHROPIC_API_KEY`(官方)或 `ANTHROPIC_AUTH_TOKEN` + `ANTHROPIC_BASE_URL`(第三方中转端点),在配置页填写即可
|
|
16
|
+
- **继承本机**:本机已 `claude login`(任意鉴权方式),桥接器自动共享 `~/.claude` 全套配置
|
|
36
17
|
|
|
37
|
-
|
|
18
|
+
### 安装
|
|
38
19
|
|
|
39
20
|
```bash
|
|
40
21
|
npm install -g @jesonliu/lark-claudecode-bridge
|
|
@@ -43,72 +24,78 @@ lcb start
|
|
|
43
24
|
|
|
44
25
|
首次运行 `lcb start` 检测到没有配置时,会自动打开浏览器进入配置页(`http://127.0.0.1:17317`):填飞书凭证 → 选认证方式 → 完成后重新 `lcb start` 即可使用。
|
|
45
26
|
|
|
46
|
-
偏好命令行问答的也可以用 `lcb setup
|
|
27
|
+
偏好命令行问答的也可以用 `lcb setup`(两者产物等价)。配置页也可单独启动:`lcb ui`(不启动机器人,可与运行中的桥接器共存)。
|
|
47
28
|
|
|
48
|
-
|
|
29
|
+
### 飞书应用配置(图文)
|
|
49
30
|
|
|
50
|
-
1. https://open.feishu.cn → 创建企业自建应用 → 添加「机器人」能力
|
|
51
|
-
2. 权限管理开通:`im:message
|
|
31
|
+
1. [https://open.feishu.cn](https://open.feishu.cn) → 创建企业自建应用 → 添加「机器人」能力
|
|
32
|
+
2. 权限管理开通:`im:message`(含读取单聊消息,回复引用拼接用)、`im:message:send_as_bot`、`im:resource`(接收图片用)、`contact:user.base:readonly`、`application:app_slash_command:write` / `application:app_slash_command:read`(斜杠命令同步用)、`cardkit:card:write`(建议开通:卡片局部刷新;不开自动降级为整卡更新,功能不缺)
|
|
52
33
|
3. 事件与回调 → 事件配置 → 订阅方式选「使用长连接接收事件」→ 添加 `im.message.receive_v1`
|
|
53
34
|
4. 事件与回调 → 回调配置 → 订阅方式选「使用长连接接收回调」→「已订阅的回调」点「添加回调」,添加「卡片回传交互」(`card.action.trigger`)
|
|
54
35
|
5. 凭证与基础信息 → 复制 App ID / App Secret
|
|
55
36
|
6. 版本管理与发布 → 创建版本并发布,管理员审核通过
|
|
56
37
|
7. `lcb start`(首次自动进配置页)或 `lcb setup` 填入凭证 → 私聊机器人发「/help」
|
|
57
38
|
|
|
58
|
-
|
|
39
|
+
> 飞书权限开通后,需在飞书开发者后台创建新版本并发布,然后重启 该bridge 才生效。
|
|
59
40
|
|
|
60
|
-
|
|
41
|
+
## 首次使用
|
|
61
42
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
- 前置:应用已开通 `application:app_slash_command:write` / `read` 权限并发布版本
|
|
43
|
+
- 每个机器人应用的**首位发消息用户免配对**,自动成为 admin
|
|
44
|
+
- 后续新用户收到 6 位配对码(15 分钟内有效):在 `lcb start` 的运行终端输入该码回车,或另开终端 `lcb pair <code>` 批准——写盘后自动生效,无需重启
|
|
65
45
|
|
|
66
|
-
##
|
|
46
|
+
## Web 配置页
|
|
67
47
|
|
|
68
|
-
|
|
48
|
+
配置页随桥接器常驻 `http://127.0.0.1:17317`(也可 `lcb ui` 单独启动,写盘后运行中的桥接器自动拾取可热字段)。共 9 个配置页签:
|
|
69
49
|
|
|
70
|
-
|
|
50
|
+
- **概览** —— 桥接器进程启停 / 重启 / 后台运行、版本检查与一键更新、各机器人应用运行状态。
|
|
51
|
+
- **飞书应用** —— 多机器人管理:App ID / Secret(脱敏回显)、默认工作区、并发上限、人格补充(`append_system_prompt`)、触发词、环境变量。
|
|
52
|
+
- **工作区** —— 工作区白名单(名称 / 路径)与全局默认工作区,改动热生效。
|
|
53
|
+
- **Claude 认证** —— inherit / managed 双模式切换、认证凭证(API Key / Auth Token / Base URL)、模型、厂商档案(多套凭证一键切换)、托管环境变量。
|
|
54
|
+
- **权限** —— 免确认工具白名单(`permissions.allow_tools`)与危险命令黑名单,保存后热生效。
|
|
55
|
+
- **斜杠命令** —— 把内置命令(`/new` `/status` …)+ 自定义透传命令一键同步为飞书输入框斜杠指令(输入 `/` 弹面板,选中后可继续输入描述再发送)。
|
|
56
|
+
- **插件** —— Claude Code 插件清单(启停 / 卸载,本机 `~/.claude` 与托管目录带来源标记)、从 marketplace 安装、管理市场。
|
|
57
|
+
- **Skills** —— 四来源技能聚合清单(本机用户级 / bridge 托管 / 工作区项目级 / 插件内只读),支持新建、删除、zip 导入。
|
|
58
|
+
- **MCP** —— MCP Servers 管理(命令方式或 JSON 配置添加)、状态探测、抽屉查看 env 引用展开值;任务级热生效。
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|---|---|
|
|
74
|
-
| `lcb setup` | 引导式配置(命令行问答;与配置页产物等价,预置 permissions / server 默认段) |
|
|
75
|
-
| `lcb start` | 启动桥接器(前台,为每个机器人各建一条长连接 + 内嵌 Web 配置页;首次安装自动进引导;终端可直接输入配对码批准) |
|
|
76
|
-
| `lcb ui` | 仅启动 Web 配置页(不启动机器人;可与运行中的桥接器共存,写盘后桥接器自动拾取可热字段) |
|
|
77
|
-
| `lcb pair <code>` | 另开终端批准 6 位配对码 |
|
|
78
|
-
| `lcb app list` | 列出机器人应用 |
|
|
79
|
-
| `lcb app add` | 添加机器人应用(交互式;旧单应用配置会自动升级为多应用格式,重启后生效) |
|
|
80
|
-
| `lcb app remove <名字\|app_id>` | 删除机器人应用(最后一个不可删;其会话分片与落盘目录保留待人工清理) |
|
|
81
|
-
| `lcb ws add <名字> <路径>` | 添加工作区(路径需已存在;增量写回,保留 config.yaml 注释) |
|
|
82
|
-
| `lcb ws remove <名字>` | 删除工作区(默认工作区被删时自动回退;apps 里的引用联动清理) |
|
|
83
|
-
| `lcb ws list` | 列出工作区(`*` 为默认) |
|
|
84
|
-
| `lcb version` | 查看版本 |
|
|
60
|
+
## lcb 命令
|
|
85
61
|
|
|
86
|
-
> **热生效**:桥接器运行中执行 `lcb ws add / remove`,下一条消息到达时自动重读配置,无需重启(apps 应用列表、凭证与 `concurrency` 改动除外,需重启)。
|
|
87
62
|
|
|
88
|
-
|
|
63
|
+
| 命令 | 说明 |
|
|
64
|
+
| ---------------------------- | ----------------------------------------------------------- |
|
|
65
|
+
| `lcb setup` | 引导式配置(命令行问答;与配置页产物等价,预置 permissions / server 默认段) |
|
|
66
|
+
| `lcb start` | 启动桥接器(前台,为每个机器人各建一条长连接 + 内嵌 Web 配置页;首次安装自动进引导;终端可直接输入配对码批准) |
|
|
67
|
+
| `lcb ui` | 仅启动 Web 配置页(不启动机器人;可与运行中的桥接器共存,写盘后桥接器自动拾取可热字段) |
|
|
68
|
+
| `lcb pair <code>` | 另开终端批准 6 位配对码 |
|
|
69
|
+
| `lcb app list` | 列出机器人应用 |
|
|
70
|
+
| `lcb app add` | 添加机器人应用(交互式;旧单应用配置会自动升级为多应用格式,重启后生效) |
|
|
71
|
+
| `lcb app remove <名字|app_id>` | 删除机器人应用(最后一个不可删;其会话分片与落盘目录保留待人工清理) |
|
|
72
|
+
| `lcb ws add <名字> <路径>` | 添加工作区(路径需已存在;增量写回,保留 config.yaml 注释) |
|
|
73
|
+
| `lcb ws remove <名字>` | 删除工作区(默认工作区被删时自动回退;apps 里的引用联动清理) |
|
|
74
|
+
| `lcb ws list` | 列出工作区(`*` 为默认) |
|
|
75
|
+
| `lcb version` | 查看版本 |
|
|
89
76
|
|
|
90
|
-
配置页「概览」支持托管桥接器进程与自更新(源码 tsx 运行模式下自动降级为手动指引):
|
|
91
77
|
|
|
92
|
-
|
|
93
|
-
- **后台运行日志**:桥接器输出按天落 `~/.lark-claudecode-bridge/logs/bridge-YYYY-MM-DD.log`(自动跨天切换,保留 14 天);进程 PID 记录于 `~/.lark-claudecode-bridge/bridge.pid`(进程消亡后自动清理)。
|
|
94
|
-
- **版本更新**:概览「版本与更新」卡自动对比 npm registry(跟随本机 `.npmrc` 镜像配置)与当前版本;有新版时一键更新(`npm install -g`)并自动重启生效。
|
|
78
|
+
> **热生效**:桥接器运行中执行 `lcb ws add / remove`,下一条消息到达时自动重读配置,无需重启(apps 应用列表、凭证与 `concurrency` 改动除外,需重启)。
|
|
95
79
|
|
|
96
80
|
## 命令速查(飞书里发给机器人)
|
|
97
81
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
101
|
-
| /
|
|
102
|
-
| /
|
|
103
|
-
| /
|
|
104
|
-
| /
|
|
105
|
-
| /
|
|
106
|
-
| /model
|
|
107
|
-
| /
|
|
108
|
-
| /
|
|
109
|
-
| /
|
|
110
|
-
| /
|
|
111
|
-
|
|
|
82
|
+
|
|
83
|
+
| 命令 | 说明 |
|
|
84
|
+
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| /new | 开新会话(历史保留,`/resume` 可随时切回) |
|
|
86
|
+
| /resume | 列出/恢复历史会话(`/resume <编号>` 恢复指定会话;列表标注当前续接的会话) |
|
|
87
|
+
| /stop | 停止当前任务 |
|
|
88
|
+
| /status | 当前状态 |
|
|
89
|
+
| /ws list / /ws use 名字 | 工作区(切换仅 admin 可用) |
|
|
90
|
+
| /model | 查看当前模型;`/model <名字>` 通道级切换;`/model reset` 恢复默认 |
|
|
91
|
+
| /model-profile | 查看/切换厂商档案(多厂商凭证+模型整体切换,切换仅 admin;managed 模式下一条消息生效) |
|
|
92
|
+
| /plan | 计划模式开关:开启后每个任务先出计划 → 飞书卡片批准 / 按意见修改 / 放弃 → 批准后自动执行;git 仓库工作区任务收尾发汇总 diff 卡片 |
|
|
93
|
+
| /skills / /plugins / /mcp | 查看本会话实际加载的技能 / 插件 / MCP 服务 |
|
|
94
|
+
| /plugin | 插件管理:`/plugin list`(全员,含本机 ~/.claude 与托管目录两处清单);`install/uninstall/enable/disable/marketplace …`(仅 admin,默认装 ~/.claude,`--dir=managed` 装托管目录),装好下一条消息自动加载 |
|
|
95
|
+
| /reload-plugins | 重载插件:清插件发现缓存,下一条消息重新扫描加载(终端命令的 bridge 等价物) |
|
|
96
|
+
| /help | 帮助 |
|
|
97
|
+
| 其它 `/xxx` | **原文透传**给 Claude Code 派发斜杠命令(如 `/superpowers:brainstorming` 触发插件技能) |
|
|
98
|
+
|
|
112
99
|
|
|
113
100
|
> 清单类命令(/skills 等)的数据来自最近一次会话的加载清单;刚启动还没跑过任务时,先发一条普通消息(如「你好」)再查。
|
|
114
101
|
|
|
@@ -131,7 +118,6 @@ apps: # 多机器人:每个应用一条长连接
|
|
|
131
118
|
workspaces: # 工作区白名单(列表全局共享;「当前用哪个」per-app 隔离)
|
|
132
119
|
- name: demo
|
|
133
120
|
path: F:\workspace\demo
|
|
134
|
-
# 计划模式在飞书发 /plan 按通道切换;git 仓库工作区收尾自动发汇总 diff 卡片
|
|
135
121
|
defaults:
|
|
136
122
|
workspace: demo
|
|
137
123
|
concurrency: 3 # 通道间并发上限(未单独配置的 app 沿用)
|
|
@@ -164,70 +150,24 @@ concurrency: 3 # 通道间并发上限(未单独配置的 app 沿
|
|
|
164
150
|
|
|
165
151
|
### 认证双模式(inherit / managed)
|
|
166
152
|
|
|
167
|
-
| | inherit(缺省) | managed |
|
|
168
|
-
|---|---|---|
|
|
169
|
-
| 认证来源 | 本机 `~/.claude`(`claude login` 或其 settings.json) | config.yaml `claude` 段 → 写入 `~/.lark-claudecode-bridge/claude/settings.json` |
|
|
170
|
-
| 适用 | 本机已在用 Claude Code 的用户 | 干净机器 / 不想动本机配置;配 API Key 或中转站 Token |
|
|
171
|
-
| 模型/MCP/skills | 继承 `~/.claude` 全套 | 全部落在托管目录,与本机 `~/.claude` 完全隔离 |
|
|
172
|
-
| 插件 | `~/.claude` 已启用插件自动加载 | **双目录合并加载**:托管目录 + 本机 `~/.claude` 已启用插件(同名托管目录优先),本机已装插件无须重装 |
|
|
173
|
-
| 切换 | 改 `claude.mode` 后**重启**生效 | 同 |
|
|
174
|
-
|
|
175
|
-
配置页「Claude 认证」tab 可视化切换;managed 模式下认证/模型改动保存后即对后续任务生效(无需重启)。
|
|
176
|
-
|
|
177
|
-
**managed 模式的 MCP 与环境继承**(0.14.0 起,`lcb start` 时自动完成):本机 `~/.claude.json` 的全局 `mcpServers` 单向同步到托管目录(CLI 读 `$CLAUDE_CONFIG_DIR/.claude.json`,不同步则托管会话丢掉全部 user 级 MCP);本机 `~/.claude/settings.json` env 块中的**非认证键**(MCP 工具依赖的 `IMAGE_GEN_*`、`API_HOST` 等自定义变量)并入托管 settings.json。需要覆盖继承值或本机没有这些配置时,用**显式配置**:配置页「Claude 认证」→「环境变量」行编辑器(或 config.yaml 的 `claude.env` 键值对),优先级 `claude.env` > 本机继承 > 托管目录既有值;认证与模型 4 键(`ANTHROPIC_AUTH_TOKEN/API_KEY/BASE_URL/MODEL`)不在此生效——永远以认证表单为准。
|
|
178
|
-
|
|
179
|
-
**插件双目录(managed 模式)**:新装插件默认装到本机 `~/.claude`(与本机 claude CLI 共用一份),`/plugin install xxx --dir=managed` 或配置页安装框选「bridge 托管目录」可装到托管目录;启停/卸载自动按插件所在目录执行,两处清单在配置页「插件」tab 与 `/plugin list` 中均带来源标记。注意:**卸载按所选目录逐处执行**——同一插件在两个目录各装一份时,卸载一处不影响另一处(本机 CLI 的 `/plugins list` 看的是它自己的配置目录);配置页卸载后会校验安装清单已清除,残留(仅被禁用)会显式报错并附 CLI 输出。配置页「管理市场」支持 git 地址与本机路径(本地路径市场按 CLI 语义不复制文件,登记原路径读取)。
|
|
180
|
-
|
|
181
|
-
### 计划模式工作流(/plan 命令)
|
|
182
|
-
|
|
183
|
-
在飞书会话里发 `/plan`(或 `/plan on`)即可为当前通道开启计划模式,每个任务自动走「先计划、后执行」(`/plan off` 关闭;开关是通道级偏好,跨重启保留,`/new` 不清除):
|
|
184
|
-
|
|
185
|
-
1. **计划审批**:任务以 plan mode 启动(期间只允许读操作),Claude 查阅代码后提交计划 → 飞书收到计划卡片:
|
|
186
|
-
- **✅ 批准执行**:批准即授权——Claude 自动切入 acceptEdits 模式按计划开工,**后续写文件不再逐次弹确认卡**(对齐本机 CLI「批准计划 → accept edits on」语义;Bash 危险命令黑名单仍生效,命中照弹确认)
|
|
187
|
-
- **📝 按意见修改**:在卡片输入框填修改意见后点击,Claude 修订计划重新提交(同一会话内循环,直到批准或放弃)
|
|
188
|
-
- **❌ 放弃计划**:任务终止;10 分钟无操作自动放弃
|
|
189
|
-
2. **收尾汇总 diff**:任务完成后不再把改动文件逐个上传,而是发**汇总 diff 卡片**(标题含文件数与 +X/-Y 行统计,正文红绿着色,超长自动拆多张)。改动以 `git diff HEAD` + untracked 新文件为准——**git 仓库工作区都会自动发**(非 git 仓库天然跳过,无需任何配置)
|
|
190
|
-
|
|
191
|
-
**执行器为 Streaming Input 模式**(0.14.0 起):prompt 经持久输入流送入 CLI,stdin 全程保持打开——这是计划审批与提问卡片能稳定工作的前提(旧版单轮模式在轮次边界会触发 CLI 的 "Stream closed" 中断,属 Agent SDK 已知问题)。**提问卡片**:Claude 调用 AskUserQuestion 时飞书收到问题选项卡,点选项作答(多选题可多选)、全部作答后「提交答案」——答案直接回传模型继续任务。
|
|
192
|
-
|
|
193
|
-
**读操作免确认**:读工具与 Bash 默认直通(`ls`/`cat`/`grep` 不再弹卡),命中 `dangerous_commands` 黑名单(`rm -rf`、`sudo`、`git push --force` 等)仍弹确认卡;「本次会话不再询问」的记忆同样绕不过黑名单。想放行其它工具(如 `Edit`)往 `permissions.allow_tools` 追加即可——注意配置即**整体替换**内置默认,需把内置读工具一并写上。**白名单/黑名单热生效**(0.18.0 起):配置页保存后,已有会话通道的下一个工具调用即用新名单(旧版需新通道或重启)。
|
|
194
|
-
|
|
195
|
-
**plan mode 下的白名单语义**:计划模式开启时的计划阶段,Claude Code 内部对写操作强制走桥接的权限闸(官方语义:plan 模式无视 CLI 侧 allow 规则、写工具一律路由到宿主判定)——因此桥接白名单在计划阶段对写工具**依然生效**(命中直通执行,未命中弹确认卡嵌在计时卡上),直到计划批准切回可编辑模式。只读工具不经桥接直接执行。计时卡上工具行的 `✘` 表示该次工具调用**执行失败**(含首行失败原因),不代表「工具没权限」。
|
|
196
|
-
|
|
197
|
-
> /plan 开关按通道即时生效(下一条任务起);permissions 配置同样支持热生效(见上)。
|
|
198
|
-
|
|
199
|
-
### 配置继承(inherit 模式:本机 ~/.claude 一处配置,全机器人共享)
|
|
200
153
|
|
|
201
|
-
inherit
|
|
154
|
+
| | inherit(缺省) | managed |
|
|
155
|
+
| ---------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
156
|
+
| 认证来源 | 本机 `~/.claude`(`claude login` 或其 settings.json) | config.yaml `claude` 段 → 写入 `~/.lark-claudecode-bridge/claude/settings.json` |
|
|
157
|
+
| 适用 | 本机已在用 Claude Code 的用户 | 干净机器 / 不想动本机配置;配 API Key 或中转站 Token |
|
|
158
|
+
| 模型 / MCP / skills / 插件 | 继承 `~/.claude` 全套,无须二次配置 | 全部落在托管目录,与本机 `~/.claude` 完全隔离;已启用插件双目录合并加载 |
|
|
159
|
+
| 切换 | 改 `claude.mode` 后**重启**生效 | 同 |
|
|
202
160
|
|
|
203
|
-
| 继承项 | 来源 | 说明 |
|
|
204
|
-
|---|---|---|
|
|
205
|
-
| 模型设置 | `~/.claude/settings.json` 的 `model` 与 `env` | 含 `ANTHROPIC_*`、第三方端点等全部环境变量 |
|
|
206
|
-
| 登录态 | `~/.claude/.credentials.json` / settings.json 认证声明 | 本机 `claude login` 一次即可 |
|
|
207
|
-
| user 级 MCP | `~/.claude.json` 的 `mcpServers` | 与本机 CLI 用同一批 MCP 服务 |
|
|
208
|
-
| skills | `~/.claude/skills/` | 可直接在飞书发 `/技能名` 触发(透传) |
|
|
209
|
-
| 插件 | `~/.claude/plugins/` 中已启用的 marketplace 插件 | 按 `installed_plugins.json` + `enabledPlugins` 自动加载 |
|
|
210
|
-
| 会话记录 | `~/.claude/projects/` | 飞书跑过的会话,本机 `claude --resume` 也能接着看 |
|
|
211
161
|
|
|
212
|
-
|
|
162
|
+
配置页「Claude 认证」tab 可视化切换;managed 模式下认证 / 模型改动保存后即对后续任务生效(无需重启)。多个机器人共享同一套 Claude 配置,会话池与并发各自独立。
|
|
213
163
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- **会话池**:每个机器人一份 `sessions.<app_id>.json`,历史会话绝不共享,`/resume` 只见自己的
|
|
217
|
-
- **人格**:`append_system_prompt` 按机器人定制(如对话助手 / 素材收集各一套)
|
|
218
|
-
- **并发**:按机器人独立限额(缺省沿用全局 `concurrency`);同时与多个机器人对话互不排队
|
|
219
|
-
- **升级兼容**:旧版 `feishu:` 单应用配置无需改动即可启动(自动归一化);首次 `lcb app add` 会把旧配置原地转为 `apps:` 格式,**转换后新增的机器人请追加在列表后面**(旧数据归属第一个应用)
|
|
220
|
-
- ⚠️ **0.4 起废弃 `claude_config_dir`**:所有机器人统一用 `~/.claude(共享配置)`,机器人间仅会话池隔离。旧版独立目录 `~/.lark-claudecode-bridge/claude/<app_id>/` 中的历史会话不再可 `/resume`(启动时会提示目录位置,确认无用后可手动删除)
|
|
221
|
-
|
|
222
|
-
### 快捷操作:飞书斜杠命令 / 机器人菜单
|
|
223
|
-
|
|
224
|
-
**推荐:斜杠命令**(配置页「斜杠命令」tab 一键同步)——聊天输入框输入 `/` 弹出指令面板,选中后命令留在输入框、**可继续输入描述**(如 `/produce 写一篇公众号文章`),发送后经命令 / 透传链路执行。自定义命令在表格维护,保存后点「一键同步」。
|
|
225
|
-
|
|
226
|
-
备选:机器人菜单(点击即发送,无法附加描述)——开放平台 → 应用功能 → 机器人 → 机器人菜单,添加「发送消息」类菜单项填 `/content-producer:content-producer` 即可。菜单命令格式:`/<插件名>:<技能名>`(插件技能)或 `/<技能名>`(user skill),可用命令清单发 `/skills` 查看。
|
|
164
|
+
## 常驻运行
|
|
227
165
|
|
|
228
|
-
|
|
166
|
+
- **Windows**:任务计划程序建「开机时启动」任务,程序指向 `windows-start.bat`(先放到固定位置,如 `C:\tools\lcb\windows-start.bat`)
|
|
167
|
+
- **macOS**:把 `com.lark-claudecode-bridge.plist` 放到 `~/Library/LaunchAgents/`,然后 `launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.lark-claudecode-bridge.plist`
|
|
168
|
+
- **Linux**:把 `lark-claudecode-bridge.service` 放到 `~/.config/systemd/user/`,然后 `systemctl --user daemon-reload && systemctl --user enable --now lark-claudecode-bridge`
|
|
229
169
|
|
|
230
|
-
|
|
170
|
+
模板文件见 [deploy/](deploy/)(npm 包内同路径)。
|
|
231
171
|
|
|
232
172
|
## ⚠️ 安全须知(必读)
|
|
233
173
|
|
|
@@ -236,67 +176,12 @@ inherit 模式(缺省)下所有机器人共享本机 `~/.claude`,以下内
|
|
|
236
176
|
|
|
237
177
|
**隐私提醒**:对话全文(含代码、文件路径)明文落盘于 `~/.lark-claudecode-bridge/transcripts/`;`config.yaml` 中的 `app_secret` 与 `apps[].env` 值同样为明文。请自行控制该目录与文件的访问权限,并按需配置 `transcripts.retention_days` 保留期。
|
|
238
178
|
|
|
239
|
-
## 常驻运行
|
|
240
|
-
|
|
241
|
-
- **Windows**:任务计划程序建「开机时启动」任务,程序指向下面的 `windows-start.bat`(先放到固定位置,如 `C:\tools\lcb\windows-start.bat`)
|
|
242
|
-
- **macOS**:launchd——把 `com.lark-claudecode-bridge.plist` 放到 `~/Library/LaunchAgents/`,然后 `launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.lark-claudecode-bridge.plist`
|
|
243
|
-
- **Linux**:systemd user 单元——把 `lark-claudecode-bridge.service` 放到 `~/.config/systemd/user/`,然后 `systemctl --user daemon-reload && systemctl --user enable --now lark-claudecode-bridge`
|
|
244
|
-
|
|
245
|
-
模板全文(npm 包内 `deploy/` 目录,或仓库 [deploy/](deploy/)):
|
|
246
|
-
|
|
247
|
-
**deploy/windows-start.bat**
|
|
248
|
-
|
|
249
|
-
```bat
|
|
250
|
-
@echo off
|
|
251
|
-
lcb start >> "%USERPROFILE%\.lark-claudecode-bridge\bridge.log" 2>&1
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**deploy/com.lark-claudecode-bridge.plist**
|
|
255
|
-
|
|
256
|
-
```xml
|
|
257
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
258
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
259
|
-
<plist version="1.0"><dict>
|
|
260
|
-
<key>Label</key><string>com.lark-claudecode-bridge</string>
|
|
261
|
-
<key>ProgramArguments</key><array><string>/usr/local/bin/lcb</string><string>start</string></array>
|
|
262
|
-
<key>RunAtLoad</key><true/>
|
|
263
|
-
<key>KeepAlive</key><true/>
|
|
264
|
-
<key>StandardOutPath</key><string>/tmp/lark-claudecode-bridge.log</string>
|
|
265
|
-
</dict></plist>
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
**deploy/lark-claudecode-bridge.service**
|
|
269
|
-
|
|
270
|
-
```ini
|
|
271
|
-
[Unit]
|
|
272
|
-
Description=lark-claudecode-bridge
|
|
273
|
-
After=network-online.target
|
|
274
|
-
|
|
275
|
-
[Service]
|
|
276
|
-
ExecStart=/usr/bin/env lcb start
|
|
277
|
-
Restart=always
|
|
278
|
-
RestartSec=10
|
|
279
|
-
|
|
280
|
-
[Install]
|
|
281
|
-
WantedBy=default.target
|
|
282
|
-
```
|
|
283
|
-
|
|
284
179
|
## 已知限制
|
|
285
180
|
|
|
286
|
-
1. **Linux 上
|
|
287
|
-
2.
|
|
288
|
-
3. **多机器人总并发 =
|
|
289
|
-
4.
|
|
290
|
-
5. **旧数据迁移归属**:升级多应用后,旧 `sessions.json` 归属 `apps` 列表的第一个应用;新增机器人请追加在列表末尾,否则历史会话会挂错机器人。
|
|
291
|
-
6. **飞书 SDK 对非法 app_id 静默失败**:`ws.start()` 对形状不合法的 app_id 只打日志不报错,启动后请确认每条「✅ <应用名> 长连接已启动」状态行都出现了。
|
|
292
|
-
7. **共享 ~/.claude 的副作用**:本机 user 级 hooks 也会在机器人任务里执行(含阻断型 PostToolUse hook);`apps[].env` 的同名键会被 `~/.claude/settings.json` 的 `env` 覆盖(优先级:CLI flags(/model)> settings.json env > apps[].env > 进程环境)。插件加载失败 SDK 会静默跳过,实际加载情况以 `/plugins` 清单为准。
|
|
293
|
-
8. **plan 卡片的「按意见修改」依赖飞书卡片输入框回传**:修改意见经卡片 input 组件随按钮回调传回;若个别客户端版本不回传输入值,点「按意见修改」会提示先填写意见——此时可改用「放弃计划」后在会话里直接发修改要求重新起任务。
|
|
294
|
-
9. **收尾 diff 基于 git**:工作区是 git 仓库(含未提交改动即可,无需 commit)且有改动时,任务收尾自动发汇总 diff 卡片;非 git 仓库天然跳过。untracked 新文件按全新增 diff 展示(目录级 untracked 与超过 20 个的 untracked 文件不展开)。
|
|
295
|
-
10. **入站图片不清理**:用户发送的图片落盘 `~/.lark-claudecode-bridge/inbox/` 后不会自动删除(供会话内多次查看),长期使用可手动清理;Claude 是否能「看懂」图片取决于当前模型是否多模态(非多模态模型可配置识图 MCP 兜底)。富文本(post)中的超链接以 `[文字](链接)` 形式拍平进文本,@用户 被移除。
|
|
296
|
-
11. **短回复不再单独发结果消息**:回复不超过进度卡终态上限(400 字)时,结果就展示在进度卡终态里(避免同内容两条消息);更长回复仍会单独发一条结果消息(进度卡只保留尾部)。运行中的进度卡**不展示**过程文本与思考内容(主卡只留状态 / 当前工具 / 子代理 / 确认区 / 计时等关键信息)。
|
|
297
|
-
12. **Web 配置页改 apps/workspaces 段会丢段内手写注释**:页面按整段替换写回(值未变的段落跳过重写、注释保留;`lcb ws add` 等增量命令不受影响)。手工注释建议写在段外或段头。
|
|
298
|
-
13. **config.yaml 并发写**:配置页写盘为原子替换,但与 `lcb ws add` / `lcb app add` 等独立进程命令同时操作存在读-改-写窗口,请避免同时修改。
|
|
299
|
-
14. **配置页默认仅本机可访问**(127.0.0.1);改 `server.host` 放开到局域网意味着页面可读写全部凭证,请仅在可信网络使用。
|
|
181
|
+
1. **Linux 上 >10 文件不打 zip**:文件打包依赖 bsdtar 的 zip 容器支持(Windows 10+ / macOS 自带),Linux 的 GNU tar 会自动退化为逐个上传文件(功能不丢,只是消息条数多)。
|
|
182
|
+
2. **共享 ~/.claude 的副作用**:本机 user 级 hooks 也会在机器人任务里执行(含阻断型 PostToolUse hook);`apps[].env` 的同名键会被 `~/.claude/settings.json` 的 `env` 覆盖。
|
|
183
|
+
3. **多机器人总并发 = 各应用并发之和**:N 个机器人同时满载时会同时跑 Σ(concurrency) 个 Claude Code 子进程,机器吃紧可按 app 调低。
|
|
184
|
+
4. **配置页默认仅本机可访问**(127.0.0.1):改 `server.host` 放开到局域网意味着页面可读写全部凭证,请仅在可信网络使用。
|
|
300
185
|
|
|
301
186
|
## 开发
|
|
302
187
|
|
|
@@ -309,4 +194,4 @@ node dist/bin/lcb.js version
|
|
|
309
194
|
|
|
310
195
|
## License
|
|
311
196
|
|
|
312
|
-
MIT
|
|
197
|
+
MIT
|
package/assets/web/css/main.css
CHANGED
|
@@ -285,6 +285,8 @@ pre.report {
|
|
|
285
285
|
|
|
286
286
|
/* ---------- 表格 ---------- */
|
|
287
287
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
288
|
+
/* 列表操作列:按钮一律横排(.btn 自身 nowrap,但 td 内模板换行空白给了折行点 → 竖排) */
|
|
289
|
+
td.ops { white-space: nowrap; }
|
|
288
290
|
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
289
291
|
thead th {
|
|
290
292
|
color: var(--muted); font-weight: 500; font-size: 12px; letter-spacing: .03em;
|
|
@@ -92,7 +92,7 @@ async function loadMcp() {
|
|
|
92
92
|
<td>${conn}</td>
|
|
93
93
|
<td><span class="chip">${tag}</span></td>
|
|
94
94
|
<td class="mcp-status" data-status-for="${esc(s.name)}"><span class="desc">未检测</span></td>
|
|
95
|
-
<td>
|
|
95
|
+
<td class="ops">
|
|
96
96
|
<button class="btn sm" data-view="${esc(s.name)}">查看</button>
|
|
97
97
|
${isDeletable(s) ? `<button class="btn sm danger" data-del="${esc(s.name)}">删除</button>` : '<span class="desc">只读</span>'}
|
|
98
98
|
</td>`;
|
|
@@ -32,7 +32,7 @@ function renderSkills(el) {
|
|
|
32
32
|
<input type="file" id="skillZipFile" accept=".zip" style="display:none">
|
|
33
33
|
<input type="search" class="list-search" id="skillSearch" placeholder="按名字 / 来源 / 路径过滤…">
|
|
34
34
|
<table><thead><tr>
|
|
35
|
-
<th style="width:170px">名字</th><th>说明</th><th style="width:130px">来源</th><th style="width:240px">路径</th><th style="width:
|
|
35
|
+
<th style="width:170px">名字</th><th>说明</th><th style="width:130px">来源</th><th style="width:240px">路径</th><th style="width:130px"></th>
|
|
36
36
|
</tr></thead>
|
|
37
37
|
<tbody id="skillBody"><tr><td colspan="5" class="desc">加载中…</td></tr></tbody>
|
|
38
38
|
</table>
|
|
@@ -96,7 +96,7 @@ async function loadSkills() {
|
|
|
96
96
|
<td>${esc(s.description) || '<span class="desc">(无说明)</span>'}</td>
|
|
97
97
|
<td><span class="chip">${tag}</span></td>
|
|
98
98
|
<td><code class="desc">${esc(s.path)}</code></td>
|
|
99
|
-
<td>
|
|
99
|
+
<td class="ops">
|
|
100
100
|
<button class="btn sm" data-browse="${esc(s.name)}">查看</button>
|
|
101
101
|
${isDeletable(s) ? `<button class="btn sm danger" data-del="${esc(s.name)}">删除</button>` : ''}
|
|
102
102
|
</td>`;
|
|
@@ -53,17 +53,33 @@ export declare const LONG_OUTPUT_THRESHOLD = 2000;
|
|
|
53
53
|
/** 图片卡片:caption(可选)显示在图片上方——逐张发图时带编号说明用 */
|
|
54
54
|
export declare function buildImageCard(caption: string | undefined, imgKey: string): unknown;
|
|
55
55
|
/** 进度卡局部更新的固定组件 ID(cardkit element_id,长度限 1-20):状态主块 + 计时行。
|
|
56
|
-
* 局部更新只替换这两个 markdown 组件的 content
|
|
57
|
-
*
|
|
56
|
+
* 局部更新只替换这两个 markdown 组件的 content,不触碰 form 组件定义——但真机实测
|
|
57
|
+
* (0.20.1)实体更新落地时部分客户端仍会重置 form 内未提交输入(官方错误码 200810 亦
|
|
58
|
+
* 表明交互期间流式更新受限),因此挂起输入期间的维持性刷新由 ProgressCard 冻结,不依赖
|
|
59
|
+
* 「局部更新保输入」这一假设 */
|
|
58
60
|
export declare const MAIN_ELEMENT_ID = "lcb_main";
|
|
59
61
|
export declare const TIMER_ELEMENT_ID = "lcb_timer";
|
|
60
62
|
export declare function buildProgressCard(state: ProgressState, widthMode?: 'default' | 'fill'): unknown;
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
|
|
64
|
+
* qa 选项按钮级局部更新 actions(cardkit partial_update_element):仅 qa_opt_* 按钮的
|
|
65
|
+
* 文案(✓ 前缀)与样式(primary/default),不含 main/timer——0.20.1 起这是挂起输入冻结
|
|
66
|
+
* 期间的唯一放行通道(用户刚点选项按钮的即时反馈,最小触碰 form 未提交输入)。
|
|
67
|
+
* question 未挂起时返回空数组(调用方需空判后视同冻结跳过)。
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildQaButtonActions(state: ProgressState): Array<{
|
|
70
|
+
action: string;
|
|
71
|
+
params: {
|
|
72
|
+
element_id: string;
|
|
73
|
+
partial_element: unknown;
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
/**
|
|
77
|
+
* 局部更新 actions(cardkit batch_update 的 partial_update_element):替换状态主块与
|
|
78
|
+
* 计时行两个 markdown 组件的 content。仅结构未变化且无挂起输入时使用(结构变化走全量
|
|
79
|
+
* 替换;挂起期间维持性刷新由 ProgressCard 冻结——真机实测实体更新落地会重置 form 未提交
|
|
80
|
+
* 输入,见 MAIN_ELEMENT_ID 处注释)。
|
|
81
|
+
* includeQaButtons=true 时追加 qa 选项按钮的局部替换(选中态变化不触发全量替换——那会
|
|
82
|
+
* 清空 form 内未提交的自定义输入,改走按钮 element_id 级局部更新)。
|
|
67
83
|
*/
|
|
68
84
|
export declare function buildPartialUpdateActions(state: ProgressState, includeQaButtons?: boolean): Array<{
|
|
69
85
|
action: string;
|
|
@@ -27,8 +27,10 @@ export function buildImageCard(caption, imgKey) {
|
|
|
27
27
|
return card(elements);
|
|
28
28
|
}
|
|
29
29
|
/** 进度卡局部更新的固定组件 ID(cardkit element_id,长度限 1-20):状态主块 + 计时行。
|
|
30
|
-
* 局部更新只替换这两个 markdown 组件的 content
|
|
31
|
-
*
|
|
30
|
+
* 局部更新只替换这两个 markdown 组件的 content,不触碰 form 组件定义——但真机实测
|
|
31
|
+
* (0.20.1)实体更新落地时部分客户端仍会重置 form 内未提交输入(官方错误码 200810 亦
|
|
32
|
+
* 表明交互期间流式更新受限),因此挂起输入期间的维持性刷新由 ProgressCard 冻结,不依赖
|
|
33
|
+
* 「局部更新保输入」这一假设 */
|
|
32
34
|
export const MAIN_ELEMENT_ID = 'lcb_main';
|
|
33
35
|
export const TIMER_ELEMENT_ID = 'lcb_timer';
|
|
34
36
|
/** 进度卡主块内容(标题/状态/工具行/子代理清单/收敛提示/终态结果尾部)——整卡渲染与局部更新共用 */
|
|
@@ -86,6 +88,12 @@ function buildMainLines(state) {
|
|
|
86
88
|
}
|
|
87
89
|
/** 计时行文案——整卡渲染与局部更新共用 */
|
|
88
90
|
function buildTimerLine(state) {
|
|
91
|
+
// 挂起等待用户输入(plan 意见框 / qa 作答)期间维持性刷新被冻结、计时停走——必须显式
|
|
92
|
+
// 标注暂停及恢复条件,不可静默停止(停走的计时读起来像「已结束/卡死」,用户分不清)
|
|
93
|
+
if (state.plan)
|
|
94
|
+
return `<font color='grey'>⏸ 计时已暂停 · 计划确认后恢复</font>`;
|
|
95
|
+
if (state.question)
|
|
96
|
+
return `<font color='grey'>⏸ 计时已暂停 · 提交答案后恢复</font>`;
|
|
89
97
|
const elapsed = Math.max(0, Math.floor((Date.now() - state.startedAt) / 1000));
|
|
90
98
|
const duration = `${Math.floor(elapsed / 60)} 分 ${elapsed % 60} 秒`;
|
|
91
99
|
// 终态改为「总耗时」+ 完成时刻:运行中的「已运行」在停止刷新后读起来仍像在计时,任务
|
|
@@ -191,35 +199,48 @@ export function buildProgressCard(state, widthMode = 'default') {
|
|
|
191
199
|
return card(elements, widthMode);
|
|
192
200
|
}
|
|
193
201
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
|
|
202
|
+
* qa 选项按钮级局部更新 actions(cardkit partial_update_element):仅 qa_opt_* 按钮的
|
|
203
|
+
* 文案(✓ 前缀)与样式(primary/default),不含 main/timer——0.20.1 起这是挂起输入冻结
|
|
204
|
+
* 期间的唯一放行通道(用户刚点选项按钮的即时反馈,最小触碰 form 未提交输入)。
|
|
205
|
+
* question 未挂起时返回空数组(调用方需空判后视同冻结跳过)。
|
|
206
|
+
*/
|
|
207
|
+
export function buildQaButtonActions(state) {
|
|
208
|
+
if (!state.question)
|
|
209
|
+
return [];
|
|
210
|
+
const q = state.question;
|
|
211
|
+
const actions = [];
|
|
212
|
+
q.questions.forEach((qq, qIndex) => {
|
|
213
|
+
const sel = q.answers[qIndex];
|
|
214
|
+
const picked = Array.isArray(sel) ? sel : sel !== undefined ? [sel] : [];
|
|
215
|
+
qq.options.forEach((o, optIndex) => {
|
|
216
|
+
const selected = picked.includes(o.label);
|
|
217
|
+
actions.push({
|
|
218
|
+
action: 'partial_update_element',
|
|
219
|
+
params: {
|
|
220
|
+
element_id: qaOptionElementId(qIndex, optIndex),
|
|
221
|
+
// 按钮可局部更新的字段:文案(✓ 前缀)与样式(primary/default)
|
|
222
|
+
partial_element: { text: { tag: 'plain_text', content: selected ? `✓ ${o.label}` : o.label }, type: selected ? 'primary' : 'default' },
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
return actions;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 局部更新 actions(cardkit batch_update 的 partial_update_element):替换状态主块与
|
|
231
|
+
* 计时行两个 markdown 组件的 content。仅结构未变化且无挂起输入时使用(结构变化走全量
|
|
232
|
+
* 替换;挂起期间维持性刷新由 ProgressCard 冻结——真机实测实体更新落地会重置 form 未提交
|
|
233
|
+
* 输入,见 MAIN_ELEMENT_ID 处注释)。
|
|
234
|
+
* includeQaButtons=true 时追加 qa 选项按钮的局部替换(选中态变化不触发全量替换——那会
|
|
235
|
+
* 清空 form 内未提交的自定义输入,改走按钮 element_id 级局部更新)。
|
|
199
236
|
*/
|
|
200
237
|
export function buildPartialUpdateActions(state, includeQaButtons = false) {
|
|
201
238
|
const actions = [
|
|
202
239
|
{ action: 'partial_update_element', params: { element_id: MAIN_ELEMENT_ID, partial_element: { content: buildMainLines(state).join('\n') } } },
|
|
203
240
|
{ action: 'partial_update_element', params: { element_id: TIMER_ELEMENT_ID, partial_element: { content: buildTimerLine(state) } } },
|
|
204
241
|
];
|
|
205
|
-
if (includeQaButtons
|
|
206
|
-
|
|
207
|
-
q.questions.forEach((qq, qIndex) => {
|
|
208
|
-
const sel = q.answers[qIndex];
|
|
209
|
-
const picked = Array.isArray(sel) ? sel : sel !== undefined ? [sel] : [];
|
|
210
|
-
qq.options.forEach((o, optIndex) => {
|
|
211
|
-
const selected = picked.includes(o.label);
|
|
212
|
-
actions.push({
|
|
213
|
-
action: 'partial_update_element',
|
|
214
|
-
params: {
|
|
215
|
-
element_id: qaOptionElementId(qIndex, optIndex),
|
|
216
|
-
// 按钮可局部更新的字段:文案(✓ 前缀)与样式(primary/default)
|
|
217
|
-
partial_element: { text: { tag: 'plain_text', content: selected ? `✓ ${o.label}` : o.label }, type: selected ? 'primary' : 'default' },
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
}
|
|
242
|
+
if (includeQaButtons)
|
|
243
|
+
actions.push(...buildQaButtonActions(state));
|
|
223
244
|
return actions;
|
|
224
245
|
}
|
|
225
246
|
export const DECISION_TEXT = {
|