lark-remote 0.2.2 → 0.4.0
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/CHANGELOG.md +33 -0
- package/README.en.md +8 -5
- package/README.md +10 -6
- package/dist/binder.d.ts +9 -16
- package/dist/binder.js +14 -40
- package/dist/bridge/final-usage.js +2 -2
- package/dist/bridge/inbound-media.js +7 -5
- package/dist/bridge/index.js +75 -75
- package/dist/clone.d.ts +139 -0
- package/dist/clone.js +415 -0
- package/dist/common/fs.js +1 -1
- package/dist/config/codex-config.js +79 -24
- package/dist/config/index.js +1 -1
- package/dist/config/kimi-config.js +16 -5
- package/dist/connector/index.d.ts +25 -0
- package/dist/connector/index.js +133 -50
- package/dist/error-classification.d.ts +7 -0
- package/dist/error-classification.js +37 -1
- package/dist/index.js +48 -15
- package/dist/platform/command.js +1 -1
- package/dist/platform/sleep-blocker.d.ts +17 -0
- package/dist/platform/sleep-blocker.js +73 -0
- package/dist/platform/terminator-win32.js +1 -1
- package/dist/restart.d.ts +20 -3
- package/dist/restart.js +42 -23
- package/dist/router/config/kimi.js +4 -3
- package/dist/router/index.d.ts +10 -0
- package/dist/router/index.js +44 -6
- package/dist/runner/common/acp/base-acp-runner.js +7 -2
- package/dist/runner/common/acp/base-translator.d.ts +19 -0
- package/dist/runner/common/acp/base-translator.js +82 -0
- package/dist/runner/common/acp/protocol-types.d.ts +16 -2
- package/dist/runner/common/acp/protocol-types.js +3 -0
- package/dist/runner/kimi/acp/translator.js +60 -4
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- **运行期间阻止系统休眠**:运行中自动阻止系统休眠(macOS 用 `caffeinate`、Windows 用 `SetThreadExecutionState`)——典型用法是人不在电脑前远程使用,系统一旦休眠连接即断。只阻止系统休眠,不影响显示器睡眠;退出(含异常退出)后自动解除,helper 意外退出也会留痕兜底。注意:MacBook 合盖睡眠无法阻止(除非接电源并外接显示器)
|
|
13
|
+
- **入站媒体保存提示带完整路径**:收到的图片/文件落盘后,提示带目录完整路径,方便直接定位
|
|
14
|
+
|
|
15
|
+
### 修复
|
|
16
|
+
|
|
17
|
+
- **卡片更新瞬态失败自动重试**:飞书卡片更新遇到连接瞬时中断等瞬态传输错误时就地重试(最多 3 次),不再把失败直接落到未处理 rejection 上导致进程退出
|
|
18
|
+
- **Kimi 审批卡恢复完整 Bash 命令**:审批卡片按 `toolCallId` 关联流式参数,恢复展示完整命令而非截断片段
|
|
19
|
+
- **Kimi 权限模式文案对齐 kimi 0.40+ 语义**:`yolo` 现指「必要时询问」而非完全放行,完全自动请选 `auto`;README 与配置文档同步更新
|
|
20
|
+
|
|
21
|
+
### 变更
|
|
22
|
+
|
|
23
|
+
- **文档自称统一**:README、使用文档与架构文档中的自称统一为 lark-remote
|
|
24
|
+
|
|
25
|
+
## [0.3.0] - 2026-09-13
|
|
26
|
+
|
|
27
|
+
### 新增
|
|
28
|
+
|
|
29
|
+
- **`/clone [name]`(复制分身)**:把当前配置目录克隆为一份新分身(`<configDir>-<name>`,省略名字时随机生成后缀;除飞书 `appId`/`appSecret` 外全部复制),引导你扫码创建全新飞书应用——二维码以图片消息直接发进私聊,约 10 分钟内有效。扫码成功后自动完成绑定并把 workspace 别名、常用指令、工作目录与会话状态一并同步到分身,随后自动拉起新实例(新应用会收到它的启动通知),原应用回执新应用 App ID、配置目录与进程号。等待扫码期间消息不转发给 agent:输入「重发」重新生成二维码,`/Q` 取消。`/help` 卡片的「/clone」按钮可直接点击开始
|
|
30
|
+
- **一键复制分身按钮**:`/help` 卡片按钮区新增「/clone」,与无参 `/clone` 等价
|
|
31
|
+
|
|
32
|
+
### 变更
|
|
33
|
+
|
|
34
|
+
- **首次绑定简化**:未绑定时首条私聊消息(任意内容)即完成 owner 绑定,移除原 4 位 PIN。飞书自建应用私聊入口本身由 owner 掌控,把应用分享出去才属主动行为;绑定成功会回执确认
|
|
35
|
+
|
|
36
|
+
### 修复
|
|
37
|
+
|
|
38
|
+
- **Codex / Kimi CLI 版本探测走平台 spawn 收口**:CLI 可执行文件探测统一经由跨平台 spawn 层,修复 Windows 下的探测兼容性问题
|
|
39
|
+
- **`bun run test:live` 退出码**:在 `bun run` 下正确透传退出码,不再误报失败
|
|
40
|
+
|
|
8
41
|
## [0.2.2] - 2026-09-12
|
|
9
42
|
|
|
10
43
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -16,7 +16,7 @@ Designed for a single-user, peer-to-peer private chat. The agent's system prompt
|
|
|
16
16
|
|
|
17
17
|
## ⚠️ Security Warning (read first)
|
|
18
18
|
|
|
19
|
-
This tool turns Feishu messages directly into local agent executions. **Out of the box, agents run without approval prompts**: Claude defaults to `bypassPermissions` (no approval card), but that permission mode is **configurable** — set it to anything other than `bypassPermissions` (`default` / `acceptEdits` / `auto` / `dontAsk` / `plan`) to enable interactive approval, where high-risk actions wait for your confirmation on the Feishu card. Codex uses the **app-server approval mode** (default `approvalPolicy=on-request` — command execution requires your confirmation on the Feishu card, sandbox defaults to `workspace-write`); Kimi defaults to `manual` (per-action approval); other agents run with their CLI defaults. **In any mode**, agents can read and write the local directories you specify. You MUST:
|
|
19
|
+
This tool turns Feishu messages directly into local agent executions. **Out of the box, agents run without approval prompts**: Claude defaults to `bypassPermissions` (no approval card), but that permission mode is **configurable** — set it to anything other than `bypassPermissions` (`default` / `acceptEdits` / `auto` / `dontAsk` / `plan`) to enable interactive approval, where high-risk actions wait for your confirmation on the Feishu card. Codex uses the **app-server approval mode** (default `approvalPolicy=on-request` — command execution requires your confirmation on the Feishu card, sandbox defaults to `workspace-write`); Kimi defaults to `manual` (per-action approval; note that since kimi 0.40, `yolo` means Ask-When-Needed rather than allow-everything — use `auto` for fully unattended, no-approval execution); other agents run with their CLI defaults. **In any mode**, agents can read and write the local directories you specify. You MUST:
|
|
20
20
|
|
|
21
21
|
- **Use it only in your own private (p2p) chat.** Never add the bot to any group chat; never let anyone else talk to it.
|
|
22
22
|
- Restrict the app's visibility to **yourself only** in the Feishu Open Platform.
|
|
@@ -52,7 +52,7 @@ git clone https://github.com/bungabungawoda/lark-remote.git
|
|
|
52
52
|
cd lark-remote
|
|
53
53
|
bun install
|
|
54
54
|
bun run build
|
|
55
|
-
bun install -g "$(pwd)" # install globally as
|
|
55
|
+
bun install -g "$(pwd)" # install globally as lark-remote command (bun parses `.` as an empty package name; use an absolute path)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Configuration
|
|
@@ -94,7 +94,7 @@ lark-remote --config-dir ~/.lark-remote-test # custom config dir (multiple in
|
|
|
94
94
|
lark-remote --settings ~/.claude/settings.json # specify Claude settings file
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
lark-remote prints nothing to the terminal after startup; logs go to `~/.lark-remote/logs/YYYY-MM-DD/lark-remote-<pid>.log` (rotated daily, one subdirectory per day). Only one `lark-remote` instance is allowed per `configDir` — a duplicate start fails with the existing pid. After connecting to Feishu, a startup notification (with start time and pid) is sent to the most recent private chat.
|
|
98
98
|
|
|
99
99
|
Each agent run creates one CardKit 2.0 card that updates in place with thinking, body text, and tool summaries in real time.
|
|
100
100
|
Timestamps are read from the JSONL and shown in local time as `YYYY-MM-DD HH:mm` for thinking, body, tool call/result, and session history events.
|
|
@@ -117,14 +117,17 @@ Your original message also gets an emoji reaction: `Typing` while the run is in
|
|
|
117
117
|
| `/status` | `/s` | Current directory, session, model, process status |
|
|
118
118
|
| `/stop` | `/t` | Kill the current agent process (SIGKILL) |
|
|
119
119
|
| `/ps` | - | Whether a process is running |
|
|
120
|
-
| `/restart` | - | In-place
|
|
120
|
+
| `/restart` | - | In-place lark-remote self-restart (new process takes over with the same config) |
|
|
121
|
+
| `/clone [name]` | - | Clone lark-remote: copies the current config into a new directory and walks you through creating a brand-new Feishu app by QR scan; on success it binds, syncs state, and launches the new instance |
|
|
121
122
|
| `/config get\|set` | `/c` | View/change runtime config (agent-aware card) |
|
|
122
123
|
| `/order save\|list\|edit` | `/o` | Save, list, or edit frequently used prompts |
|
|
123
124
|
| `!<cmd>` | - | Run a bash command with streaming card output (bypasses the serial queue) |
|
|
124
|
-
| `/exit` | `/e` | Exit
|
|
125
|
+
| `/exit` | `/e` | Exit lark-remote |
|
|
125
126
|
|
|
126
127
|
Any message not starting with `/` is forwarded to the current default agent.
|
|
127
128
|
|
|
129
|
+
**Clone the bridge (`/clone`)**: to run an independent bridge instance next to another project directory, send `/clone <name>` — the current config directory is cloned to `<configDir>-<name>` (everything except the Feishu credentials), a QR code is sent into the chat as an image to create a brand-new Feishu app, and once you scan it the new app is bound automatically with workspace aliases, saved prompts, working directory, and session state carried over; the new instance launches itself and is ready to chat. You can also hit the "/clone" button on the `/help` card. See [docs/en/usage.md](docs/en/usage.md) for details.
|
|
130
|
+
|
|
128
131
|
## Testing
|
|
129
132
|
|
|
130
133
|
```bash
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
## ⚠️ 安全警告(必读)
|
|
18
18
|
|
|
19
|
-
本工具会把飞书消息直接转为本地 agent 执行。**开箱默认是不弹审批的**:Claude 默认以 `bypassPermissions` 运行(无审批卡),但该权限模式**可配置**——把它改成 `bypassPermissions` 以外的值(`default` / `acceptEdits` / `auto` / `dontAsk` / `plan`)即启用交互式审批,高风险操作会在飞书卡片上等你确认;Codex 采用 **app-server 审批模式**(默认 `approvalPolicy=on-request`,执行命令需在飞书卡片上确认,沙箱默认 `workspace-write`);Kimi 默认 `manual
|
|
19
|
+
本工具会把飞书消息直接转为本地 agent 执行。**开箱默认是不弹审批的**:Claude 默认以 `bypassPermissions` 运行(无审批卡),但该权限模式**可配置**——把它改成 `bypassPermissions` 以外的值(`default` / `acceptEdits` / `auto` / `dontAsk` / `plan`)即启用交互式审批,高风险操作会在飞书卡片上等你确认;Codex 采用 **app-server 审批模式**(默认 `approvalPolicy=on-request`,执行命令需在飞书卡片上确认,沙箱默认 `workspace-write`);Kimi 默认 `manual`(逐条审批;注意 kimi 0.40+ 起 `yolo` 已改为「必要时询问」而非全部放行,要完全不弹审批需选 `auto`);其他 agent 以各自 CLI 的默认权限运行。**无论哪种模式**,agent 都能读写你指定的本地目录,请务必:
|
|
20
20
|
|
|
21
21
|
- **仅限你自己的私聊(p2p)使用**。不要把机器人拉进任何群聊,不要让任何其他人能与它对话。
|
|
22
22
|
- 在飞书开放平台把应用可见范围限制到**只有你自己**。
|
|
@@ -94,7 +94,7 @@ lark-remote --config-dir ~/.lark-remote-test # 自定义配置目录(同机
|
|
|
94
94
|
lark-remote --settings ~/.claude/settings.json # 指定 Claude 配置文件
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
lark-remote 启动后不在终端输出,运行日志写入 `~/.lark-remote/logs/YYYY-MM-DD/lark-remote-<pid>.log`(按日期轮转,每天一个子目录)。同一 `configDir` 只允许一个 `lark-remote` 实例,重复启动会直接报错并提示已有 pid。连接飞书成功后会向最近私聊用户发送启动通知,包含启动时间和进程号。
|
|
98
98
|
|
|
99
99
|
每次 agent 运行会创建一张 CardKit 2.0 卡片,并在原地实时更新 thinking、正文和工具摘要。
|
|
100
100
|
卡片会读取 JSONL 的 `timestamp`,以本地时间 `YYYY-MM-DD HH:mm` 显示 thinking、正文、tool call/result 和会话历史事件。
|
|
@@ -117,17 +117,21 @@ bridge 启动后不在终端输出,运行日志写入 `~/.lark-remote/logs/YYY
|
|
|
117
117
|
| `/status` | `/s` | 当前目录、session、模型、进程状态 |
|
|
118
118
|
| `/stop` | `/t` | 终止当前 agent 进程(SIGKILL) |
|
|
119
119
|
| `/ps` | - | 是否有进程在跑 |
|
|
120
|
-
| `/restart` | - | 原地自重启
|
|
120
|
+
| `/restart` | - | 原地自重启 lark-remote(新进程同 config 接管) |
|
|
121
|
+
| `/clone [name]` | - | 复制分身:克隆当前配置到新目录并扫码创建新飞书应用,完成后自动绑定、同步状态并拉起新实例 |
|
|
121
122
|
| `/config get\|set` | `/c` | 查改运行时配置(agent-aware 卡片) |
|
|
122
123
|
| `/order save\|list\|edit\|alias` | `/o` | 收藏常用指令;`/order edit` 修改指令文本(保留别名/使用统计);`/order alias` 注册快捷别名(输入 `$name` 展开) |
|
|
123
124
|
| `!<cmd>` | - | 执行 bash 命令并流式输出到卡片(绕过串行队列) |
|
|
124
|
-
| `/exit` | `/e` | 退出
|
|
125
|
+
| `/exit` | `/e` | 退出 lark-remote |
|
|
125
126
|
|
|
126
127
|
直接发非 `/` 开头的消息即转发给当前默认 agent。
|
|
127
128
|
|
|
129
|
+
**复制分身(`/clone`)**:想在另一个项目/机器目录旁跑一个独立 lark-remote 实例时,发 `/clone 名字` 即可——当前配置目录会被克隆为 `<configDir>-<名字>`(除飞书凭据外全部复制),二维码以图片消息发进私聊引导你扫码创建一个全新飞书应用;扫码成功后自动完成绑定,workspace 别名、常用指令、工作目录与会话状态一并迁移,新实例自动拉起并可直接续聊。也可直接点 `/help` 卡片上的「/clone」按钮。详见 [docs/zh/usage.md](docs/zh/usage.md)。
|
|
130
|
+
|
|
128
131
|
往飞书私聊发**图片/文件**会自动保存到当前目录的 `.lark-remote-temp/<YYYYMMDDHHmm>/`
|
|
129
|
-
(建议把 `.lark-remote-temp/` 加进项目 `.gitignore
|
|
130
|
-
|
|
132
|
+
(建议把 `.lark-remote-temp/` 加进项目 `.gitignore`),保存提示里会带上目录完整路径,
|
|
133
|
+
随后说「请处理刚才保存的文件」(或直接把提示转给 agent)即可让 agent 直接读本地文件。
|
|
134
|
+
更多用法见 [docs/zh/usage.md](docs/zh/usage.md)。
|
|
131
135
|
|
|
132
136
|
## 测试
|
|
133
137
|
|
package/dist/binder.d.ts
CHANGED
|
@@ -6,31 +6,25 @@ type BindDecision = {
|
|
|
6
6
|
kind: 'rejected';
|
|
7
7
|
} | {
|
|
8
8
|
kind: 'bind_success';
|
|
9
|
-
} | {
|
|
10
|
-
kind: 'pin_wrong';
|
|
11
9
|
};
|
|
12
10
|
/**
|
|
13
|
-
* Owner
|
|
11
|
+
* Owner 绑定器:首次私聊发送任意消息完成"认领",此后仅该 openId 可用。
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
* owner
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* 设计依据:lark-remote 进程持有用户本机完整权限,唯一合法主体是 owner 本人。
|
|
14
|
+
* 飞书自建应用的私聊入口本身就是 owner 掌控的(应用只有 owner 能看到/使用,
|
|
15
|
+
* 分享出去属于 owner 的主动行为),因此首次消息即绑定,不再要求输入 PIN。
|
|
16
|
+
* 本类复用已有 startup-contact.json,不新增配置字段。
|
|
19
17
|
*
|
|
20
18
|
* 状态机:
|
|
21
|
-
* -
|
|
22
|
-
* 输错 PIN 完全静默(不回复、不提醒);首次启动仅用户本机可见,无需严格防枚举。
|
|
19
|
+
* - 未绑定:第一条私聊消息(任意内容)即完成绑定 -> 写入 startup-contact.json。
|
|
23
20
|
* - 已绑定:仅 senderId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
|
|
24
21
|
*
|
|
25
22
|
* cardAction 同样校验 operator.openId === bound.userId(未绑定也视为非 owner)。
|
|
26
23
|
*/
|
|
27
24
|
export declare class OwnerBinder {
|
|
28
25
|
private readonly store;
|
|
29
|
-
private pin;
|
|
30
26
|
private rejectedTotal;
|
|
31
27
|
constructor(store: StartupContactStore);
|
|
32
|
-
/** 仅未绑定时返回待输入的 PIN(控制台展示用)。 */
|
|
33
|
-
get pendingPin(): string | undefined;
|
|
34
28
|
isBound(): boolean;
|
|
35
29
|
/** 已绑定的 owner openId;未绑定时 undefined。 */
|
|
36
30
|
boundOpenId(): string | undefined;
|
|
@@ -42,16 +36,15 @@ export declare class OwnerBinder {
|
|
|
42
36
|
* 对入站私聊消息做绑定/授权判定,含副作用:
|
|
43
37
|
* - `owner`:放行,调用方继续正常处理
|
|
44
38
|
* - `rejected`:已绑定但非 owner,静默丢弃(计数 + debug)
|
|
45
|
-
* - `bind_success
|
|
46
|
-
* - `pin_wrong`:未绑定且 PIN 错误,静默丢弃(不回复、不提醒)
|
|
39
|
+
* - `bind_success`:未绑定收到首条消息,写入绑定(任意内容均可)
|
|
47
40
|
*/
|
|
48
41
|
classify(senderId: string, content: string, chatId: string): BindDecision;
|
|
49
42
|
/** 卡片操作被拒时计数(与消息路径共用计数器)。 */
|
|
50
43
|
recordRejectedCardAction(openId: string): void;
|
|
51
44
|
}
|
|
52
45
|
/**
|
|
53
|
-
*
|
|
46
|
+
* 控制台绑定引导文案(首次未绑定时输出到 stderr;守护模式下被 watchdog
|
|
54
47
|
* 重定向到 daemon 日志)。绑定后此文案不再出现。
|
|
55
48
|
*/
|
|
56
|
-
export declare function
|
|
49
|
+
export declare function formatBindGuidance(): string;
|
|
57
50
|
export {};
|
package/dist/binder.js
CHANGED
|
@@ -1,40 +1,23 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
1
|
import { getLogger } from './logger/index.js';
|
|
3
|
-
/** 绑定 PIN 位数。4 位数字便于人工输入。 */
|
|
4
|
-
const PIN_DIGITS = 4;
|
|
5
|
-
/** 用 CSPRNG 生成固定长度、零填充的数字 PIN(首位可为 0)。 */
|
|
6
|
-
function generatePin() {
|
|
7
|
-
const n = crypto.randomInt(0, 10 ** PIN_DIGITS);
|
|
8
|
-
return String(n).padStart(PIN_DIGITS, '0');
|
|
9
|
-
}
|
|
10
2
|
/**
|
|
11
|
-
* Owner
|
|
3
|
+
* Owner 绑定器:首次私聊发送任意消息完成"认领",此后仅该 openId 可用。
|
|
12
4
|
*
|
|
13
|
-
*
|
|
14
|
-
* owner
|
|
15
|
-
*
|
|
16
|
-
*
|
|
5
|
+
* 设计依据:lark-remote 进程持有用户本机完整权限,唯一合法主体是 owner 本人。
|
|
6
|
+
* 飞书自建应用的私聊入口本身就是 owner 掌控的(应用只有 owner 能看到/使用,
|
|
7
|
+
* 分享出去属于 owner 的主动行为),因此首次消息即绑定,不再要求输入 PIN。
|
|
8
|
+
* 本类复用已有 startup-contact.json,不新增配置字段。
|
|
17
9
|
*
|
|
18
10
|
* 状态机:
|
|
19
|
-
* -
|
|
20
|
-
* 输错 PIN 完全静默(不回复、不提醒);首次启动仅用户本机可见,无需严格防枚举。
|
|
11
|
+
* - 未绑定:第一条私聊消息(任意内容)即完成绑定 -> 写入 startup-contact.json。
|
|
21
12
|
* - 已绑定:仅 senderId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
|
|
22
13
|
*
|
|
23
14
|
* cardAction 同样校验 operator.openId === bound.userId(未绑定也视为非 owner)。
|
|
24
15
|
*/
|
|
25
16
|
export class OwnerBinder {
|
|
26
17
|
store;
|
|
27
|
-
pin;
|
|
28
18
|
rejectedTotal = 0;
|
|
29
19
|
constructor(store) {
|
|
30
20
|
this.store = store;
|
|
31
|
-
if (!store.getContact()) {
|
|
32
|
-
this.pin = generatePin();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/** 仅未绑定时返回待输入的 PIN(控制台展示用)。 */
|
|
36
|
-
get pendingPin() {
|
|
37
|
-
return this.pin;
|
|
38
21
|
}
|
|
39
22
|
isBound() {
|
|
40
23
|
return this.store.getContact() !== undefined;
|
|
@@ -56,8 +39,7 @@ export class OwnerBinder {
|
|
|
56
39
|
* 对入站私聊消息做绑定/授权判定,含副作用:
|
|
57
40
|
* - `owner`:放行,调用方继续正常处理
|
|
58
41
|
* - `rejected`:已绑定但非 owner,静默丢弃(计数 + debug)
|
|
59
|
-
* - `bind_success
|
|
60
|
-
* - `pin_wrong`:未绑定且 PIN 错误,静默丢弃(不回复、不提醒)
|
|
42
|
+
* - `bind_success`:未绑定收到首条消息,写入绑定(任意内容均可)
|
|
61
43
|
*/
|
|
62
44
|
classify(senderId, content, chatId) {
|
|
63
45
|
const bound = this.store.getContact();
|
|
@@ -68,16 +50,10 @@ export class OwnerBinder {
|
|
|
68
50
|
getLogger().debug(`[binder] rejected message from ${senderId} (total rejected=${this.rejectedCount})`);
|
|
69
51
|
return { kind: 'rejected' };
|
|
70
52
|
}
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
getLogger().info(`[binder] owner bound: openId=${senderId} chatId=${chatId}`);
|
|
76
|
-
return { kind: 'bind_success' };
|
|
77
|
-
}
|
|
78
|
-
// 输错 PIN:完全静默(不回复、不提醒、不计数),仅内部 debug 日志
|
|
79
|
-
getLogger().debug('[binder] wrong pin (unbound, ignored)');
|
|
80
|
-
return { kind: 'pin_wrong' };
|
|
53
|
+
// 未绑定:任意内容即绑定(首条消息完成认领)
|
|
54
|
+
this.store.save({ chatId, userId: senderId });
|
|
55
|
+
getLogger().info(`[binder] owner bound: openId=${senderId} chatId=${chatId}`);
|
|
56
|
+
return { kind: 'bind_success' };
|
|
81
57
|
}
|
|
82
58
|
/** 卡片操作被拒时计数(与消息路径共用计数器)。 */
|
|
83
59
|
recordRejectedCardAction(openId) {
|
|
@@ -86,16 +62,14 @@ export class OwnerBinder {
|
|
|
86
62
|
}
|
|
87
63
|
}
|
|
88
64
|
/**
|
|
89
|
-
*
|
|
65
|
+
* 控制台绑定引导文案(首次未绑定时输出到 stderr;守护模式下被 watchdog
|
|
90
66
|
* 重定向到 daemon 日志)。绑定后此文案不再出现。
|
|
91
67
|
*/
|
|
92
|
-
export function
|
|
68
|
+
export function formatBindGuidance() {
|
|
93
69
|
return [
|
|
94
70
|
'',
|
|
95
71
|
'🔒 首次绑定',
|
|
96
|
-
'
|
|
97
|
-
'',
|
|
98
|
-
` ${pin}`,
|
|
72
|
+
'请在飞书私聊本应用,发送任意消息完成绑定:',
|
|
99
73
|
'',
|
|
100
74
|
'绑定后仅该账号可使用本应用。',
|
|
101
75
|
'需更换账号:删除 <configDir>/startup-contact.json 后重启。',
|
|
@@ -26,7 +26,7 @@ export function resolveFinalUsage(registry, defaultAgent, sessionId, cwd, agentK
|
|
|
26
26
|
// EnterWorktree relocate (2026-08-04): jsonl read silently returned no
|
|
27
27
|
// usage (e.g. transcript moved mid-session). Surface it so token-stat
|
|
28
28
|
// fallback to per-run live usage is visible in logs, not silent.
|
|
29
|
-
getLogger().warn(`[
|
|
29
|
+
getLogger().warn(`[lark-remote] resolveFinalUsage: no usage from jsonl sessionId=${sessionId} cwd=${cwd} agent=${agentKind}, card falls back to per-run live usage`);
|
|
30
30
|
}
|
|
31
31
|
return content.usage
|
|
32
32
|
? {
|
|
@@ -48,7 +48,7 @@ export function resolveFinalUsage(registry, defaultAgent, sessionId, cwd, agentK
|
|
|
48
48
|
: undefined;
|
|
49
49
|
}
|
|
50
50
|
catch (err) {
|
|
51
|
-
getLogger().warn(`[
|
|
51
|
+
getLogger().warn(`[lark-remote] resolveFinalUsage failed sessionId=${sessionId}: ${errorMessage(err)}`);
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -266,11 +266,13 @@ export class InboundMediaHandler {
|
|
|
266
266
|
const lines = batch.saved.slice(0, MAX_PATHS_IN_NOTIFICATION).map((p) => `- ${p}`);
|
|
267
267
|
const overflow = batch.saved.length > MAX_PATHS_IN_NOTIFICATION ? `\n… 等 ${batch.saved.length} 个文件` : '';
|
|
268
268
|
const failures = batch.errors.length > 0 ? `\n⚠️ ${batch.errors.join(';')}` : '';
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
// 提示带上保存目录:用户把整句转给 agent 即可定位,无需靠目录约定反查。
|
|
270
|
+
// 合批窗口跨分钟时可能落在两个目录,此时全部列出。
|
|
271
|
+
const dirs = [...new Set(batch.saved.map((p) => path.dirname(p)))];
|
|
272
|
+
const hint = dirs.length === 1
|
|
273
|
+
? `\n💡 你可以直接说:请处理 ${dirs[0]} 下的文件`
|
|
274
|
+
: `\n💡 你可以直接说:请处理以下目录下的文件:${dirs.join('、')}`;
|
|
275
|
+
const text = `📎 已保存 ${batch.saved.length} 个文件:\n` + lines.join('\n') + overflow + failures + hint;
|
|
274
276
|
await this.deps.send({
|
|
275
277
|
userId: batch.payload.userId,
|
|
276
278
|
chatId: batch.payload.chatId,
|