eve-lark 0.3.0 → 0.3.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.zh-CN.md CHANGED
@@ -2,48 +2,48 @@
2
2
 
3
3
  [English](./README.md) | 简体中文
4
4
 
5
- 一个为 [eve](https://eve.dev) agent 框架打造的 [Lark](https://www.larksuite.com) / [Feishu](https://www.feishu.cn) 通道。把工厂函数放到 `agent/channels/lark.ts`,eve 就会挂载一个 Lark webhook,把收到的私聊消息和群组 @ 提及转换成流式交互卡片回复。
5
+ 一个为 [eve](https://eve.dev) agent 框架打造的 [Lark](https://www.larksuite.com) / [Feishu](https://www.feishu.cn) 通道。把工厂函数放到 `agent/channels/lark.ts`,eve 就会挂载一个 Lark webhook,把收到的私聊消息和群组 @ 提及转成回复。
6
6
 
7
7
  ## 特性
8
8
 
9
9
  **入站**
10
- - 文本、富文本(`post`)、`@` 提及(包括 `@all`)
11
- - 图片和文件附件(服务端下载并暂存给模型使用)
12
- - 通过 `root_id` / `parent_id` 实现的话题回复
13
- - `event_id` 去重(处理 Feishu 的 at-least-once 重试)
10
+ - 文本、富文本(`post`)、`@`-提及(包括 `@all`)
11
+ - 图片和文件附件(服务端下载并 stage 给模型)
12
+ - 通过 `root_id` / `parent_id` 跟踪线程
13
+ - `event_id` 去重(应对飞书 at-least-once 重试)
14
14
 
15
15
  **出站**
16
- - 流式交互卡片(在对话过程中实时 patch 更新)—— 默认模式
17
- - 静态一次性卡片回复 —— 可配置
18
- - 话题回复保留原始 `root_id`
16
+ - 流式交互卡片(对话过程中实时 patch)—— 可选模式
17
+ - `post` 富文本消息(**默认**,渲染为原生聊天消息大小,支持 markdown)
18
+ - 静态一次性卡片 —— 可选
19
+ - 线程回复保留原始 `root_id`
19
20
 
20
21
  **安全**
21
- - `X-Lark-Signature` 签名校验(`sha256(timestamp + nonce + encrypt_key + body)`,恒定时间比较)
22
- - 当配置了 `encryptKey` 时,对 `encrypt` 信封进行 AES-256-CBC 解密
23
- - 时间戳偏移窗口(默认 5 分钟)
24
- - 抑制 bot 自身发出的消息
22
+ - `X-Lark-Signature` 校验(`sha256(timestamp + nonce + encrypt_key + body)`,constant-time)
23
+ - 当配置了 `encryptKey` 时,AES-256-CBC 解密 `encrypt` 信封
24
+ - 时间戳偏差窗口(默认 5 分钟)
25
+ - 抑制 bot 自己发的消息
25
26
 
26
- **Feishu Lark 都支持**,只需切换一个 `baseUrl` 即可。
27
+ **交互式 ask_question**——当模型调用 eve 内置的 `ask_question` 工具时,eve-lark 会把提示渲染成一张飞书交互卡片,每个选项对应一个按钮(option.style `primary` / `default` / `danger` 直接映射到飞书 button type)。用户点击触发 `card.action.trigger` 回调,channel 把答案作为 `InputResponse` 发回 eve,parked session 恢复。`allowFreeform: true` 允许用户直接回复普通聊天消息代替点击——下一条同 chat 内的消息会被拦截为答案。回答后卡片原地更新(移除按钮,选中项以绿色 ✓ 标记)。
27
28
 
28
- ### v1 暂不支持
29
+ **Feishu(飞书)和 Lark(国际版)** 通过单一的 `baseUrl` 切换支持。
29
30
 
30
- 以下能力**有意**没有发布 —— 如果需要请提 issue:
31
- - 入站的音频 / 媒体 / 贴纸 / share_chat / share_user(仅 ack 并跳过)
31
+ ### 不在 v1 范围内
32
+
33
+ 以下功能**未实现**——需要的话请提 issue:
34
+ - 音频 / 媒体 / sticker / share_chat / share_user 入站(仅 ack-and-skip)
32
35
  - 多账号配置
33
- - 用户级 OAuth(`user_access_token` 设备流程)
34
- - Feishu API 工具(文档 / 多维表格 / 日历 / 任务 / 云盘)
35
- - 卡片动作按钮(不支持交互式表单)
36
+ - 用户级 OAuth(`user_access_token` device flow)
37
+ - 飞书 API 工具(docs / bitable / calendar / tasks / drive)
36
38
 
37
- ## 快速开始
39
+ > `ask_question` 的卡片按钮**已实现**(0.3.0+)。下面列表中剩下的「Card action buttons」指的是 agent 自己生成的完全自定义的卡片 schema。
40
+ - Card action buttons(agent 自定义的交互表单)
38
41
 
39
- 安装:
42
+ ## 快速开始
40
43
 
41
- ```bash
42
- pnpm add eve-lark
43
- # 或者:npm install eve-lark / yarn add eve-lark
44
- ```
44
+ 两步。一个文件,一条命令。
45
45
 
46
- 在你的 eve agent 中创建通道:
46
+ **1. 声明 channel:**
47
47
 
48
48
  ```ts
49
49
  // agent/channels/lark.ts
@@ -58,19 +58,29 @@ export default createLarkChannel({
58
58
  });
59
59
  ```
60
60
 
61
- 然后在 [Feishu 开发者后台](https://open.feishu.cn/app)(或 [Lark 开发者后台](https://open.larksuite.com/app))中:
61
+ **2. `eve dev`:**
62
62
 
63
- 1. 创建一个**自建应用**,记录 `App ID` 和 `App Secret`。
64
- 2. 在**事件订阅**中,把请求 URL 设置为你的 agent 的 `/lark/webhook`(可通过 `webhookPath` 选项覆盖)。
65
- 3. 生成 **Verification Token** 和 **Encrypt Key** —— 都复制到你的环境变量里。
66
- 4. 订阅 `im.message.receive_v1` 事件。
67
- 5. 把 bot 拉进群组或直接私聊。
63
+ ```bash
64
+ pnpm add eve-lark eve
65
+ eve dev
66
+ ```
67
+
68
+ 完事。channel 在构造时副作用启动一个飞书 `WSClient`——飞书只看到这条出站 WebSocket,**本地开发不需要公网 webhook URL**。每个事件被重新签名 + 重新加密,POST 到 channel 自己在 `localhost` 的 webhook,由标准 handler 处理(完整 `send()` 访问权限)。
69
+
70
+ 在 [飞书开发者后台](https://open.feishu.cn/app):
71
+ 1. 创建**自建应用**。记下 `App ID` 和 `App Secret`。
72
+ 2. 进入**事件订阅**,选择**「使用长连接接收事件」**模式(不是 HTTP 回调)。
73
+ 3. 生成**Verification Token** 和**Encrypt Key**——都填进你的 env。
74
+ 4. 订阅 `im.message.receive_v1`。
75
+ 5. 把 bot 拉进群或直接私聊。
76
+
77
+ 生产部署时,在飞书后台切回 HTTP 回调模式,并给 `createLarkChannel` 传 `mode: "webhook"`。详见[生产部署](#生产部署)。
68
78
 
69
79
  ## 配置参考
70
80
 
71
- 所有字段都可以通过选项传入,或从对应的环境变量读取(选项优先)。
81
+ 所有字段既能作为选项传入,也能从对应 env var 读取(选项优先)。
72
82
 
73
- | 字段 | 类型 | 必填 | 默认值 | 环境变量 |
83
+ | 字段 | 类型 | 必填 | 默认 | env var |
74
84
  |---|---|---|---|---|
75
85
  | `appId` | `string` | 是 | — | `LARK_APP_ID` |
76
86
  | `appSecret` | `string` | 是 | — | `LARK_APP_SECRET` |
@@ -78,8 +88,10 @@ export default createLarkChannel({
78
88
  | `encryptKey` | `string` | 否 | — | `LARK_ENCRYPT_KEY` |
79
89
  | `baseUrl` | `string` | 否 | `https://open.feishu.cn` | `LARK_BASE_URL` |
80
90
  | `botOpenId` | `string` | 否 | — | `LARK_BOT_OPEN_ID` |
91
+ | `mode` | `"long-connection" \| "webhook"` | 否 | `"long-connection"` | `LARK_MODE` |
92
+ | `port` | `number` | 否 | `$PORT` 或 `2000` | `PORT` |
81
93
  | `webhookPath` | `string` | 否 | `/lark/webhook` | — |
82
- | `replyMode` | `"streaming" \| "static"` | 否 | `"streaming"` | |
94
+ | `replyMode` | `"post" \| "streaming" \| "static"` | 否 | `"post"` | `LARK_REPLY_MODE` |
83
95
  | `streamPatchIntervalMs` | `number` | 否 | `1000` | — |
84
96
  | `streamCreateThresholdMs` | `number` | 否 | `400` | — |
85
97
  | `dedupTtlMs` | `number` | 否 | `1_800_000`(30 分钟) | — |
@@ -88,11 +100,12 @@ export default createLarkChannel({
88
100
  | `maxRetries` | `number` | 否 | `2` | — |
89
101
  | `tokenRefreshBufferMs` | `number` | 否 | `300_000`(5 分钟) | — |
90
102
  | `signatureSkewMs` | `number` | 否 | `300_000`(5 分钟) | — |
103
+ | `ackReaction` | `string \| readonly string[] \| false` | 否 | `"Typing"` | — |
91
104
  | `fetch` | `typeof fetch` | 否 | `globalThis.fetch` | — |
92
105
 
93
- ## Feishu Lark(国际版)
106
+ ## Feishu vs Lark(国际版)
94
107
 
95
- 两套部署使用相同的 API。通过 `baseUrl` 切换:
108
+ 两个部署用同一套 API。通过 `baseUrl` 切换:
96
109
 
97
110
  ```ts
98
111
  createLarkChannel({
@@ -101,122 +114,141 @@ createLarkChannel({
101
114
  });
102
115
  ```
103
116
 
104
- 或通过环境变量:`LARK_BASE_URL=https://open.larksuite.com`。
117
+ 或通过 env:`LARK_BASE_URL=https://open.larksuite.com`。
118
+
119
+ ## 回复模式
105
120
 
106
- ## 流式 vs 静态模式
121
+ - **`post`**(默认):channel `message.completed`,把回复作为 `msg_type: "post"` 富文本消息发出。**渲染为原生聊天消息大小**,完整支持 markdown(粗体、链接、代码、`<font>` 颜色 tag)。代价:不能流式——用户在 turn 完成时才看到回复。
122
+ - **`streaming`**:channel 在第一个 delta 时创建交互卡片,节流地实时 patch(约 1 秒一次),turn 完成时收尾。**实时 UX 好**,但卡片文字比原生消息字号小(飞书把卡片当作「结构化内容」)。
123
+ - **`static`**:和 `post` 一样等完成再发,但用交互卡片而非 post。适合需要卡片特性(按钮、多列布局)且不在乎字号小的场景。
107
124
 
108
- - **`streaming`**(默认):通道在第一个 delta 时创建交互卡片,节流地实时 patch(约 1 秒一次),并在回合结束时收尾。用户体验最好。
109
- - **`static`**:通道等待 `message.completed`,然后一次性发送包含完整答案的卡片。API 调用量更低;当你撞上 Feishu 的 PATCH 限流时有用。
125
+ 流式节流通过 `streamPatchIntervalMs` 调整(值越小越平滑,但 API 调用越多)。
110
126
 
111
- 通过 `streamPatchIntervalMs` 调节节流间隔(值越小越平滑,API 调用越多)。
127
+ ```bash
128
+ LARK_REPLY_MODE=streaming # 切到实时 patch
129
+ ```
112
130
 
113
- ## 续接 token 与话题
131
+ ## Continuation token 与线程
114
132
 
115
- eve-lark 使用 chat id 加上话题根消息 id 作为会话续接 token:
133
+ eve-lark chat id 加线程 root message id 作为 session continuation token:
116
134
 
117
135
  ```
118
136
  <chat_id>:<root_message_id>
119
137
  ```
120
138
 
121
- 对于顶层会话,root 是 `_`:
139
+ 对于顶层对话,root 是 `_`:
122
140
 
123
141
  ```
124
- oc_xxx:_ — 顶层对话
125
- oc_xxx:om_yyy — om_yyy 话题中的回复
142
+ oc_xxx:_ — 顶层
143
+ oc_xxx:om_yyy — om_yyy 线程里的回复
126
144
  ```
127
145
 
128
- 话题中的回复会跨回合保持话题锚点。该 token 按通道 id 命名空间化(eve 框架会前置通道文件名),所以同时部署多个自定义通道与 `lark` 共存是安全的。
146
+ 线程内的回复跨 turn 保留 thread anchor。token channel id 命名空间隔离(eve 框架在前面拼上 channel 文件名),所以可以同时挂多个自定义 channel。
129
147
 
130
148
  ## 安全模型
131
149
 
132
- - **签名校验**:当设置了 `encryptKey` 时,每个入站 webhook 必须携带有效的 `X-Lark-Signature` 头。不匹配返回 HTTP 401。
133
- - **AES 解密**:设置了 `encryptKey` 时,使用 AES-256-CBC 解密 `encrypt` 信封,其中 `key = SHA256(encrypt_key)`,IV 取前 16 字节。
134
- - **时间戳偏移**:早于 `signatureSkewMs` 的请求会以 HTTP 408 拒绝。
135
- - **去重**:`event_id` 会被记住 `dedupTtlMs` 时间。重放返回 200 但不会重新启动回合。
136
- - **Serverless 注意事项**:去重是进程内的。多实例部署在极端时序窗口下可能会重复处理同一事件 —— 请把你的工具做成幂等的。
150
+ - **签名校验**:设置了 `encryptKey` 时,每个入站 webhook 必须带有效的 `X-Lark-Signature` 头。不匹配返回 HTTP 401。
151
+ - **AES 解密**:设置了 `encryptKey` 时,`encrypt` 信封用 AES-256-CBC 解密,`key = SHA256(encrypt_key)`,前 16 字节作为 IV。
152
+ - **时间戳偏差**:超过 `signatureSkewMs` 的请求返回 HTTP 408
153
+ - **去重**:`event_id` 记忆 `dedupTtlMs` 时长。重放返回 200,不重新启动 turn。
154
+ - **Serverless 注意**:去重是进程内的。多实例部署在极少数 timing 窗口下可能 double-process 事件——让你的工具幂等。
137
155
 
138
- ## 文件与图片入站
156
+ ## 文件 & 图片入站
139
157
 
140
- 入站的图片/文件消息会被转换成 eve `UserContent` 文件 part。其 `data` 字段是一个指向 Lark 资源端点的 `URL`,所以 eve 的管道会调用通道的 `fetchFile` 钩子(使用 bot 的 `tenant_access_token`)把字节暂存给模型。
158
+ 入站的图片/文件消息会被转成 eve `UserContent` file part。`data` 字段是指向飞书 resource endpoint `URL`,所以 eve pipeline 会调用 channel 的 `fetchFile` 钩子(用 bot 的 `tenant_access_token`)把字节 stage 给模型。
141
159
 
142
- 如果你希望 URL part 直接透传而不暂存字节(例如在 eve sandbox 之外运行),不要设置 `encryptKey`,并在你的工具里检查 `attributes`。
160
+ 如果你想让 URL 部分直接透传(比如在 eve sandbox 外运行),不要设 `encryptKey`,改在工具里读 `attributes`。
143
161
 
144
162
  ## 错误
145
163
 
146
- eve-lark 抛出一个小的有类型层次结构:
164
+ eve-lark 抛出一组类型化错误:
147
165
 
148
166
  ```
149
167
  LarkChannelError
150
- ├── LarkConfigError — 缺少必填选项
151
- ├── LarkSignatureError — 签名校验失败(很少抛出;通常返回 401 Response
168
+ ├── LarkConfigError — 缺必填选项
169
+ ├── LarkSignatureError — 签名校验失败(很少抛;通常返回 401)
152
170
  ├── LarkDecryptError — AES 解密失败
153
- └── LarkApiError — Lark API 调用失败(携带 .code、.status、.body)
171
+ └── LarkApiError — Lark API 调用失败(带 .code、.status、.body)
154
172
  ```
155
173
 
156
- webhook 处理器返回结构化的 HTTP 响应,方便服务端处理:
174
+ webhook handler 返回结构化 HTTP 响应,方便服务端处理:
157
175
 
158
- | 状态码 | 原因 |
176
+ | Status | 原因 |
159
177
  |---|---|
160
- | 200 | Ack(成功或故意忽略的事件) |
161
- | 400 | JSON 无效 / 解密失败 |
162
- | 401 | 签名缺失/无效,或 verification token 不匹配 |
163
- | 408 | 超出时间戳偏移窗口 |
178
+ | 200 | Ack(成功或有意忽略的事件) |
179
+ | 400 | 无效 JSON / 解密失败 |
180
+ | 401 | 签名缺失/无效或 verification token 不匹配 |
181
+ | 408 | 时间戳偏差超过窗口 |
182
+ | 413 | 请求 body 超过 1 MB 上限 |
164
183
 
165
- ## 限制与路线图
184
+ ## 限制 & roadmap
166
185
 
167
- **v1 限制**:见 [暂不支持](#v1-暂不支持)。
186
+ **v1 限制**:见[不在范围内](#不在-v1-范围内)。
168
187
 
169
- **v2 规划**(如果你希望优先实现某项,欢迎提 issue):
170
- - 卡片动作按钮处理(交互式表单、确认流程)
188
+ **v2 计划**(想优先哪个就开 issue):
189
+ - 完全自定义的卡片交互(agent 自渲染表单、确认流)
171
190
  - 音频 / 媒体入站转写
172
- - 可选的 Redis 支持的多实例去重
173
- - 用户级 OAuth(`user_access_token`),用于 Feishu API 工具
191
+ - 可选的 Redis 后端去重,支持多实例部署
192
+ - 用户级 OAuth(`user_access_token`)用于飞书 API 工具
174
193
 
175
194
  ## 开发
176
195
 
177
196
  ```bash
178
197
  pnpm install
179
- pnpm test # 运行 vitest 测试套件
180
- pnpm test:watch # 交互式 watch 模式
198
+ pnpm test # vitest 测试套件
199
+ pnpm test:watch # 交互式 watch
181
200
  pnpm typecheck # tsc --noEmit
182
201
  pnpm lint # eslint
183
- pnpm build # tsup 构建 → dist/
202
+ pnpm build # tsup build → dist/
184
203
  ```
185
204
 
186
- ## 对接真实 Feishu 应用做冒烟测试
205
+ ## 真实飞书应用冒烟测试
206
+
207
+ 完整流程见 [`examples/README.md`](./examples/README.md)。TL;DR 跟[快速开始](#快速开始)一样:装依赖、填 `.env`、跑 `eve dev`。给 bot 发 `ping`,应该收到 `pong` 回复。
208
+
209
+ ## 生产部署
210
+
211
+ 生产环境切到 HTTP 回调模式:
212
+
213
+ ```ts
214
+ // agent/channels/lark.ts
215
+ export default createLarkChannel({
216
+ // ... 凭据 ...
217
+ mode: "webhook", // 关闭 WSClient 副作用
218
+ });
219
+ ```
187
220
 
188
- 参见 [`examples/README.md`](./examples/README.md),其中介绍了一种双进程的搭建方式,使用 Feishu 的长连接传输(无需公网 webhook URL)。简单来说:
221
+ 在飞书后台,把**事件订阅**从「长连接」切回**HTTP 回调**,URL 设为你部署的 agent 的 `/lark/webhook`。然后:
189
222
 
190
223
  ```bash
191
- pnpm build # 构建 eve-lark,让 agent 能 import
192
- cp examples/agent/.env.example examples/agent/.env
193
- $EDITOR examples/agent/.env # 填入凭据
194
- cd examples/agent && pnpm install
195
- # 终端 A:
196
- cd examples/agent && pnpm dev # eve dev server
197
- # 终端 B(在 repo 根目录):
198
- pnpm tsx examples/ws-forwarder.ts # Feishu WS → localhost HTTP
224
+ eve build
225
+ eve deploy # 或:在有公网 URL 的服务器上跑 eve start
199
226
  ```
200
227
 
201
- 在 Feishu 中向 bot 发送 `ping`,应该能收到 `pong` 的流式卡片回复。
228
+ 其他逻辑(签名、AES、去重、流式)不变。
202
229
 
203
- 测试布局:
230
+ 测试目录:
204
231
 
205
232
  ```
206
233
  test/
207
- ├── crypto.spec.ts # 签名 & AES 向量(包含一个 round-trip 辅助函数)
208
- ├── dedup.spec.ts # TTL、FIFO 淘汰、惰性清理
234
+ ├── crypto.spec.ts # 签名 & AES 测试向量(含 round-trip helper)
235
+ ├── dedup.spec.ts # TTL、FIFO 淘汰、惰性 sweep
209
236
  ├── options.spec.ts # env 回退、默认值、校验
210
- ├── parse.spec.ts # text/image/file/post/mention fixture
211
- ├── lark-client.spec.ts # token 互斥锁、重试策略(429/5xx/401)、nock 等价的 mock
237
+ ├── parse.spec.ts # text/image/file/post/mention fixtures
238
+ ├── lark-client.spec.ts # token mutex、retry policy(429/5xx/401)、mock fetch
212
239
  ├── streaming-controller.spec.ts # FSM 状态转换、节流、降级
213
- ├── card.spec.ts # 卡片构建器
214
- ├── channel.spec.ts # 端到端 webhook:校验、解密、去重、会话启动、流式串联
240
+ ├── card.spec.ts # 卡片构造器
241
+ ├── feishu-emoji.spec.ts # 飞书 emoji 白名单
242
+ ├── launcher-detection.spec.ts # eve start launcher 进程识别
243
+ ├── long-connection.spec.ts # WSClient 单例守卫、转发签名/AES
244
+ ├── channel.spec.ts # 端到端 webhook:校验、解密、去重、session 启动、ack reaction
245
+ ├── ask-card.spec.ts # ask_question 卡片构造器
246
+ ├── ask-flow.spec.ts # ask_question 端到端:渲染、点击回调、freeform 拦截
215
247
  └── helpers/
216
- ├── encrypt.ts # 仅测试用的 AES 加密镜像
217
- └── mock-fetch.ts # 替代 nock 的微型 mock fetch(兼容原生 fetch)
248
+ ├── encrypt.ts # 仅测试用的 AES cipher 镜像
249
+ └── mock-fetch.ts # 替代 nock 的迷你 mock fetch
218
250
  ```
219
251
 
220
- ## 协议
252
+ ## License
221
253
 
222
254
  MIT —— 见 [LICENSE](./LICENSE)。
package/dist/index.js CHANGED
@@ -1327,17 +1327,39 @@ function buildUserContent(text, files, options, messageId) {
1327
1327
  return parts;
1328
1328
  }
1329
1329
  __name(buildUserContent, "buildUserContent");
1330
- function errMsgFrom(data, fallback) {
1331
- if (typeof data !== "object" || data === null) return fallback;
1332
- const err = data.error;
1333
- if (typeof err === "string") return err;
1334
- if (typeof err === "object" && err !== null) {
1335
- const msg = err.message;
1336
- if (typeof msg === "string") return msg;
1337
- }
1338
- return fallback;
1330
+ function formatErrorHint(data) {
1331
+ if (typeof data !== "object" || data === null) return "";
1332
+ const d = data;
1333
+ const detailsName = typeof d.details === "object" && d.details !== null ? d.details.name : void 0;
1334
+ const name = typeof detailsName === "string" && detailsName.length > 0 ? detailsName : void 0;
1335
+ const message = typeof d.message === "string" ? d.message.trim() : "";
1336
+ if (name && message.length > 0) return ` (${name}: ${truncateForDisplay(message)})`;
1337
+ if (name) return ` (${name})`;
1338
+ if (message.length > 0) return ` (${truncateForDisplay(message)})`;
1339
+ return "";
1339
1340
  }
1340
- __name(errMsgFrom, "errMsgFrom");
1341
+ __name(formatErrorHint, "formatErrorHint");
1342
+ function extractErrorId(details) {
1343
+ if (typeof details === "object" && details !== null) {
1344
+ const id = details.errorId;
1345
+ return typeof id === "string" && id.length > 0 ? id : void 0;
1346
+ }
1347
+ return void 0;
1348
+ }
1349
+ __name(extractErrorId, "extractErrorId");
1350
+ function truncateForDisplay(s, max = 160) {
1351
+ return s.length <= max ? s : `${s.slice(0, max - 1).trimEnd()}\u2026`;
1352
+ }
1353
+ __name(truncateForDisplay, "truncateForDisplay");
1354
+ function formatFailureMessage(data, fallback, opts = { sentence: "turn" }) {
1355
+ const hint = formatErrorHint(data);
1356
+ const errorId = extractErrorId(data?.details);
1357
+ const lead = opts.sentence === "session" ? "This session couldn't recover from an error" : "I hit an error while handling your request";
1358
+ const idSuffix = errorId ? ` (Error id: ${errorId})` : "";
1359
+ if (!hint && !errorId) return `\u26A0 ${fallback}`;
1360
+ return `\u26A0 ${lead}${hint}.${idSuffix}`;
1361
+ }
1362
+ __name(formatFailureMessage, "formatFailureMessage");
1341
1363
  function createLarkChannel(optionsInput) {
1342
1364
  const options = resolveOptions(optionsInput);
1343
1365
  const client = new LarkClient(options);
@@ -1815,15 +1837,15 @@ function createLarkChannel(optionsInput) {
1815
1837
  console.warn(`[eve-lark] turn.failed: no session info (sessionId=${sessionId})`);
1816
1838
  return;
1817
1839
  }
1818
- const errMsg = errMsgFrom(data, "turn failed");
1840
+ const userText = formatFailureMessage(data, "turn failed", { sentence: "turn" });
1841
+ const errorId = extractErrorId(data?.details);
1819
1842
  console.warn(
1820
- `[eve-lark] turn.failed sessionId=${sessionId} chatId=${info.chatId} err="${errMsg.slice(0, 200)}"`
1843
+ `[eve-lark] turn.failed sessionId=${sessionId} chatId=${info.chatId} err="${userText.slice(0, 200)}"` + (errorId ? ` errorId=${errorId}` : "")
1821
1844
  );
1822
- const userText = `\u26A0 ${errMsg}`;
1823
1845
  const ctrl = controllers.get(sessionId);
1824
1846
  if (ctrl) {
1825
1847
  try {
1826
- await ctrl.abort(errMsg);
1848
+ await ctrl.abort(userText);
1827
1849
  console.log(`[eve-lark] error shown via streaming abort (sessionId=${sessionId})`);
1828
1850
  } catch (e) {
1829
1851
  console.warn(
@@ -1845,8 +1867,11 @@ function createLarkChannel(optionsInput) {
1845
1867
  dropController(sessionId);
1846
1868
  },
1847
1869
  async "session.failed"(data) {
1848
- const errMsg = errMsgFrom(data, "session failed");
1849
- console.error("[eve-lark] session.failed:", errMsg);
1870
+ const userText = formatFailureMessage(data, "session failed", { sentence: "session" });
1871
+ const errorId = extractErrorId(data?.details);
1872
+ console.error(
1873
+ `[eve-lark] session.failed: ${userText}` + (errorId ? ` (errorId=${errorId})` : "")
1874
+ );
1850
1875
  }
1851
1876
  };
1852
1877
  const channel = defineChannel({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/channel.ts","../src/errors.ts","../src/lark-client.ts","../src/dedup.ts","../src/crypto.ts","../src/parse.ts","../src/card.ts","../src/streaming-controller.ts","../src/options.ts","../src/long-connection.ts","../src/feishu-emoji.ts"],"sourcesContent":["import {\n defineChannel,\n POST,\n type Channel,\n type RouteHandlerArgs,\n} from \"eve/channels\";\n\nimport { LarkClient } from \"./lark-client.js\";\nimport { DedupMap } from \"./dedup.js\";\nimport { decryptPayload, verifySignature } from \"./crypto.js\";\nimport { parseInbound } from \"./parse.js\";\nimport { StreamingCardController } from \"./streaming-controller.js\";\nimport {\n ASK_BUTTON_VALUE_MARKER,\n buildAskAnsweredCard,\n buildAskCard,\n buildTextCard,\n} from \"./card.js\";\nimport { resolveOptions } from \"./options.js\";\nimport { isEveStartLauncher, startLongConnection } from \"./long-connection.js\";\nimport { isValidFeishuEmojiType } from \"./feishu-emoji.js\";\nimport type {\n LarkCardActionTriggerEvent,\n LarkChannelOptions,\n LarkContinuationToken,\n LarkEncryptedBody,\n LarkEventBody,\n LarkInboundEvent,\n LarkInboundFile,\n LarkInputRequest,\n LarkInputResponse,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\n/** Hard cap on inbound webhook body size. Feishu payloads are <10 KB; this\n * is purely defense against a malicious or buggy peer OOMing the process. */\nconst MAX_BODY_BYTES = 1_000_000;\n\n/** Drop a session's controller if it's been inactive this long. Bounds the\n * closure-scoped `controllers`/`sessionMeta` Maps against crashes that\n * prevent `message.completed`/`turn.failed` from firing. */\nconst STALE_SESSION_MS = 30 * 60 * 1000;\n\n/** How often to sweep stale controllers. */\nconst SWEEP_INTERVAL_MS = 5 * 60 * 1000;\n\n/** Reply text used when the model returns null/empty — guarantees the user\n * always sees *something* so they're not left looking at a typing emoji. */\nconst EMPTY_REPLY_TEXT = \"(model returned no content)\";\n\n/**\n * Continuation token format: `${chatId}:${rootMessageId ?? \"_\"}`.\n * The framework prepends the channel file stem before handing the token to\n * the runtime; consumers should call this helper rather than concatenate.\n */\nexport function larkContinuationToken(\n chatId: string,\n rootMessageId: string | null,\n): LarkContinuationToken {\n return `${chatId}:${rootMessageId ?? \"_\"}` as LarkContinuationToken;\n}\n\ninterface LarkSessionMeta {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n /** The user message we ack-reacted to; needed to remove the reaction\n * after delivery. Same value as `ctx.session.auth.initiator.attributes.messageId`,\n * mirrored here so terminal handlers don't have to re-extract it. */\n messageId?: string | undefined;\n /** Reaction id returned by `addReaction`. Present once the ack-reaction\n * POST resolves, which may be after the first terminal event fires. */\n ackReactionId?: string | undefined;\n /** When the controller was last touched. Used by the stale-sweep. */\n touchedAt: number;\n}\n\ninterface ResolvedSessionInfo {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n messageId?: string | undefined;\n}\n\n/**\n * Extract chat + message metadata stashed on `auth.initiator.attributes` at\n * session start. This is the canonical place to read it: closure state\n * doesn't reliably cross eve's process/worker boundary, but auth attributes\n * are persisted with the session.\n */\nfunction sessionInfoFromCtx(ctx: { session?: { auth?: { initiator?: { attributes?: unknown } | null } | null } | null }): ResolvedSessionInfo | null {\n const attrs = (ctx.session?.auth?.initiator?.attributes ?? {}) as {\n chatId?: unknown;\n rootMessageId?: unknown;\n parentId?: unknown;\n messageId?: unknown;\n };\n if (typeof attrs.chatId !== \"string\" || !attrs.chatId) return null;\n return {\n chatId: attrs.chatId,\n rootId: typeof attrs.rootMessageId === \"string\" ? attrs.rootMessageId : undefined,\n parentId: typeof attrs.parentId === \"string\" ? attrs.parentId : undefined,\n messageId: typeof attrs.messageId === \"string\" ? attrs.messageId : undefined,\n };\n}\n\nfunction ackOk(): Response {\n return Response.json({ code: 0 });\n}\n\n/**\n * Resolve the configured `ackReaction` to a single valid emoji type for this\n * event, or `false` if reactions are disabled (or the configured value is\n * invalid). Picks randomly when given an array.\n *\n * Validates against {@link VALID_FEISHU_EMOJI_TYPES} because Feishu rejects\n * unknown emoji types with HTTP 400 code=231001. The validation is\n * case-sensitive — `TYPING` is invalid but `Typing` is. Without this check a\n * typo in the default or a user-supplied value fails silently on every\n * inbound message.\n */\nfunction pickAckEmoji(reaction: string | readonly string[] | false): string | false {\n if (reaction === false) return false;\n if (typeof reaction === \"string\") {\n if (!isValidFeishuEmojiType(reaction)) {\n console.warn(\n `[eve-lark] ackReaction \"${reaction}\" is not a valid Feishu emoji type ` +\n `(case-sensitive; e.g. \"Typing\" not \"TYPING\"). Skipping ack reaction. ` +\n `See VALID_FEISHU_EMOJI_TYPES for the full list.`,\n );\n return false;\n }\n return reaction;\n }\n if (Array.isArray(reaction)) {\n const valid = reaction.filter(isValidFeishuEmojiType);\n if (valid.length === 0) {\n const sample = reaction.slice(0, 3).join(\", \");\n console.warn(\n `[eve-lark] ackReaction array contains no valid Feishu emoji types ` +\n `(got [${sample}${reaction.length > 3 ? \", …\" : \"\"}]). Skipping ack reaction.`,\n );\n return false;\n }\n if (valid.length < reaction.length) {\n const dropped = reaction.filter((e) => !isValidFeishuEmojiType(e));\n console.warn(\n `[eve-lark] ackReaction array dropped ${dropped.length} invalid emoji type(s): ` +\n `${dropped.slice(0, 3).join(\", \")}`,\n );\n }\n const idx = Math.floor(Math.random() * valid.length);\n return valid[idx] ?? false;\n }\n return false;\n}\n\nfunction resourceUrl(\n options: ResolvedLarkOptions,\n file: LarkInboundFile,\n messageId: string,\n): string {\n const type = file.kind === \"image\" ? \"image\" : \"file\";\n return `${options.baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/resources/${encodeURIComponent(file.fileKey)}?type=${type}`;\n}\n\n/**\n * Build the eve UserContent payload from a parsed inbound event. Text comes\n * first; each inbound image/file becomes a `file` part carrying a URL pointing\n * at the Lark resource endpoint. The channel's `fetchFile` hook will stage\n * those URLs to bytes when the model runs.\n */\nfunction buildUserContent(\n text: string,\n files: LarkInboundFile[],\n options: ResolvedLarkOptions,\n messageId: string,\n): unknown[] {\n const parts: unknown[] = [];\n if (text.length > 0) parts.push({ type: \"text\", text });\n for (const f of files) {\n parts.push({\n type: \"file\",\n data: new URL(resourceUrl(options, f, messageId)),\n mediaType: f.mediaType,\n });\n }\n return parts;\n}\n\nfunction errMsgFrom(data: unknown, fallback: string): string {\n if (typeof data !== \"object\" || data === null) return fallback;\n const err = (data as { error?: unknown }).error;\n if (typeof err === \"string\") return err;\n if (typeof err === \"object\" && err !== null) {\n const msg = (err as { message?: unknown }).message;\n if (typeof msg === \"string\") return msg;\n }\n return fallback;\n}\n\n/**\n * Create a Lark/Feishu channel for the eve agent framework.\n *\n * The channel mounts a single POST webhook that verifies the request,\n * decrypts the body when an encrypt key is configured, deduplicates events\n * by id, parses the inbound message, and starts or resumes an eve session.\n *\n * Streaming happens via eve's native channel events: `message.appended`\n * drives live card patches, `message.completed` finalizes the card, and\n * `turn.failed` aborts it. In `replyMode: \"static\"` the controller is\n * skipped and `message.completed` delivers a single card.\n *\n * **Delivery guarantee**: every terminal event (`message.completed` or\n * `turn.failed`) delivers *something* to the user. If the streaming card\n * path fails, we fall back to a fresh card; if that fails, plain text; if\n * even that fails, the error is logged. The user is never left looking at\n * a typing-emoji reaction with no reply.\n */\nexport function createLarkChannel(\n optionsInput: LarkChannelOptions,\n): Channel<undefined, Record<string, unknown>, Record<string, unknown>> {\n const options = resolveOptions(optionsInput);\n const client = new LarkClient(options);\n const dedup = new DedupMap(options.dedupTtlMs, options.dedupMaxEntries);\n\n // Long-connection side effect: when mode is \"long-connection\" (the\n // default), start a Feishu WSClient in the background. Each inbound event\n // is re-signed and POSTed to this channel's webhook on localhost, where\n // the standard handler runs with full access to send() etc.\n //\n // Skip when running inside the `eve start` launcher process: eve forks\n // a nitro server child to actually serve HTTP, and both processes load\n // the channel module. Without this guard each process spawns its own\n // WSClient and Feishu delivers every event twice.\n if (options.mode === \"long-connection\" && !isEveStartLauncher()) {\n const eveWebhookUrl = `http://127.0.0.1:${options.port}${options.webhookPath}`;\n void startLongConnection({ resolved: options, eveWebhookUrl }).catch((e) => {\n console.error(\"[eve-lark] long-connection startup failed:\", e);\n });\n } else if (options.mode === \"long-connection\" && isEveStartLauncher()) {\n console.log(\"[eve-lark] skipping WSClient start in eve-start launcher process; the spawned server will start it\");\n }\n\n // Channel-scoped (closure) state. Each session has its own controller +\n // chat metadata, keyed by session.id. Bounded by the stale-sweep below.\n const controllers = new Map<string, StreamingCardController>();\n const sessionMeta = new Map<string, LarkSessionMeta>();\n\n // Pending ask_question input requests, keyed by eve's requestId. Used to\n // resolve card.action.trigger callbacks back to the originating session.\n // Also keyed by chat-continuation-token for freeform interception.\n interface PendingInput {\n requestId: string;\n sessionId: string;\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n /** The card message id we sent (so we can patch it after the user answers). */\n cardMessageId?: string | undefined;\n /** Full request, so the post-click renderer can show selected label. */\n request: LarkInputRequest;\n /** When the pending input was registered (for stale-sweep). */\n createdAt: number;\n /** Whether to intercept the next inbound chat message as the response. */\n awaitingFreeform: boolean;\n touchedAt: number;\n }\n const pendingInputsByRequestId = new Map<string, PendingInput>();\n const pendingInputsByChatToken = new Map<string, PendingInput>();\n\n function getController(sessionId: string, meta: ResolvedSessionInfo): StreamingCardController {\n let ctrl = controllers.get(sessionId);\n if (!ctrl) {\n ctrl = new StreamingCardController(client, {\n chatId: meta.chatId,\n rootId: meta.rootId,\n parentId: meta.parentId,\n patchIntervalMs: options.streamPatchIntervalMs,\n createThresholdMs: options.streamCreateThresholdMs,\n });\n controllers.set(sessionId, ctrl);\n }\n if (sessionMeta.has(sessionId)) {\n sessionMeta.get(sessionId)!.touchedAt = Date.now();\n } else {\n sessionMeta.set(sessionId, {\n chatId: meta.chatId,\n rootId: meta.rootId,\n parentId: meta.parentId,\n messageId: meta.messageId,\n touchedAt: Date.now(),\n });\n }\n return ctrl;\n }\n\n function dropController(sessionId: string): void {\n controllers.delete(sessionId);\n sessionMeta.delete(sessionId);\n }\n\n /** Best-effort ack-reaction cleanup. Called from terminal handlers. */\n async function cleanupAckReaction(sessionId: string): Promise<void> {\n const meta = sessionMeta.get(sessionId);\n if (!meta?.ackReactionId || !meta.messageId) return;\n try {\n await client.removeReaction({\n messageId: meta.messageId,\n reactionId: meta.ackReactionId,\n });\n } catch (e) {\n console.warn(\n \"[eve-lark] ack reaction cleanup failed:\",\n e instanceof Error ? e.message : e,\n );\n }\n }\n\n /**\n * Cascade-deliver a reply to the user. Tries (in order):\n *\n * post mode (default — native chat size + markdown):\n * 1. sendPost (msg_type: \"post\", renders at native size)\n * 2. sendText (post POST rejected)\n *\n * streaming mode (live card patches during the turn):\n * 1. streaming finalize (patches existing card OR creates one)\n * 2. sendCard (finalize failed)\n * 3. sendText (card POST rejected)\n *\n * static mode (one-shot card):\n * 1. sendCard (single card with the full text)\n * 2. sendText (card POST rejected)\n *\n * Each failure logs; we never throw out of here.\n */\n async function deliverReply(sessionId: string, info: ResolvedSessionInfo, text: string): Promise<void> {\n if (options.replyMode === \"post\") {\n try {\n await client.sendPost({\n chatId: info.chatId,\n content: text,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendPost (sessionId=${sessionId})`);\n return;\n } catch (postErr) {\n console.warn(\n `[eve-lark] sendPost failed; falling back to plain text (sessionId=${sessionId}):`,\n postErr instanceof Error ? postErr.message : postErr,\n );\n // Fall through to sendText.\n }\n // post-specific fallback (skip the card cascade below).\n try {\n await client.sendText({\n chatId: info.chatId,\n content: text,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendText fallback (sessionId=${sessionId})`);\n } catch (textErr) {\n console.error(\n `[eve-lark] sendText fallback ALSO failed; the user will not see this reply (sessionId=${sessionId}):`,\n textErr instanceof Error ? textErr.message : textErr,\n );\n }\n return;\n }\n\n if (options.replyMode === \"streaming\") {\n const ctrl = controllers.get(sessionId) ?? getController(sessionId, info);\n try {\n await ctrl.finalize(text);\n console.log(`[eve-lark] delivered via streaming finalize (sessionId=${sessionId})`);\n return;\n } catch (e) {\n console.warn(\n `[eve-lark] streaming finalize failed; falling back to fresh card (sessionId=${sessionId}):`,\n e instanceof Error ? e.message : e,\n );\n }\n }\n\n try {\n await client.sendCard({\n chatId: info.chatId,\n card: buildTextCard(text),\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendCard (sessionId=${sessionId})`);\n return;\n } catch (cardErr) {\n console.warn(\n `[eve-lark] sendCard failed; falling back to plain text (sessionId=${sessionId}):`,\n cardErr instanceof Error ? cardErr.message : cardErr,\n );\n }\n\n try {\n await client.sendText({\n chatId: info.chatId,\n content: text,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendText fallback (sessionId=${sessionId})`);\n } catch (textErr) {\n console.error(\n `[eve-lark] sendText fallback ALSO failed; the user will not see this reply (sessionId=${sessionId}):`,\n textErr instanceof Error ? textErr.message : textErr,\n );\n }\n }\n\n // Lazy sweep: drop controllers whose session hasn't been touched in\n // STALE_SESSION_MS. Guards against the case where eve crashes mid-turn.\n // Also drops stale pending input requests.\n let lastSweepAt = 0;\n function maybeSweep(): void {\n const now = Date.now();\n if (now - lastSweepAt < SWEEP_INTERVAL_MS) return;\n lastSweepAt = now;\n const cutoff = now - STALE_SESSION_MS;\n for (const [id, meta] of sessionMeta) {\n if (meta.touchedAt < cutoff) {\n controllers.delete(id);\n sessionMeta.delete(id);\n }\n }\n for (const [reqId, p] of pendingInputsByRequestId) {\n if (p.touchedAt < cutoff) {\n pendingInputsByRequestId.delete(reqId);\n const tokenKey = chatTokenKey(p.chatId, p.rootId, p.parentId);\n if (pendingInputsByChatToken.get(tokenKey)?.requestId === reqId) {\n pendingInputsByChatToken.delete(tokenKey);\n }\n }\n }\n }\n\n /** Compose the chat-scoped key used for freeform interception. */\n function chatTokenKey(chatId: string, rootId?: string, parentId?: string): string {\n return `${chatId}:${parentId ?? rootId ?? \"_\"}`;\n }\n\n function dropPendingInput(p: PendingInput): void {\n pendingInputsByRequestId.delete(p.requestId);\n const tokenKey = chatTokenKey(p.chatId, p.rootId, p.parentId);\n if (pendingInputsByChatToken.get(tokenKey)?.requestId === p.requestId) {\n pendingInputsByChatToken.delete(tokenKey);\n }\n }\n\n const webhookHandler = async (\n req: Request,\n helpers: RouteHandlerArgs[\"send\"] extends never ? never : RouteHandlerArgs,\n ): Promise<Response> => {\n maybeSweep();\n\n // 0) Body size cap — refuse gigantic bodies before allocating.\n const contentLength = Number(req.headers.get(\"content-length\") ?? \"0\");\n if (Number.isFinite(contentLength) && contentLength > MAX_BODY_BYTES) {\n return new Response(\"request body too large\", { status: 413 });\n }\n const rawBody = Buffer.from(await req.arrayBuffer());\n if (rawBody.byteLength > MAX_BODY_BYTES) {\n return new Response(\"request body too large\", { status: 413 });\n }\n\n // 1) Skew check (only enforced when a real timestamp header is present)\n const tsHeader = req.headers.get(\"x-lark-request-timestamp\") ?? \"\";\n const ts = Number(tsHeader);\n if (\n tsHeader &&\n Number.isFinite(ts) &&\n ts > 0 &&\n Math.abs(Date.now() / 1000 - ts) > options.signatureSkewMs / 1000\n ) {\n return new Response(\"request timestamp out of skew window\", { status: 408 });\n }\n\n // 2) Signature verify + AES decrypt when encryptKey configured\n let workingBody: Buffer = rawBody;\n if (options.encryptKey) {\n const nonce = req.headers.get(\"x-lark-request-nonce\") ?? \"\";\n const sigHeader = req.headers.get(\"x-lark-signature\");\n if (!sigHeader) return new Response(\"missing signature\", { status: 401 });\n const ok = verifySignature({\n timestamp: tsHeader,\n nonce,\n encryptKey: options.encryptKey,\n rawBody,\n signatureHeader: sigHeader,\n });\n if (!ok) return new Response(\"bad signature\", { status: 401 });\n\n try {\n const envelope = JSON.parse(rawBody.toString(\"utf8\")) as LarkEncryptedBody;\n if (envelope.encrypt) {\n workingBody = decryptPayload(envelope.encrypt, options.encryptKey) as Buffer;\n }\n } catch {\n return new Response(\"decrypt failed\", { status: 400 });\n }\n }\n\n // 3) Parse body\n let body: LarkEventBody;\n try {\n body = JSON.parse(workingBody.toString(\"utf8\")) as LarkEventBody;\n } catch {\n return new Response(\"invalid json\", { status: 400 });\n }\n\n // 4) url_verification short-circuit\n if (body.type === \"url_verification\") {\n return Response.json({ challenge: body.challenge ?? \"\" });\n }\n\n // 5) Schema check\n if (body.schema !== \"2.0\") {\n return ackOk();\n }\n\n // 6) Verification-token check\n if (body.header?.token !== options.verificationToken) {\n return new Response(\"verification token mismatch\", { status: 401 });\n }\n\n // 7) Dedup. Card-action callbacks dedup on open_message_id (one click\n // per render — re-clicks after patch are no-ops because we replaced\n // the buttons with text).\n const evtMsg = body.event as { message?: { message_id?: string }; open_message_id?: string } | undefined;\n const dedupKey = body.header?.event_id ?? evtMsg?.message?.message_id ?? evtMsg?.open_message_id;\n if (dedupKey) {\n if (dedup.has(dedupKey)) return ackOk();\n dedup.set(dedupKey);\n }\n\n // 8) Event-type dispatch. Two event types we own:\n // - \"im.message.receive_v1\" — normal inbound message (existing flow).\n // - \"card.action.trigger\" — user clicked a button on an ask_card.\n // Anything else is ack-and-skip.\n const eventType = body.header?.event_type;\n if (eventType === \"card.action.trigger\") {\n return handleCardAction(body.event as LarkCardActionTriggerEvent, helpers);\n }\n if (eventType !== \"im.message.receive_v1\") {\n return ackOk();\n }\n if (!body.event) return ackOk();\n\n // 9) Parse — body.event is now narrowed to the message-event branch.\n const parsed = parseInbound(body.event as LarkInboundEvent, options.botOpenId);\n\n // 10) Self-message suppression\n if (parsed.senderType === \"app\") {\n return ackOk();\n }\n\n // 11) Skip unsupported message types\n if (parsed.text === \"\" && parsed.files.length === 0) {\n return ackOk();\n }\n\n // 11.5) Freeform-input interception. If this chat has a pending\n // ask_question awaiting a freeform text reply, treat this inbound\n // message as the answer instead of starting a new turn. eve resumes\n // the parked session with the user's text as InputResponse.text.\n const tokenKey = chatTokenKey(parsed.chatId, parsed.rootId ?? undefined, parsed.parentId ?? undefined);\n const pending = pendingInputsByChatToken.get(tokenKey);\n if (pending && pending.awaitingFreeform && parsed.text.length > 0) {\n const resp: LarkInputResponse = { requestId: pending.requestId, text: parsed.text };\n const resumeAuth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: parsed.senderOpenId,\n attributes: {\n chatId: parsed.chatId,\n rootMessageId: parsed.rootId,\n messageId: parsed.messageId,\n chatType: parsed.chatType,\n },\n };\n const resumeToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);\n try {\n await helpers.send(\n { inputResponses: [resp] } as never,\n { auth: resumeAuth as never, continuationToken: resumeToken },\n );\n // Update the card (if any) to show the typed answer.\n if (pending.cardMessageId) {\n try {\n await client.patchCard({\n messageId: pending.cardMessageId,\n card: buildAskAnsweredCard(pending.request, { kind: \"freeform\", text: parsed.text }),\n });\n } catch (e) {\n console.warn(\"[eve-lark] patchCard after freeform answer failed:\", e instanceof Error ? e.message : e);\n }\n }\n } catch (e) {\n console.error(\"[eve-lark] freeform input-response send failed:\", e instanceof Error ? e.message : e);\n } finally {\n dropPendingInput(pending);\n }\n return ackOk();\n }\n\n // 12) Build session inputs\n const userContent = buildUserContent(parsed.text, parsed.files, options, parsed.messageId);\n const continuationToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);\n const auth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: parsed.senderOpenId,\n attributes: {\n chatId: parsed.chatId,\n rootMessageId: parsed.rootId,\n messageId: parsed.messageId,\n chatType: parsed.chatType,\n },\n };\n\n // 13) Start/resume session.\n const session = await helpers.send(userContent as never, {\n auth: auth as never,\n continuationToken,\n });\n\n // 14) Remember chat metadata keyed by session.id so terminal handlers\n // can look up where to deliver replies and which reaction to clean up.\n sessionMeta.set(session.id, {\n chatId: parsed.chatId,\n rootId: parsed.rootId ?? undefined,\n parentId: parsed.parentId ?? undefined,\n messageId: parsed.messageId,\n touchedAt: Date.now(),\n });\n\n // 15) Ack reaction — fire-and-forget. Stash the resulting reaction id\n // so terminal handlers can remove it once the reply has been delivered.\n const emoji = pickAckEmoji(options.ackReaction);\n if (emoji) {\n const sessionId = session.id;\n helpers.waitUntil(\n client\n .addReaction({ messageId: parsed.messageId, emojiType: emoji })\n .then(({ reactionId }) => {\n const m = sessionMeta.get(sessionId);\n if (m) m.ackReactionId = reactionId;\n })\n .catch((e) => {\n console.warn(\n \"[eve-lark] ack reaction failed:\",\n e instanceof Error ? e.message : e,\n );\n }),\n );\n }\n\n return ackOk();\n };\n\n /**\n * Handle a `card.action.trigger` callback from Feishu. The user clicked a\n * button on an ask_card we rendered earlier. Extract the requestId +\n * optionId from `action.value`, resume the parked eve session with an\n * InputResponse, and patch the card to show the selection.\n *\n * Buttons we created carry `{__eveLarkAsk, requestId, optionId}` in their\n * `value`. Buttons from any other source are ignored.\n */\n async function handleCardAction(\n evt: LarkCardActionTriggerEvent,\n helpers: RouteHandlerArgs,\n ): Promise<Response> {\n const value = evt.action?.value;\n if (!value || value[ASK_BUTTON_VALUE_MARKER] !== true) {\n // Not our button — ignore (could be from another integration).\n return ackOk();\n }\n const requestId = typeof value.requestId === \"string\" ? value.requestId : \"\";\n const optionId = typeof value.optionId === \"string\" ? value.optionId : \"\";\n if (!requestId) return ackOk();\n\n const pending = pendingInputsByRequestId.get(requestId);\n if (!pending) {\n console.warn(`[eve-lark] card action for unknown requestId=${requestId} (already answered or expired)`);\n return ackOk();\n }\n\n // Build the InputResponse and resume the parked session.\n const resp: LarkInputResponse = { requestId, optionId: optionId || undefined };\n const resumeToken = larkContinuationToken(pending.chatId, pending.parentId ?? pending.rootId ?? null);\n const resumeAuth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: evt.open_id,\n attributes: {\n chatId: pending.chatId,\n rootMessageId: pending.rootId,\n messageId: evt.open_message_id,\n chatType: pending.request.display === \"confirmation\" ? \"p2p\" : \"group\",\n },\n };\n\n try {\n await helpers.send(\n { inputResponses: [resp] } as never,\n { auth: resumeAuth as never, continuationToken: resumeToken },\n );\n console.log(`[eve-lark] ask answered via button click requestId=${requestId} optionId=${optionId}`);\n } catch (e) {\n console.error(\n `[eve-lark] ask input-response send failed (requestId=${requestId}):`,\n e instanceof Error ? e.message : e,\n );\n }\n\n // Patch the card to show the selection (disable buttons by replacing\n // the card body with prompt + \"✓ <label>\"). Best-effort.\n const selectedOpt = pending.request.options?.find((o) => o.id === optionId);\n if (pending.cardMessageId && selectedOpt) {\n try {\n await client.patchCard({\n messageId: pending.cardMessageId,\n card: buildAskAnsweredCard(pending.request, { kind: \"option\", label: selectedOpt.label }),\n });\n } catch (e) {\n console.warn(\"[eve-lark] patchCard after ask-answer failed:\", e instanceof Error ? e.message : e);\n }\n }\n\n dropPendingInput(pending);\n return ackOk();\n }\n\n // Channel event handlers — declared as a standalone const so tests can\n // invoke them directly (eve's defineChannel hides events on the returned\n // Channel object). createLarkChannel attaches them as `__testEvents` on\n // the returned channel for that purpose; production code never reads it.\n const channelEvents = {\n // Streaming delta — patch the card.\n \"message.appended\"(data: unknown, _channel: unknown, ctx: { session: { id: string } }) {\n if (options.replyMode !== \"streaming\") return;\n const sessionId = ctx.session.id;\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) return;\n const d = data as { messageDelta?: string };\n if (typeof d.messageDelta !== \"string\") return;\n const ctrl = getController(sessionId, info);\n ctrl.appendDelta(d.messageDelta);\n },\n\n // eve's ask_question (and similar HITL tools) fire this event with a\n // list of input requests. Each request becomes a Feishu card with\n // buttons (one per option) plus optional freeform hint.\n async \"input.requested\"(data: unknown, _channel: unknown, ctx: { session: { id: string } }) {\n const sessionId = ctx.session.id;\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) {\n console.warn(`[eve-lark] input.requested: no session info (sessionId=${sessionId})`);\n return;\n }\n const d = data as { requests?: readonly LarkInputRequest[] };\n const requests = d.requests ?? [];\n if (requests.length === 0) return;\n\n console.log(\n `[eve-lark] input.requested sessionId=${sessionId} chatId=${info.chatId} count=${requests.length}`,\n );\n\n for (const req of requests) {\n const card = buildAskCard(req);\n let cardMessageId: string | undefined;\n try {\n const res = await client.sendCard({\n chatId: info.chatId,\n card,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n cardMessageId = res.messageId;\n } catch (e) {\n console.error(\n `[eve-lark] ask card send failed (requestId=${req.requestId}):`,\n e instanceof Error ? e.message : e,\n );\n continue;\n }\n\n const pending: PendingInput = {\n requestId: req.requestId,\n sessionId,\n chatId: info.chatId,\n rootId: info.rootId,\n parentId: info.parentId,\n cardMessageId,\n request: req,\n createdAt: Date.now(),\n touchedAt: Date.now(),\n awaitingFreeform: req.allowFreeform === true,\n };\n pendingInputsByRequestId.set(req.requestId, pending);\n if (pending.awaitingFreeform) {\n const tokenKey = chatTokenKey(info.chatId, info.rootId, info.parentId);\n pendingInputsByChatToken.set(tokenKey, pending);\n }\n }\n },\n\n // Terminal — deliver the final reply, then clean up the ack reaction.\n async \"message.completed\"(data: unknown, _channel: unknown, ctx: { session: { id: string } }) {\n const sessionId = ctx.session.id;\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) {\n console.warn(`[eve-lark] message.completed: no session info, cannot deliver (sessionId=${sessionId})`);\n return;\n }\n const d = data as { message?: string | null };\n const rawText = typeof d.message === \"string\" ? d.message : \"\";\n console.log(\n `[eve-lark] message.completed sessionId=${sessionId} chatId=${info.chatId} msgLen=${rawText.length}`,\n );\n const text = rawText.length > 0 ? rawText : EMPTY_REPLY_TEXT;\n\n try {\n await deliverReply(sessionId, info, text);\n } finally {\n await cleanupAckReaction(sessionId);\n dropController(sessionId);\n }\n },\n\n async \"turn.failed\"(data: unknown, _channel: unknown, ctx: { session?: { id: string } } | null) {\n const sessionId = ctx?.session?.id;\n if (!sessionId) {\n console.warn(\"[eve-lark] turn.failed: no sessionId on ctx\");\n return;\n }\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) {\n console.warn(`[eve-lark] turn.failed: no session info (sessionId=${sessionId})`);\n return;\n }\n const errMsg = errMsgFrom(data, \"turn failed\");\n console.warn(\n `[eve-lark] turn.failed sessionId=${sessionId} chatId=${info.chatId} err=\"${errMsg.slice(0, 200)}\"`,\n );\n const userText = `⚠ ${errMsg}`;\n\n const ctrl = controllers.get(sessionId);\n if (ctrl) {\n try {\n await ctrl.abort(errMsg);\n console.log(`[eve-lark] error shown via streaming abort (sessionId=${sessionId})`);\n } catch (e) {\n console.warn(\n `[eve-lark] turn.failed: streaming abort failed, will deliver fresh error (sessionId=${sessionId}):`,\n e instanceof Error ? e.message : e,\n );\n try {\n await deliverReply(sessionId, info, userText);\n } catch {\n // unreachable\n }\n }\n } else {\n try {\n await deliverReply(sessionId, info, userText);\n } catch {\n // unreachable\n }\n }\n\n await cleanupAckReaction(sessionId);\n dropController(sessionId);\n },\n\n async \"session.failed\"(data: unknown) {\n const errMsg = errMsgFrom(data, \"session failed\");\n console.error(\"[eve-lark] session.failed:\", errMsg);\n },\n };\n\n const channel = defineChannel({\n routes: [POST(options.webhookPath, webhookHandler as never)],\n\n fetchFile: async (url: string) => {\n if (!url.startsWith(options.baseUrl)) return null;\n const m = url.match(/\\/messages\\/([^/]+)\\/resources\\/([^?]+)\\?type=(image|file)/);\n if (!m || !m[1] || !m[2] || !m[3]) return null;\n return client.downloadResource({\n messageId: m[1],\n fileKey: m[2],\n type: m[3] as \"image\" | \"file\",\n });\n },\n\n events: channelEvents,\n });\n\n // Test seam: expose the events map so tests can drive handlers directly\n // (eve's defineChannel hides them on the returned Channel). Production\n // code MUST NOT read this — it's typing-loose and not part of the API.\n (channel as Channel<undefined, Record<string, unknown>, Record<string, unknown>> & {\n __testEvents?: typeof channelEvents;\n }).__testEvents = channelEvents;\n\n return channel;\n}\n","/**\n * Typed error hierarchy for eve-lark.\n *\n * All errors extend a common base so consumers can `instanceof LarkChannelError`\n * to catch anything thrown by the channel.\n */\n\nexport class LarkChannelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\nexport class LarkConfigError extends LarkChannelError {}\n\nexport class LarkSignatureError extends LarkChannelError {}\n\nexport class LarkDecryptError extends LarkChannelError {}\n\nexport interface LarkApiErrorBody {\n code?: number | undefined;\n msg?: string | undefined;\n}\n\nexport class LarkApiError extends LarkChannelError {\n readonly code: number | undefined;\n readonly body: LarkApiErrorBody | undefined;\n readonly status: number | undefined;\n\n constructor(\n message: string,\n opts?: {\n code?: number | undefined;\n body?: LarkApiErrorBody | undefined;\n status?: number | undefined;\n cause?: unknown;\n },\n ) {\n super(message, { cause: opts?.cause });\n this.code = opts?.code;\n this.body = opts?.body;\n this.status = opts?.status;\n }\n}\n","import { LarkApiError, type LarkApiErrorBody } from \"./errors.js\";\nimport type { LarkCard, ResolvedLarkOptions } from \"./types.js\";\n\ninterface TokenState {\n value: string;\n expiresAt: number;\n}\n\nconst TOKEN_INVALID_CODES = new Set<number>([99991663, 99991664, 99991661]);\n\ninterface RequestResult {\n status: number;\n body: unknown;\n retryAfter: number | null;\n}\n\nexport class LarkClient {\n private readonly options: ResolvedLarkOptions;\n private token: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(options: ResolvedLarkOptions) {\n this.options = options;\n }\n\n async getTenantAccessToken(): Promise<string> {\n if (\n this.token &&\n Date.now() + this.options.tokenRefreshBufferMs < this.token.expiresAt\n ) {\n return this.token.value;\n }\n if (this.refreshPromise) return this.refreshPromise;\n this.refreshPromise = this.#refresh();\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n async #refresh(): Promise<string> {\n const body = {\n app_id: this.options.appId,\n app_secret: this.options.appSecret,\n };\n const res = await this.options.fetch(\n `${this.options.baseUrl}/open-apis/auth/v3/tenant_access_token/internal`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n },\n );\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: token refresh failed (HTTP ${res.status})`,\n { status: res.status },\n );\n }\n const json = (await res.json()) as { code?: number; tenant_access_token?: string; expire?: number; msg?: string };\n if (json.code !== 0 || !json.tenant_access_token) {\n throw new LarkApiError(\n `eve-lark: token refresh returned code=${json.code ?? \"?\"} msg=${json.msg ?? \"?\"}`,\n { body: json, code: json.code },\n );\n }\n const expireSec = typeof json.expire === \"number\" ? json.expire : 7200;\n this.token = {\n value: json.tenant_access_token,\n expiresAt: Date.now() + expireSec * 1000,\n };\n return this.token.value;\n }\n\n async sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify({ text: args.content });\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"text\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify(args.card);\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"interactive\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async sendPost(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n // `msg_type: \"post\"` renders at native chat-message size with full\n // markdown support (bold, links, code, <font> color tags) via the\n // inner `{tag: \"md\"}` element. Cards render noticeably smaller because\n // Feishu treats them as \"structured content\"; post does not.\n //\n // The content schema is post > zh_cn > content > lines > inline nodes.\n // We put the whole reply in one md node — the md tag honors embedded\n // newlines, so multi-paragraph replies work as a single text string.\n const post = {\n zh_cn: {\n content: [[{ tag: \"md\", text: args.content }]],\n },\n };\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"post\",\n content: JSON.stringify(post),\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async #sendMessage(body: Record<string, unknown>): Promise<{ messageId: string }> {\n const payload = Object.fromEntries(\n Object.entries(body).filter(([, v]) => v !== undefined),\n );\n const json = await this.#request(\"POST\", \"/open-apis/im/v1/messages?receive_id_type=chat_id\", payload);\n const messageId = (json as { data?: { message_id?: string } }).data?.message_id;\n if (!messageId) {\n throw new LarkApiError(\"eve-lark: send missing message_id in response\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { messageId };\n }\n\n async patchCard(args: { messageId: string; card: LarkCard }): Promise<void> {\n await this.#request(\n \"PATCH\",\n `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}`,\n { content: JSON.stringify(args.card) },\n );\n }\n\n async downloadResource(args: {\n messageId: string;\n fileKey: string;\n type: \"image\" | \"file\";\n }): Promise<Buffer> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/resources/${encodeURIComponent(args.fileKey)}?type=${args.type}`;\n const token = await this.getTenantAccessToken();\n const res = await this.options.fetch(`${this.options.baseUrl}${path}`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: downloadResource HTTP ${res.status}`,\n { status: res.status },\n );\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n async addReaction(args: {\n messageId: string;\n emojiType: string;\n }): Promise<{ reactionId: string }> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions`;\n const json = (await this.#request(\"POST\", path, {\n reaction_type: { emoji_type: args.emojiType },\n })) as { data?: { reaction_id?: string } };\n const reactionId = json.data?.reaction_id;\n if (!reactionId) {\n throw new LarkApiError(\"eve-lark: addReaction missing reaction_id\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { reactionId };\n }\n\n async removeReaction(args: { messageId: string; reactionId: string }): Promise<void> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions/${encodeURIComponent(args.reactionId)}`;\n await this.#request(\"DELETE\", path, undefined);\n }\n\n /**\n * Central request wrapper with auth, retry, and Feishu error decoding.\n *\n * Retry policy:\n * - 429 (rate limit): always retry with `Retry-After` backoff. Safe —\n * server rejected the request before processing.\n * - 5xx: retry ONLY for idempotent methods (GET / PATCH / DELETE). POST\n * is NOT retried on 5xx because Feishu's POST /messages and POST\n * /reactions are non-idempotent — the server may have created the\n * resource before returning the error, and retrying would silently\n * double-send.\n * - 401 / token-invalid code: refresh and retry once.\n * - Other 4xx: throw LarkApiError with the Feishu code/msg.\n */\n async #request(method: string, path: string, body: unknown): Promise<unknown> {\n const url = `${this.options.baseUrl}${path}`;\n let token = await this.getTenantAccessToken();\n let tokenRefreshed = false;\n const methodNorm = method.toUpperCase();\n const retryableMethod = methodNorm !== \"POST\";\n\n for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {\n const res = await this.options.fetch(url, {\n method,\n headers: {\n authorization: `Bearer ${token}`,\n \"content-type\": \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n\n const result = await this.#consumeResponse(res);\n const status = result.status;\n\n if (status >= 200 && status < 300) {\n const jsonBody = result.body as { code?: number; msg?: string };\n if (jsonBody && typeof jsonBody.code === \"number\" && jsonBody.code !== 0) {\n if (TOKEN_INVALID_CODES.has(jsonBody.code) && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed code=${jsonBody.code} msg=${jsonBody.msg ?? \"?\"}`,\n { code: jsonBody.code, body: jsonBody as LarkApiErrorBody, status },\n );\n }\n return result.body;\n }\n\n if (status === 401 && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n\n const isRateLimited = status === 429;\n const isServerErr = status >= 500 && status < 600;\n const retryable = isRateLimited || (isServerErr && retryableMethod);\n if (retryable && attempt < this.options.maxRetries) {\n const delayMs = this.#computeBackoff(status, result.retryAfter, attempt);\n await sleep(delayMs);\n continue;\n }\n\n const bodyObj = result.body as LarkApiErrorBody | undefined;\n const code = bodyObj?.code;\n const msg = bodyObj?.msg;\n const detail = msg ? ` code=${code ?? \"?\"} msg=${msg}` : \"\";\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed HTTP ${status}${detail}`,\n { status, body: bodyObj, code },\n );\n }\n throw new LarkApiError(`eve-lark: ${method} ${path} exhausted retries`);\n }\n\n async #consumeResponse(res: Response): Promise<RequestResult> {\n const retryAfterRaw = res.headers.get(\"retry-after\");\n const retryAfter = retryAfterRaw ? parseRetryAfter(retryAfterRaw) : null;\n const text = await res.text();\n if (!text) {\n return { status: res.status, body: undefined, retryAfter };\n }\n try {\n return { status: res.status, body: JSON.parse(text), retryAfter };\n } catch {\n return { status: res.status, body: { raw: text }, retryAfter };\n }\n }\n\n #computeBackoff(status: number, retryAfter: number | null, attempt: number): number {\n if (status === 429 && retryAfter !== null) {\n return Math.min(retryAfter * 1000, 10_000);\n }\n const base = 300 * Math.pow(2, attempt);\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\n return Math.max(0, Math.round(base + jitter));\n }\n}\n\nfunction parseRetryAfter(raw: string): number | null {\n const sec = Number(raw);\n if (Number.isFinite(sec) && sec >= 0) return sec;\n const date = Date.parse(raw);\n if (Number.isFinite(date)) return Math.max(0, (date - Date.now()) / 1000);\n return null;\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * In-process deduplication for Feishu webhook events.\n *\n * Feishu retries delivery on non-2xx responses and during brief outage windows,\n * so consumers must idempotently ack events they've already seen. We key by\n * `header.event_id` (or `message.message_id` as a fallback) and remember each\n * key for the TTL window.\n *\n * Backed by an insertion-ordered Map so FIFO eviction is O(1) at the front.\n * Lazy sweep on every insert prevents unbounded growth of expired entries;\n * no `setInterval` so this is safe in serverless.\n */\nexport class DedupMap {\n private readonly entries = new Map<string, number>();\n private readonly ttlMs: number;\n private readonly maxEntries: number;\n private insertsSinceSweep = 0;\n\n constructor(ttlMs: number, maxEntries: number) {\n this.ttlMs = ttlMs;\n this.maxEntries = maxEntries;\n }\n\n has(key: string): boolean {\n const at = this.entries.get(key);\n if (at === undefined) return false;\n if (Date.now() - at > this.ttlMs) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n set(key: string): void {\n this.maybeSweep();\n // Refresh timestamp by re-inserting at the tail of insertion order.\n this.entries.delete(key);\n this.entries.set(key, Date.now());\n\n while (this.entries.size > this.maxEntries) {\n const oldestKey = this.entries.keys().next().value;\n if (oldestKey === undefined) break;\n this.entries.delete(oldestKey);\n }\n }\n\n /**\n * Walk the insertion-ordered map from the front and drop expired entries.\n * Stops at the first non-expired entry since events arrive roughly in time\n * order. Called on every set, so cost is amortized.\n */\n private maybeSweep(): void {\n this.insertsSinceSweep += 1;\n if (this.insertsSinceSweep < 64 && this.entries.size < this.maxEntries) {\n return;\n }\n this.insertsSinceSweep = 0;\n const now = Date.now();\n for (const [key, at] of this.entries) {\n if (now - at <= this.ttlMs) break;\n this.entries.delete(key);\n }\n }\n}\n","import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport { LarkDecryptError } from \"./errors.js\";\n\n/**\n * Verify an `X-Lark-Signature` header against the raw webhook body.\n *\n * Feishu computes: `sha256(timestamp + nonce + encrypt_key + body)` and ships\n * the hex digest (optionally prefixed with `sha256=`) in `X-Lark-Signature`.\n * We concatenate the string parts first, then the raw bytes of the body, to\n * avoid a UTF-8 round-trip on the request body.\n *\n * Constant-time compare. Returns false on length mismatch instead of throwing.\n */\nexport function verifySignature(opts: {\n timestamp: string;\n nonce: string;\n encryptKey: string;\n rawBody: Buffer;\n signatureHeader: string;\n}): boolean {\n const expected = opts.signatureHeader.replace(/^sha256=/, \"\");\n const computed = createHash(\"sha256\")\n .update(opts.timestamp + opts.nonce + opts.encryptKey)\n .update(opts.rawBody)\n .digest(\"hex\");\n const a = Buffer.from(computed, \"hex\");\n const b = Buffer.from(expected, \"hex\");\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/**\n * Decrypt the `encrypt` field from a Feishu webhook body.\n *\n * Layout:\n * key = SHA256(encrypt_key) // 32 bytes → AES-256\n * buf = base64decode(encrypt_field)\n * iv = buf[0:16]\n * ct = buf[16:] // AES-256-CBC ciphertext\n * plaintext = AES_256_CBC_decrypt(key, iv, ct) // PKCS#7 unpadded\n *\n * Returns the raw plaintext bytes. The caller is expected to JSON.parse them.\n */\nexport function decryptPayload(encryptB64: string, encryptKey: string): Buffer {\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const buf = Buffer.from(encryptB64, \"base64\");\n\n if (buf.length < 32) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext too short (${buf.length} bytes; need >= 32 for IV + one block)`,\n );\n }\n if ((buf.length - 16) % 16 !== 0) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext length ${buf.length} is not 16 + N*16`,\n );\n }\n\n const iv = buf.subarray(0, 16);\n const ct = buf.subarray(16);\n const dec = createDecipheriv(\"aes-256-cbc\", key, iv);\n\n try {\n return Buffer.concat([dec.update(ct), dec.final()]);\n } catch (e) {\n throw new LarkDecryptError(\"eve-lark: AES decrypt failed (bad padding or wrong key)\", {\n cause: e,\n });\n }\n}\n","import type {\n LarkInboundEvent,\n LarkInboundFile,\n LarkInboundResult,\n LarkMention,\n LarkRawMention,\n} from \"./types.js\";\n\nconst MIME_BY_EXT: Record<string, string> = {\n pdf: \"application/pdf\",\n zip: \"application/zip\",\n gz: \"application/gzip\",\n tar: \"application/x-tar\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n csv: \"text/csv\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n json: \"application/json\",\n xml: \"application/xml\",\n html: \"text/html\",\n htm: \"text/html\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n mp4: \"video/mp4\",\n mov: \"video/quicktime\",\n};\n\nfunction mimeFromExt(filename: string | undefined): string {\n if (!filename) return \"application/octet-stream\";\n const dot = filename.lastIndexOf(\".\");\n if (dot < 0) return \"application/octet-stream\";\n return MIME_BY_EXT[filename.slice(dot + 1).toLowerCase()] ?? \"application/octet-stream\";\n}\n\nfunction mentionFromRaw(m: LarkRawMention, botOpenId: string | undefined): LarkMention {\n const isOpenIdOfBot =\n !!botOpenId && !!m.id.open_id && m.id.open_id === botOpenId;\n const isAll = !!m.id.open_id && m.id.open_id === \"all\";\n return {\n key: m.key,\n id: {\n openId: m.id.open_id,\n userId: m.id.user_id,\n unionId: m.id.union_id,\n },\n name: m.name,\n idType: m.id_type ?? \"open_id\",\n isOpenIdOfBot,\n isAll,\n };\n}\n\nfunction stripBotMentions(text: string, mentions: LarkMention[]): string {\n // Feishu ships mentions as opaque placeholders (e.g. \"@_user_1\") in the text\n // body alongside a structured mentions array. Rewrite them to something the\n // model can read:\n // - the bot itself: dropped (the model already knows it's being addressed)\n // - @all: replaced with a literal \"@all\" token\n // - other users: replaced with \"@<display name>\"\n let out = text;\n for (const m of mentions) {\n if (!m.key) continue;\n if (m.isOpenIdOfBot) {\n out = out.split(m.key).join(\"\");\n } else if (m.isAll) {\n out = out.split(m.key).join(\"@all\");\n } else {\n out = out.split(m.key).join(`@${m.name}`);\n }\n }\n return out.replace(/\\s+/g, \" \").trim();\n}\n\ninterface ParsedContent {\n text: string;\n files: LarkInboundFile[];\n}\n\nfunction parseContent(messageType: string, rawContent: string): ParsedContent {\n if (!rawContent) return { text: \"\", files: [] };\n let content: Record<string, unknown>;\n try {\n content = JSON.parse(rawContent) as Record<string, unknown>;\n } catch {\n return { text: \"\", files: [] };\n }\n\n switch (messageType) {\n case \"text\": {\n const text = typeof content.text === \"string\" ? content.text : \"\";\n return { text, files: [] };\n }\n case \"image\": {\n const imageKey = typeof content.image_key === \"string\" ? content.image_key : \"\";\n if (!imageKey) return { text: \"\", files: [] };\n return {\n text: \"\",\n files: [{ fileKey: imageKey, mediaType: \"image/png\", kind: \"image\" }],\n };\n }\n case \"file\": {\n const fileKey = typeof content.file_key === \"string\" ? content.file_key : \"\";\n if (!fileKey) return { text: \"\", files: [] };\n const fileName = typeof content.file_name === \"string\" ? content.file_name : undefined;\n return {\n text: \"\",\n files: [{ fileKey, mediaType: mimeFromExt(fileName), kind: \"file\" }],\n };\n }\n case \"post\": {\n const locale = (content.zh_cn ?? content.en_us ?? content.ja_jp ?? null) as\n | { content?: unknown[][] }\n | null;\n if (!locale?.content) return { text: \"\", files: [] };\n const text = locale.content\n .flatMap((line) =>\n (line ?? [])\n .filter((node): node is { tag: string; text?: unknown } => {\n if (typeof node !== \"object\" || node === null) return false;\n const tag = (node as { tag?: unknown }).tag;\n const text = (node as { text?: unknown }).text;\n return tag === \"text\" && typeof text === \"string\";\n })\n .map((node) => node.text as string),\n )\n .join(\" \");\n return { text, files: [] };\n }\n default:\n // audio, media, sticker, share_chat, share_user, interactive — not in v1 scope.\n return { text: \"\", files: [] };\n }\n}\n\nexport function parseInbound(\n event: LarkInboundEvent,\n botOpenId?: string,\n): LarkInboundResult {\n const messageType = event.message.message_type;\n const parsed = parseContent(messageType, event.message.content);\n const rawMentions = event.message.mentions ?? [];\n const mentions = rawMentions.map((m) => mentionFromRaw(m, botOpenId));\n\n const senderOpenId =\n event.sender.sender_id.open_id ??\n event.sender.sender_id.user_id ??\n event.sender.sender_id.union_id ??\n \"\";\n\n const text =\n messageType === \"text\"\n ? stripBotMentions(parsed.text, mentions)\n : parsed.text;\n\n const chatType = event.chat_type === \"group\" ? \"group\" : \"p2p\";\n const senderType = event.sender.sender_type === \"app\" ? \"app\" : \"user\";\n\n return {\n text,\n files: parsed.files,\n chatId: event.message.chat_id,\n rootId: event.message.root_id ?? null,\n parentId: event.message.parent_id ?? null,\n messageId: event.message.message_id,\n senderOpenId,\n senderType,\n chatType,\n mentions,\n };\n}\n","import type { LarkCard, LarkCardButton, LarkInputRequest } from \"./types.js\";\n\nconst BASE_CONFIG = {\n wide_screen_mode: true,\n update_multi: true,\n} as const;\n\n/**\n * Build a simple single-shot card with the given markdown text. Renders via\n * `div` + `lark_md` so the font size is close to a native chat message\n * (the bare `markdown` element renders noticeably smaller).\n */\nexport function buildTextCard(text: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"div\", text: { tag: \"lark_md\", content: text } }],\n };\n}\n\n/**\n * Build a streaming card with an optional status prefix and an answer buffer.\n */\nexport function buildStreamingCard(opts: { buffer: string; status?: string | undefined }): LarkCard {\n const lines: string[] = [];\n if (opts.status) {\n lines.push(`<font color='grey'>${opts.status}</font>`);\n }\n lines.push(opts.buffer.length > 0 ? opts.buffer : \"_…_\");\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"div\", text: { tag: \"lark_md\", content: lines.join(\"\\n\\n\") } }],\n };\n}\n\n/**\n * Build an error card displayed when a turn fails.\n */\nexport function buildErrorCard(message: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [\n { tag: \"div\", text: { tag: \"lark_md\", content: `<font color='red'>⚠ ${message}</font>` } },\n ],\n };\n}\n\n/** Marker placed in every ask-card button value so the card-action handler\n * can recognise our own callbacks (and ignore card actions from other\n * sources on the same message). */\nexport const ASK_BUTTON_VALUE_MARKER = \"__eveLarkAsk\";\n\n/**\n * Build a Feishu interactive card that surfaces an eve `ask_question`\n * input request. Each selectable option becomes a button whose `value`\n * carries `{__eveLarkAsk, requestId, optionId}` — when the user clicks,\n * Feishu's `card.action.trigger` callback returns that JSON to us.\n *\n * For `allowFreeform: true` with no options, renders just the prompt\n * (the user replies with a normal chat message, which the channel\n * intercepts as the freeform response).\n *\n * For `allowFreeform: true` WITH options, renders buttons AND a footer\n * hint that the user can also type a reply.\n */\nexport function buildAskCard(request: LarkInputRequest): LarkCard {\n const elements: LarkCard[\"elements\"] = [\n { tag: \"div\", text: { tag: \"lark_md\", content: request.prompt } },\n ];\n\n if (request.options && request.options.length > 0) {\n const buttons: LarkCardButton[] = request.options.map((opt) => ({\n tag: \"button\",\n text: { tag: \"plain_text\", content: opt.label },\n type: opt.style ?? \"default\",\n value: {\n [ASK_BUTTON_VALUE_MARKER]: true,\n requestId: request.requestId,\n optionId: opt.id,\n },\n ...(opt.description\n ? { confirm: { title: { tag: \"plain_text\", content: opt.label }, text: { tag: \"plain_text\", content: opt.description } } }\n : {}),\n }));\n elements.push({ tag: \"action\", actions: buttons });\n }\n\n if (request.allowFreeform) {\n const hint =\n request.options && request.options.length > 0\n ? \"_…or reply to this chat with your own answer_\"\n : \"_Reply to this chat with your answer_\";\n elements.push({ tag: \"div\", text: { tag: \"lark_md\", content: hint } });\n }\n\n return { config: { ...BASE_CONFIG }, elements };\n}\n\n/**\n * Build the \"post-click\" card body that replaces the ask-card once the user\n * has answered. Disables further clicks by removing the action row and\n * appending a \"✓ <selected label>\" line.\n */\nexport function buildAskAnsweredCard(\n request: LarkInputRequest,\n selected: { kind: \"option\"; label: string } | { kind: \"freeform\"; text: string },\n): LarkCard {\n const elements: LarkCard[\"elements\"] = [\n { tag: \"div\", text: { tag: \"lark_md\", content: request.prompt } },\n ];\n const summary =\n selected.kind === \"option\"\n ? `<font color='green'>✓ ${escapeMarkdown(selected.label)}</font>`\n : `<font color='green'>✓ ${escapeMarkdown(selected.text)}</font>`;\n elements.push({ tag: \"div\", text: { tag: \"lark_md\", content: summary } });\n return { config: { ...BASE_CONFIG }, elements };\n}\n\n/** Escape characters that have special meaning in lark_md so user-controlled\n * strings can't inject formatting. */\nfunction escapeMarkdown(s: string): string {\n return s.replace(/[*_`~\\[\\]]/g, (m) => `\\\\${m}`);\n}\n\n","import { buildErrorCard, buildStreamingCard, buildTextCard } from \"./card.js\";\nimport type { LarkCard } from \"./types.js\";\n\ntype State = \"idle\" | \"creating\" | \"streaming\" | \"completed\" | \"aborted\";\n\ninterface ControllerDeps {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n patchIntervalMs: number;\n createThresholdMs: number;\n}\n\ninterface LarkClientLike {\n sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n patchCard(args: { messageId: string; card: LarkCard }): Promise<void>;\n sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n}\n\n/**\n * Streaming interactive-card state machine.\n *\n * idle ──first delta──> creating ──sendCard ok──> streaming ──finalize──> completed\n * │\n * └──sendCard fail──> aborted (flag; static fallback on message.completed)\n *\n * The card is created lazily after `createThresholdMs` of the first delta, so\n * short turns can short-circuit straight to `finalize` (which sends the card\n * with the full answer in one shot). Once streaming, patches are throttled to\n * `patchIntervalMs`.\n *\n * If `sendCard` fails on creation, the controller flips to `fallbackToText`\n * and `finalize`/`ensureFinalized` deliver via `sendText` instead.\n */\nexport class StreamingCardController {\n private readonly deps: ControllerDeps;\n private readonly client: LarkClientLike;\n\n private state: State = \"idle\";\n private buffer = \"\";\n private status: string | undefined;\n private messageId: string | undefined;\n private fallbackToText = false;\n\n private createTimer: ReturnType<typeof setTimeout> | null = null;\n private patchTimer: ReturnType<typeof setTimeout> | null = null;\n private patchInFlight: Promise<void> | null = null;\n private patchScheduled = false;\n private lastPatchAt = 0;\n\n constructor(client: LarkClientLike, deps: ControllerDeps) {\n this.client = client;\n this.deps = deps;\n }\n\n appendDelta(text: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.buffer += text;\n if (this.state === \"idle\") {\n this.scheduleCreate();\n } else if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n setStatus(status: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.status = status;\n if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n async finalize(fullText: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n this.cancelPatchTimer();\n this.buffer = fullText;\n\n if (this.fallbackToText) {\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n return;\n }\n\n if (this.messageId === undefined) {\n // Never managed to create a card. Send one with the full text in a\n // single shot so the user still gets a card reply.\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildTextCard(fullText),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"completed\";\n } catch {\n // Last-resort fallback: plain text.\n this.fallbackToText = true;\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n }\n return;\n }\n\n // Card already exists; flush the final state.\n if (this.patchInFlight) {\n try {\n await this.patchInFlight;\n } catch {\n // swallow; we'll attempt the final patch below\n }\n }\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildStreamingCard({ buffer: fullText, status: undefined }),\n });\n this.state = \"completed\";\n }\n\n async abort(error: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n this.cancelPatchTimer();\n if (this.messageId === undefined) {\n // No card to patch; mark fallback and let finalize/ensureFinalized\n // deliver a plain-text error if asked.\n this.fallbackToText = true;\n this.state = \"aborted\";\n return;\n }\n try {\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildErrorCard(error),\n });\n } finally {\n this.state = \"aborted\";\n }\n }\n\n async ensureFinalized(): Promise<void> {\n if (this.state !== \"completed\" && this.state !== \"aborted\") {\n await this.finalize(this.buffer);\n }\n }\n\n isStreaming(): boolean {\n return this.state === \"streaming\" || this.state === \"creating\";\n }\n\n isCompleted(): boolean {\n return this.state === \"completed\" || this.state === \"aborted\";\n }\n\n private scheduleCreate(): void {\n if (this.createTimer) return;\n this.state = \"creating\";\n this.createTimer = setTimeout(() => {\n this.createTimer = null;\n void this.doCreate();\n }, this.deps.createThresholdMs);\n }\n\n private cancelCreateTimer(): void {\n if (this.createTimer) {\n clearTimeout(this.createTimer);\n this.createTimer = null;\n }\n }\n\n private async doCreate(): Promise<void> {\n if (this.state !== \"creating\") return;\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildStreamingCard({ buffer: this.buffer, status: this.status }),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"streaming\";\n this.lastPatchAt = Date.now();\n } catch (e) {\n console.warn(\n \"[eve-lark] streaming card create failed; will deliver via plain text on finalize:\",\n e instanceof Error ? e.message : e,\n );\n this.fallbackToText = true;\n this.state = \"streaming\"; // keep accepting deltas; finalize will deliver as text\n }\n }\n\n private schedulePatch(): void {\n if (this.patchScheduled) return;\n this.patchScheduled = true;\n const elapsed = Date.now() - this.lastPatchAt;\n const wait = Math.max(0, this.deps.patchIntervalMs - elapsed);\n this.patchTimer = setTimeout(() => {\n this.patchTimer = null;\n this.patchScheduled = false;\n void this.maybeFlushPatch();\n }, wait);\n }\n\n private cancelPatchTimer(): void {\n if (this.patchTimer) {\n clearTimeout(this.patchTimer);\n this.patchTimer = null;\n }\n this.patchScheduled = false;\n }\n\n private async maybeFlushPatch(): Promise<void> {\n if (this.state !== \"streaming\") return;\n if (this.patchInFlight) return;\n if (this.messageId === undefined) return;\n const card = buildStreamingCard({ buffer: this.buffer, status: this.status });\n this.patchInFlight = this.client\n .patchCard({ messageId: this.messageId, card })\n .catch((e) => {\n // Best-effort: the next delta will retry. Log so operators can see\n // when the card stream is degraded.\n console.warn(\n \"[eve-lark] streaming card patch failed:\",\n e instanceof Error ? e.message : e,\n );\n })\n .finally(() => {\n this.patchInFlight = null;\n this.lastPatchAt = Date.now();\n });\n await this.patchInFlight;\n }\n}\n","import { LarkConfigError } from \"./errors.js\";\nimport type {\n LarkChannelOptions,\n LarkReplyMode,\n LarkTransportMode,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\nconst DEFAULTS = {\n baseUrl: \"https://open.feishu.cn\",\n webhookPath: \"/lark/webhook\",\n // \"post\" renders at native chat-message size with full markdown support\n // (bold, links, code, color tags). Cards render noticeably smaller because\n // Feishu treats them as \"structured content\". The tradeoff: post can't be\n // live-patched during streaming — users who want streaming should set\n // replyMode: \"streaming\" explicitly.\n replyMode: \"post\" as LarkReplyMode,\n streamPatchIntervalMs: 1000,\n streamCreateThresholdMs: 400,\n dedupTtlMs: 30 * 60 * 1000,\n dedupMaxEntries: 5000,\n requestTimeoutMs: 15000,\n maxRetries: 2,\n tokenRefreshBufferMs: 5 * 60 * 1000,\n signatureSkewMs: 5 * 60 * 1000,\n ackReaction: \"Typing\" as string | false,\n mode: \"long-connection\" as LarkTransportMode,\n};\n\nconst ENV_KEYS = {\n appId: \"LARK_APP_ID\",\n appSecret: \"LARK_APP_SECRET\",\n verificationToken: \"LARK_VERIFICATION_TOKEN\",\n encryptKey: \"LARK_ENCRYPT_KEY\",\n baseUrl: \"LARK_BASE_URL\",\n botOpenId: \"LARK_BOT_OPEN_ID\",\n replyMode: \"LARK_REPLY_MODE\",\n mode: \"LARK_MODE\",\n} as const;\n\nexport type ResolveEnv = Record<string, string | undefined>;\n\nfunction defaultEnv(): ResolveEnv {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env as ResolveEnv;\n }\n return {};\n}\n\nfunction pick(input: string | undefined, envValue: string | undefined): string | undefined {\n return input ?? envValue;\n}\n\nexport function resolveOptions(\n options: LarkChannelOptions,\n env: ResolveEnv = defaultEnv(),\n): ResolvedLarkOptions {\n const appId = pick(options.appId, env[ENV_KEYS.appId]);\n const appSecret = pick(options.appSecret, env[ENV_KEYS.appSecret]);\n const verificationToken = pick(\n options.verificationToken,\n env[ENV_KEYS.verificationToken],\n );\n\n if (!appId) {\n throw new LarkConfigError(\n `eve-lark: appId is required (option \\`appId\\` or env \\`${ENV_KEYS.appId}\\`)`,\n );\n }\n if (!appSecret) {\n throw new LarkConfigError(\n `eve-lark: appSecret is required (option \\`appSecret\\` or env \\`${ENV_KEYS.appSecret}\\`)`,\n );\n }\n if (!verificationToken) {\n throw new LarkConfigError(\n `eve-lark: verificationToken is required (option \\`verificationToken\\` or env \\`${ENV_KEYS.verificationToken}\\`)`,\n );\n }\n\n const rawBaseUrl = pick(options.baseUrl, env[ENV_KEYS.baseUrl]) ?? DEFAULTS.baseUrl;\n const baseUrl = rawBaseUrl.replace(/\\/+$/, \"\");\n\n const replyModeEnv = env[ENV_KEYS.replyMode];\n const replyMode: LarkReplyMode =\n options.replyMode ??\n (replyModeEnv === \"post\" || replyModeEnv === \"static\" || replyModeEnv === \"streaming\"\n ? replyModeEnv\n : DEFAULTS.replyMode);\n\n const modeEnv = env[ENV_KEYS.mode];\n const mode: LarkTransportMode =\n options.mode ??\n (modeEnv === \"webhook\" || modeEnv === \"long-connection\" ? modeEnv : DEFAULTS.mode);\n\n return {\n appId,\n appSecret,\n verificationToken,\n encryptKey: pick(options.encryptKey, env[ENV_KEYS.encryptKey]),\n baseUrl,\n botOpenId: pick(options.botOpenId, env[ENV_KEYS.botOpenId]),\n webhookPath: options.webhookPath ?? DEFAULTS.webhookPath,\n replyMode,\n streamPatchIntervalMs: options.streamPatchIntervalMs ?? DEFAULTS.streamPatchIntervalMs,\n streamCreateThresholdMs: options.streamCreateThresholdMs ?? DEFAULTS.streamCreateThresholdMs,\n dedupTtlMs: options.dedupTtlMs ?? DEFAULTS.dedupTtlMs,\n dedupMaxEntries: options.dedupMaxEntries ?? DEFAULTS.dedupMaxEntries,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n tokenRefreshBufferMs: options.tokenRefreshBufferMs ?? DEFAULTS.tokenRefreshBufferMs,\n signatureSkewMs: options.signatureSkewMs ?? DEFAULTS.signatureSkewMs,\n fetch: options.fetch ?? globalThis.fetch,\n ackReaction: options.ackReaction ?? DEFAULTS.ackReaction,\n mode,\n port:\n options.port ??\n (process.env.PORT ? Number(process.env.PORT) : 2000),\n };\n}\n","/**\n * Long-connection transport: when `mode: \"long-connection\"` is set on\n * {@link createLarkChannel} (the default), the channel starts a Feishu\n * `@larksuiteoapi/node-sdk` WSClient as a side effect of construction. Each\n * inbound event is re-encrypted + re-signed and POSTed to the channel's own\n * webhook on localhost, where the standard webhook handler runs (with full\n * access to `send()` etc.). This lets users run the bot against a real Feishu\n * app from `eve dev` alone — no public webhook URL, no second process.\n *\n * The SDK is a hard runtime dependency of this package (declared in\n * `dependencies`), so `pnpm add eve-lark` brings it in automatically. The\n * `import()` below is dynamic only so `mode: \"webhook\"` code paths don't\n * eagerly load the SDK at module import time.\n */\n\nimport {\n createCipheriv,\n createHash,\n randomBytes,\n} from \"node:crypto\";\nimport type { ResolvedLarkOptions } from \"./types.js\";\n\n/** A Feishu v2 envelope we forward to the channel webhook. */\nexport type LarkEvent = {\n schema?: string;\n type?: string;\n challenge?: string;\n token?: string;\n header?: Record<string, unknown>;\n event?: unknown;\n [k: string]: unknown;\n};\n\nexport interface PostEventOptions {\n eveWebhookUrl: string;\n /** When set, the body is AES-encrypted and the request is signed. */\n encryptKey?: string | undefined;\n /** Override for tests. Defaults to globalThis.fetch. */\n fetch?: typeof fetch | undefined;\n}\n\nfunction aesEncrypt(plaintext: Buffer, key: string): Buffer {\n const keyBuf = createHash(\"sha256\").update(key).digest();\n const iv = randomBytes(16);\n const cipher = createCipheriv(\"aes-256-cbc\", keyBuf, iv);\n return Buffer.concat([iv, cipher.update(plaintext), cipher.final()]);\n}\n\nfunction signBody(timestamp: string, nonce: string, body: Buffer, key: string): string {\n return createHash(\"sha256\")\n .update(timestamp + nonce + key)\n .update(body)\n .digest(\"hex\");\n}\n\n/**\n * Forward one Feishu event to the local eve webhook. Re-encrypts and signs\n * when an `encryptKey` is set, so the channel handler exercises its own\n * signature + AES pipeline on every event.\n */\nexport async function postEventToWebhook(\n event: LarkEvent,\n opts: PostEventOptions,\n): Promise<void> {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const plain = Buffer.from(JSON.stringify(event), \"utf8\");\n let body: Buffer;\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n\n if (opts.encryptKey) {\n const encrypted = aesEncrypt(plain, opts.encryptKey).toString(\"base64\");\n body = Buffer.from(JSON.stringify({ encrypt: encrypted }), \"utf8\");\n const ts = Math.floor(Date.now() / 1000).toString();\n const nonce = randomBytes(8).toString(\"hex\");\n const sig = signBody(ts, nonce, body, opts.encryptKey);\n headers[\"x-lark-request-timestamp\"] = ts;\n headers[\"x-lark-request-nonce\"] = nonce;\n headers[\"x-lark-signature\"] = `sha256=${sig}`;\n } else {\n body = plain;\n }\n\n const res = await fetchImpl(opts.eveWebhookUrl, {\n method: \"POST\",\n headers,\n body: new Uint8Array(body.buffer, body.byteOffset, body.byteLength) as never,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `eve-lark: forward to ${opts.eveWebhookUrl} failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : \"\"}`,\n );\n }\n}\n\n/**\n * Post-event wrapper with a single exponential-backoff retry. Catches the\n * common case where `eve dev` is momentarily unavailable (between HMR\n * reloads, mid-restart, GC pause). Without this the event would be dropped\n * on the floor with just a log line — bad UX during dev.\n */\nexport async function postEventToWebhookRetry(\n event: LarkEvent,\n opts: PostEventOptions,\n): Promise<void> {\n try {\n await postEventToWebhook(event, opts);\n } catch (firstErr) {\n await new Promise((r) => setTimeout(r, 300));\n // Regenerate signature: timestamp/nonce moved on by ~300ms, and the\n // skew check at the channel handler would reject a stale signature.\n await postEventToWebhook(event, opts).catch((retryErr) => {\n throw retryErr instanceof Error\n ? retryErr\n : new Error(String(retryErr), { cause: firstErr });\n });\n }\n}\n\n/**\n * The Feishu SDK's EventDispatcher passes handlers a payload that may or may\n * not include the outer envelope. Rebuild a v2-shaped envelope so the channel\n * webhook can parse it the same way it parses a raw Feishu POST.\n */\nexport function rebuildEnvelopeFromSdkEvent(\n eventType: string,\n data: unknown,\n ctx: { appId: string; verificationToken: string },\n): LarkEvent {\n const maybeHeader = (data as { header?: Record<string, unknown> })?.header;\n const header =\n maybeHeader && typeof maybeHeader === \"object\"\n ? maybeHeader\n : {\n event_id: `lc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n event_type: eventType,\n create_time: String(Math.floor(Date.now() / 1000)),\n token: ctx.verificationToken,\n app_id: ctx.appId,\n };\n const maybeEvent = (data as { event?: unknown })?.event;\n return {\n schema: \"2.0\",\n header,\n event: maybeEvent ?? data,\n };\n}\n\nexport interface StartLongConnectionArgs {\n resolved: ResolvedLarkOptions;\n eveWebhookUrl: string;\n /** Override logger. */\n log?: ((msg: string) => void) | undefined;\n logError?: ((msg: string, err?: unknown) => void) | undefined;\n /** Test seam: inject a custom SDK module. */\n sdk?: unknown;\n}\n\n/**\n * Detect whether this process is the `eve start` launcher (NOT the nitro\n * server it spawns). The launcher loads the channel module for build\n * discovery but never serves HTTP; the spawned `.output/server/index.mjs`\n * child is what actually runs the server. Without this check, both\n * processes would start a WSClient and Feishu would deliver every event\n * twice.\n *\n * Discriminator: the launcher's `argv[1]` is the eve CLI binary\n * (`.../eve/bin/eve.js` or `.mjs`/`.cjs`/no-ext shim) and `argv[2]` is\n * `start`. The spawned server child has `argv[1] = .output/server/index.mjs`\n * and `argv[2]` unset. `eve dev` runs nitro in-process (no fork), so we\n * DON'T treat it as a launcher — the WSClient must start there.\n *\n * Override: set `EVE_LARK_FORCE_WS=1` to always start (escape hatch in\n * case the heuristic breaks for some package-manager layout).\n */\nexport function isEveStartLauncher(): boolean {\n if (process.env.EVE_LARK_FORCE_WS === \"1\") return false;\n const arg1 = process.argv[1] ?? \"\";\n const isEveBinary = /[/\\\\]eve[/\\\\]bin[/\\\\]eve(?:\\.[cm]?js)?$/.test(arg1);\n return isEveBinary && process.argv[2] === \"start\";\n}\n\n/**\n * Active connections keyed by `${appId}:${eveWebhookUrl}`.\n *\n * Eve's lifecycle can construct the channel module more than once (e.g.,\n * build-time scan + serve-time import, or HMR reload). Each construction\n * would naively start a fresh WSClient — Feishu then delivers every event\n * to BOTH connections, and the user sees double replies.\n *\n * Guard: if a connection for the same key is already running (or starting),\n * the second call resolves immediately without touching the SDK. On\n * failure, the slot is cleared so a retry can succeed.\n *\n * The map lives on `globalThis` so it survives module reloads (HMR, build\n * then serve) — otherwise a reloaded module instance would have its own\n * fresh map and the guard would silently fail.\n *\n * Single-process, so no lock is needed — `Map.has` + `Map.set` from the\n * same synchronous block is atomic in JS's single-threaded runtime.\n */\nconst GLOBAL_KEY = \"__eveLarkActiveConnections\";\ntype GlobalWithLark = typeof globalThis & {\n [GLOBAL_KEY]?: Map<string, Promise<void>>;\n};\nfunction getActiveConnections(): Map<string, Promise<void>> {\n const g = globalThis as GlobalWithLark;\n if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = new Map();\n return g[GLOBAL_KEY]!;\n}\n\n/** @internal — test-only seam for resetting module state between cases. */\nexport function __resetLongConnectionSingletonsForTests(): void {\n getActiveConnections().clear();\n}\n\n/**\n * Start the Feishu WSClient side effect. Connects via the official SDK,\n * registers a handler that re-signs each event and POSTs it to the local eve\n * webhook. Resolves once the connection is established; the WSClient then\n * runs in the background for the lifetime of the process.\n *\n * Idempotent: a second call with the same `appId` + `eveWebhookUrl` is a\n * no-op (see {@link getActiveConnections}). Different keys (different app,\n * or different webhook URL — e.g. different `--port`) get separate WSClients.\n *\n * @throws if @larksuiteoapi/node-sdk is not installed, or the WSClient\n * fails to establish its first connection.\n */\nexport async function startLongConnection(args: StartLongConnectionArgs): Promise<void> {\n const key = `${args.resolved.appId}:${args.eveWebhookUrl}`;\n const activeConnections = getActiveConnections();\n const existing = activeConnections.get(key);\n if (existing) {\n console.log(\n `[eve-lark] startLongConnection: skip (already running) key=${key} pid=${process.pid}`,\n );\n return;\n }\n console.log(\n `[eve-lark] startLongConnection: start new WSClient key=${key} pid=${process.pid}`,\n );\n\n const promise = (async () => {\n await doStartLongConnection(args);\n })().catch((e) => {\n // On failure, clear the slot so a future call can retry.\n activeConnections.delete(key);\n throw e;\n });\n\n // Synchronous set after the synchronous has-check above — atomic in JS's\n // single-threaded runtime. No race with another concurrent caller.\n activeConnections.set(key, promise);\n await promise;\n}\n\nasync function doStartLongConnection(args: StartLongConnectionArgs): Promise<void> {\n const log = args.log ?? ((m: string) => console.log(`[eve-lark] ${m}`));\n const logError = args.logError ?? ((m: string, e?: unknown) => console.error(`[eve-lark] ${m}`, e ?? \"\"));\n\n const sdk = (args.sdk ?? (await loadLarkSdk())) as LarkSdk;\n\n const dispatcher = new sdk.EventDispatcher({\n verificationToken: args.resolved.verificationToken,\n encryptKey: args.resolved.encryptKey,\n });\n\n dispatcher.register({\n \"im.message.receive_v1\": async (data: unknown) => {\n try {\n const envelope = rebuildEnvelopeFromSdkEvent(\"im.message.receive_v1\", data, {\n appId: args.resolved.appId,\n verificationToken: args.resolved.verificationToken,\n });\n await postEventToWebhookRetry(envelope, {\n eveWebhookUrl: args.eveWebhookUrl,\n encryptKey: args.resolved.encryptKey,\n });\n } catch (e) {\n logError(`forward failed (event dropped)`, e);\n }\n },\n\n // Card-button clicks. Feishu's card.action.trigger fires when a user\n // taps a button on a card we rendered. Forward to the channel webhook\n // — the webhook handler dispatches by event_type and feeds the click\n // back into eve as an InputResponse.\n \"card.action.trigger\": async (data: unknown) => {\n try {\n const envelope = rebuildEnvelopeFromSdkEvent(\"card.action.trigger\", data, {\n appId: args.resolved.appId,\n verificationToken: args.resolved.verificationToken,\n });\n await postEventToWebhookRetry(envelope, {\n eveWebhookUrl: args.eveWebhookUrl,\n encryptKey: args.resolved.encryptKey,\n });\n } catch (e) {\n logError(`card action forward failed (event dropped)`, e);\n }\n },\n });\n\n const domain = args.resolved.baseUrl.includes(\"larksuite.com\")\n ? sdk.Domain.Lark\n : sdk.Domain.Feishu;\n\n const wsClient = new sdk.WSClient({\n appId: args.resolved.appId,\n appSecret: args.resolved.appSecret,\n domain,\n onReady: () => log(`WS connected to Feishu (${args.resolved.baseUrl})`),\n onError: (err: Error) => logError(`WS error`, err),\n onReconnecting: () => log(`WS reconnecting…`),\n onReconnected: () => log(`WS reconnected`),\n autoReconnect: true,\n });\n\n await wsClient.start({ eventDispatcher: dispatcher });\n}\n\n/* eslint-disable @typescript-eslint/consistent-type-imports */\n// The `import()` type query is the canonical way to express \"the runtime\n// namespace of this dynamic import\" without making the SDK a hard dep.\ntype LarkSdk = typeof import(\"@larksuiteoapi/node-sdk\");\n/* eslint-enable @typescript-eslint/consistent-type-imports */\n\nasync function loadLarkSdk(): Promise<LarkSdk> {\n try {\n return await import(\"@larksuiteoapi/node-sdk\");\n } catch {\n throw new Error(\n \"eve-lark: mode:\\\"long-connection\\\" requires @larksuiteoapi/node-sdk. Install it: pnpm add @larksuiteoapi/node-sdk (or npm/yarn equivalent).\",\n );\n }\n}\n","/**\n * Valid Feishu emoji type strings for the message-reactions API.\n *\n * Feishu emoji types are case-sensitive and inconsistent: most are uppercase\n * (`THUMBSUP`, `OK`), but a meaningful subset is CamelCase (`Typing`,\n * `CrossMark`, `EatingFood`, `Drumstick`, …). Passing the wrong case fails\n * with HTTP 400 code=231001 \"reaction type is invalid\" — silent unless the\n * caller is watching logs.\n *\n * Source: openclaw-lark's `VALID_FEISHU_EMOJI_TYPES` (which references the\n * official Feishu emoji doc).\n */\nexport const VALID_FEISHU_EMOJI_TYPES: ReadonlySet<string> = new Set([\n \"OK\", \"THUMBSUP\", \"THANKS\", \"MUSCLE\", \"FINGERHEART\", \"APPLAUSE\", \"FISTBUMP\",\n \"JIAYI\", \"DONE\", \"SMILE\", \"BLUSH\", \"LAUGH\", \"SMIRK\", \"LOL\", \"FACEPALM\",\n \"LOVE\", \"WINK\", \"PROUD\", \"WITTY\", \"SMART\", \"SCOWL\", \"THINKING\", \"SOB\",\n \"CRY\", \"ERROR\", \"NOSEPICK\", \"HAUGHTY\", \"SLAP\", \"SPITBLOOD\", \"TOASTED\",\n \"GLANCE\", \"DULL\", \"INNOCENTSMILE\", \"JOYFUL\", \"WOW\", \"TRICK\", \"YEAH\",\n \"ENOUGH\", \"TEARS\", \"EMBARRASSED\", \"KISS\", \"SMOOCH\", \"DROOL\", \"OBSESSED\",\n \"MONEY\", \"TEASE\", \"SHOWOFF\", \"COMFORT\", \"CLAP\", \"PRAISE\", \"STRIVE\",\n \"XBLUSH\", \"SILENT\", \"WAVE\", \"WHAT\", \"FROWN\", \"SHY\", \"DIZZY\", \"LOOKDOWN\",\n \"CHUCKLE\", \"WAIL\", \"CRAZY\", \"WHIMPER\", \"HUG\", \"BLUBBER\", \"WRONGED\",\n \"HUSKY\", \"SHHH\", \"SMUG\", \"ANGRY\", \"HAMMER\", \"SHOCKED\", \"TERROR\",\n \"PETRIFIED\", \"SKULL\", \"SWEAT\", \"SPEECHLESS\", \"SLEEP\", \"DROWSY\", \"YAWN\",\n \"SICK\", \"PUKE\", \"BETRAYED\", \"HEADSET\", \"EatingFood\", \"MeMeMe\", \"Sigh\",\n \"Typing\", \"SLIGHT\", \"TONGUE\", \"EYESCLOSED\", \"RoarForYou\", \"CALF\", \"BEAR\",\n \"BULL\", \"RAINBOWPUKE\", \"Lemon\", \"ROSE\", \"HEART\", \"PARTY\", \"LIPS\", \"BEER\",\n \"CAKE\", \"GIFT\", \"CUCUMBER\", \"Drumstick\", \"Pepper\", \"CANDIEDHAWS\",\n \"BubbleTea\", \"Coffee\", \"Get\", \"LGTM\", \"OnIt\", \"OneSecond\", \"VRHeadset\",\n \"YouAreTheBest\", \"SALUTE\", \"SHAKE\", \"HIGHFIVE\", \"UPPERLEFT\", \"ThumbsDown\",\n \"Yes\", \"No\", \"OKR\", \"CheckMark\", \"CrossMark\", \"MinusOne\", \"Hundred\",\n \"AWESOMEN\", \"Pin\", \"Alarm\", \"Loudspeaker\", \"Trophy\", \"Fire\", \"BOMB\",\n \"Music\", \"XmasTree\", \"Snowman\", \"XmasHat\", \"FIREWORKS\", \"2022\",\n \"REDPACKET\", \"FORTUNE\", \"LUCK\", \"FIRECRACKER\", \"StickyRiceBalls\",\n \"HEARTBROKEN\", \"POOP\", \"StatusFlashOfInspiration\", \"18X\", \"CLEAVER\",\n \"Soccer\", \"Basketball\", \"GeneralDoNotDisturb\", \"Status_PrivateMessage\",\n \"GeneralInMeetingBusy\", \"StatusReading\", \"StatusInFlight\",\n \"GeneralBusinessTrip\", \"GeneralWorkFromHome\", \"StatusEnjoyLife\",\n \"GeneralTravellingCar\", \"StatusBus\", \"GeneralSun\", \"GeneralMoonRest\",\n \"MoonRabbit\", \"Mooncake\", \"JubilantRabbit\", \"TV\", \"Movie\", \"Pumpkin\",\n \"BeamingFace\", \"Delighted\", \"ColdSweat\", \"FullMoonFace\", \"Partying\",\n \"GoGoGo\", \"ThanksFace\", \"SaluteFace\", \"Shrug\", \"ClownFace\", \"HappyDragon\",\n]);\n\n/** Returns true iff `s` is a valid Feishu emoji type string (case-sensitive). */\nexport function isValidFeishuEmojiType(s: string): boolean {\n return VALID_FEISHU_EMOJI_TYPES.has(s);\n}\n"],"mappings":"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","names":["text","createHash","createHash"]}
1
+ {"version":3,"sources":["../src/channel.ts","../src/errors.ts","../src/lark-client.ts","../src/dedup.ts","../src/crypto.ts","../src/parse.ts","../src/card.ts","../src/streaming-controller.ts","../src/options.ts","../src/long-connection.ts","../src/feishu-emoji.ts"],"sourcesContent":["import {\n defineChannel,\n POST,\n type Channel,\n type RouteHandlerArgs,\n} from \"eve/channels\";\n\nimport { LarkClient } from \"./lark-client.js\";\nimport { DedupMap } from \"./dedup.js\";\nimport { decryptPayload, verifySignature } from \"./crypto.js\";\nimport { parseInbound } from \"./parse.js\";\nimport { StreamingCardController } from \"./streaming-controller.js\";\nimport {\n ASK_BUTTON_VALUE_MARKER,\n buildAskAnsweredCard,\n buildAskCard,\n buildTextCard,\n} from \"./card.js\";\nimport { resolveOptions } from \"./options.js\";\nimport { isEveStartLauncher, startLongConnection } from \"./long-connection.js\";\nimport { isValidFeishuEmojiType } from \"./feishu-emoji.js\";\nimport type {\n LarkCardActionTriggerEvent,\n LarkChannelOptions,\n LarkContinuationToken,\n LarkEncryptedBody,\n LarkEventBody,\n LarkInboundEvent,\n LarkInboundFile,\n LarkInputRequest,\n LarkInputResponse,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\n/** Hard cap on inbound webhook body size. Feishu payloads are <10 KB; this\n * is purely defense against a malicious or buggy peer OOMing the process. */\nconst MAX_BODY_BYTES = 1_000_000;\n\n/** Drop a session's controller if it's been inactive this long. Bounds the\n * closure-scoped `controllers`/`sessionMeta` Maps against crashes that\n * prevent `message.completed`/`turn.failed` from firing. */\nconst STALE_SESSION_MS = 30 * 60 * 1000;\n\n/** How often to sweep stale controllers. */\nconst SWEEP_INTERVAL_MS = 5 * 60 * 1000;\n\n/** Reply text used when the model returns null/empty — guarantees the user\n * always sees *something* so they're not left looking at a typing emoji. */\nconst EMPTY_REPLY_TEXT = \"(model returned no content)\";\n\n/**\n * Continuation token format: `${chatId}:${rootMessageId ?? \"_\"}`.\n * The framework prepends the channel file stem before handing the token to\n * the runtime; consumers should call this helper rather than concatenate.\n */\nexport function larkContinuationToken(\n chatId: string,\n rootMessageId: string | null,\n): LarkContinuationToken {\n return `${chatId}:${rootMessageId ?? \"_\"}` as LarkContinuationToken;\n}\n\ninterface LarkSessionMeta {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n /** The user message we ack-reacted to; needed to remove the reaction\n * after delivery. Same value as `ctx.session.auth.initiator.attributes.messageId`,\n * mirrored here so terminal handlers don't have to re-extract it. */\n messageId?: string | undefined;\n /** Reaction id returned by `addReaction`. Present once the ack-reaction\n * POST resolves, which may be after the first terminal event fires. */\n ackReactionId?: string | undefined;\n /** When the controller was last touched. Used by the stale-sweep. */\n touchedAt: number;\n}\n\ninterface ResolvedSessionInfo {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n messageId?: string | undefined;\n}\n\n/**\n * Extract chat + message metadata stashed on `auth.initiator.attributes` at\n * session start. This is the canonical place to read it: closure state\n * doesn't reliably cross eve's process/worker boundary, but auth attributes\n * are persisted with the session.\n */\nfunction sessionInfoFromCtx(ctx: { session?: { auth?: { initiator?: { attributes?: unknown } | null } | null } | null }): ResolvedSessionInfo | null {\n const attrs = (ctx.session?.auth?.initiator?.attributes ?? {}) as {\n chatId?: unknown;\n rootMessageId?: unknown;\n parentId?: unknown;\n messageId?: unknown;\n };\n if (typeof attrs.chatId !== \"string\" || !attrs.chatId) return null;\n return {\n chatId: attrs.chatId,\n rootId: typeof attrs.rootMessageId === \"string\" ? attrs.rootMessageId : undefined,\n parentId: typeof attrs.parentId === \"string\" ? attrs.parentId : undefined,\n messageId: typeof attrs.messageId === \"string\" ? attrs.messageId : undefined,\n };\n}\n\nfunction ackOk(): Response {\n return Response.json({ code: 0 });\n}\n\n/**\n * Resolve the configured `ackReaction` to a single valid emoji type for this\n * event, or `false` if reactions are disabled (or the configured value is\n * invalid). Picks randomly when given an array.\n *\n * Validates against {@link VALID_FEISHU_EMOJI_TYPES} because Feishu rejects\n * unknown emoji types with HTTP 400 code=231001. The validation is\n * case-sensitive — `TYPING` is invalid but `Typing` is. Without this check a\n * typo in the default or a user-supplied value fails silently on every\n * inbound message.\n */\nfunction pickAckEmoji(reaction: string | readonly string[] | false): string | false {\n if (reaction === false) return false;\n if (typeof reaction === \"string\") {\n if (!isValidFeishuEmojiType(reaction)) {\n console.warn(\n `[eve-lark] ackReaction \"${reaction}\" is not a valid Feishu emoji type ` +\n `(case-sensitive; e.g. \"Typing\" not \"TYPING\"). Skipping ack reaction. ` +\n `See VALID_FEISHU_EMOJI_TYPES for the full list.`,\n );\n return false;\n }\n return reaction;\n }\n if (Array.isArray(reaction)) {\n const valid = reaction.filter(isValidFeishuEmojiType);\n if (valid.length === 0) {\n const sample = reaction.slice(0, 3).join(\", \");\n console.warn(\n `[eve-lark] ackReaction array contains no valid Feishu emoji types ` +\n `(got [${sample}${reaction.length > 3 ? \", …\" : \"\"}]). Skipping ack reaction.`,\n );\n return false;\n }\n if (valid.length < reaction.length) {\n const dropped = reaction.filter((e) => !isValidFeishuEmojiType(e));\n console.warn(\n `[eve-lark] ackReaction array dropped ${dropped.length} invalid emoji type(s): ` +\n `${dropped.slice(0, 3).join(\", \")}`,\n );\n }\n const idx = Math.floor(Math.random() * valid.length);\n return valid[idx] ?? false;\n }\n return false;\n}\n\nfunction resourceUrl(\n options: ResolvedLarkOptions,\n file: LarkInboundFile,\n messageId: string,\n): string {\n const type = file.kind === \"image\" ? \"image\" : \"file\";\n return `${options.baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/resources/${encodeURIComponent(file.fileKey)}?type=${type}`;\n}\n\n/**\n * Build the eve UserContent payload from a parsed inbound event. Text comes\n * first; each inbound image/file becomes a `file` part carrying a URL pointing\n * at the Lark resource endpoint. The channel's `fetchFile` hook will stage\n * those URLs to bytes when the model runs.\n */\nfunction buildUserContent(\n text: string,\n files: LarkInboundFile[],\n options: ResolvedLarkOptions,\n messageId: string,\n): unknown[] {\n const parts: unknown[] = [];\n if (text.length > 0) parts.push({ type: \"text\", text });\n for (const f of files) {\n parts.push({\n type: \"file\",\n data: new URL(resourceUrl(options, f, messageId)),\n mediaType: f.mediaType,\n });\n }\n return parts;\n}\n\n/**\n * Port of eve's `formatErrorHint` from `#internal/logging.js`.\n *\n * Builds a ` (name: message)` hint from a turn.failed/session.failed event's\n * data payload. Reads `data.details.name` (error class, e.g. \"AI_APICallError\")\n * and `data.message` (the actual reason, e.g. a rate-limit string). Both are\n * optional; the hint is empty when neither is present so callers can\n * interpolate unconditionally: `` `I hit an error${hint}.` ``\n *\n * Truncated to 160 chars (matching eve) to keep one-line Feishu replies\n * readable.\n */\nfunction formatErrorHint(data: unknown): string {\n if (typeof data !== \"object\" || data === null) return \"\";\n const d = data as { details?: unknown; message?: unknown };\n const detailsName =\n typeof d.details === \"object\" && d.details !== null\n ? (d.details as { name?: unknown }).name\n : undefined;\n const name =\n typeof detailsName === \"string\" && detailsName.length > 0 ? detailsName : undefined;\n const message = typeof d.message === \"string\" ? d.message.trim() : \"\";\n if (name && message.length > 0) return ` (${name}: ${truncateForDisplay(message)})`;\n if (name) return ` (${name})`;\n if (message.length > 0) return ` (${truncateForDisplay(message)})`;\n return \"\";\n}\n\n/**\n * Port of eve's `extractErrorId`. Reads `data.details.errorId` if present —\n * a UUID users can quote to support. Returns undefined when absent.\n */\nfunction extractErrorId(details: unknown): string | undefined {\n if (typeof details === \"object\" && details !== null) {\n const id = (details as { errorId?: unknown }).errorId;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n }\n return undefined;\n}\n\nfunction truncateForDisplay(s: string, max = 160): string {\n return s.length <= max ? s : `${s.slice(0, max - 1).trimEnd()}…`;\n}\n\n/**\n * Compose a user-facing error message from a turn.failed/session.failed\n * event. Mirrors eve's official channel output:\n *\n * `⚠ I hit an error while handling your request (AI_APICallError: <reason>). (Error id: <uuid>)`\n *\n * Always returns a non-empty string. If `data` has no useful info, falls back\n * to `⚠ <fallbackReason>` (e.g. \"turn failed\").\n */\nfunction formatFailureMessage(\n data: unknown,\n fallback: string,\n opts: { sentence: \"turn\" | \"session\" } = { sentence: \"turn\" },\n): string {\n const hint = formatErrorHint(data);\n const errorId = extractErrorId((data as { details?: unknown } | null)?.details);\n const lead =\n opts.sentence === \"session\"\n ? \"This session couldn't recover from an error\"\n : \"I hit an error while handling your request\";\n const idSuffix = errorId ? ` (Error id: ${errorId})` : \"\";\n // If we have neither hint nor errorId, prefer the explicit fallback so\n // users get \"turn failed\" rather than the same string every time. With\n // hint or errorId present, the lead-in alone is informative enough.\n if (!hint && !errorId) return `⚠ ${fallback}`;\n return `⚠ ${lead}${hint}.${idSuffix}`;\n}\n\n/**\n * Create a Lark/Feishu channel for the eve agent framework.\n *\n * The channel mounts a single POST webhook that verifies the request,\n * decrypts the body when an encrypt key is configured, deduplicates events\n * by id, parses the inbound message, and starts or resumes an eve session.\n *\n * Streaming happens via eve's native channel events: `message.appended`\n * drives live card patches, `message.completed` finalizes the card, and\n * `turn.failed` aborts it. In `replyMode: \"static\"` the controller is\n * skipped and `message.completed` delivers a single card.\n *\n * **Delivery guarantee**: every terminal event (`message.completed` or\n * `turn.failed`) delivers *something* to the user. If the streaming card\n * path fails, we fall back to a fresh card; if that fails, plain text; if\n * even that fails, the error is logged. The user is never left looking at\n * a typing-emoji reaction with no reply.\n */\nexport function createLarkChannel(\n optionsInput: LarkChannelOptions,\n): Channel<undefined, Record<string, unknown>, Record<string, unknown>> {\n const options = resolveOptions(optionsInput);\n const client = new LarkClient(options);\n const dedup = new DedupMap(options.dedupTtlMs, options.dedupMaxEntries);\n\n // Long-connection side effect: when mode is \"long-connection\" (the\n // default), start a Feishu WSClient in the background. Each inbound event\n // is re-signed and POSTed to this channel's webhook on localhost, where\n // the standard handler runs with full access to send() etc.\n //\n // Skip when running inside the `eve start` launcher process: eve forks\n // a nitro server child to actually serve HTTP, and both processes load\n // the channel module. Without this guard each process spawns its own\n // WSClient and Feishu delivers every event twice.\n if (options.mode === \"long-connection\" && !isEveStartLauncher()) {\n const eveWebhookUrl = `http://127.0.0.1:${options.port}${options.webhookPath}`;\n void startLongConnection({ resolved: options, eveWebhookUrl }).catch((e) => {\n console.error(\"[eve-lark] long-connection startup failed:\", e);\n });\n } else if (options.mode === \"long-connection\" && isEveStartLauncher()) {\n console.log(\"[eve-lark] skipping WSClient start in eve-start launcher process; the spawned server will start it\");\n }\n\n // Channel-scoped (closure) state. Each session has its own controller +\n // chat metadata, keyed by session.id. Bounded by the stale-sweep below.\n const controllers = new Map<string, StreamingCardController>();\n const sessionMeta = new Map<string, LarkSessionMeta>();\n\n // Pending ask_question input requests, keyed by eve's requestId. Used to\n // resolve card.action.trigger callbacks back to the originating session.\n // Also keyed by chat-continuation-token for freeform interception.\n interface PendingInput {\n requestId: string;\n sessionId: string;\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n /** The card message id we sent (so we can patch it after the user answers). */\n cardMessageId?: string | undefined;\n /** Full request, so the post-click renderer can show selected label. */\n request: LarkInputRequest;\n /** When the pending input was registered (for stale-sweep). */\n createdAt: number;\n /** Whether to intercept the next inbound chat message as the response. */\n awaitingFreeform: boolean;\n touchedAt: number;\n }\n const pendingInputsByRequestId = new Map<string, PendingInput>();\n const pendingInputsByChatToken = new Map<string, PendingInput>();\n\n function getController(sessionId: string, meta: ResolvedSessionInfo): StreamingCardController {\n let ctrl = controllers.get(sessionId);\n if (!ctrl) {\n ctrl = new StreamingCardController(client, {\n chatId: meta.chatId,\n rootId: meta.rootId,\n parentId: meta.parentId,\n patchIntervalMs: options.streamPatchIntervalMs,\n createThresholdMs: options.streamCreateThresholdMs,\n });\n controllers.set(sessionId, ctrl);\n }\n if (sessionMeta.has(sessionId)) {\n sessionMeta.get(sessionId)!.touchedAt = Date.now();\n } else {\n sessionMeta.set(sessionId, {\n chatId: meta.chatId,\n rootId: meta.rootId,\n parentId: meta.parentId,\n messageId: meta.messageId,\n touchedAt: Date.now(),\n });\n }\n return ctrl;\n }\n\n function dropController(sessionId: string): void {\n controllers.delete(sessionId);\n sessionMeta.delete(sessionId);\n }\n\n /** Best-effort ack-reaction cleanup. Called from terminal handlers. */\n async function cleanupAckReaction(sessionId: string): Promise<void> {\n const meta = sessionMeta.get(sessionId);\n if (!meta?.ackReactionId || !meta.messageId) return;\n try {\n await client.removeReaction({\n messageId: meta.messageId,\n reactionId: meta.ackReactionId,\n });\n } catch (e) {\n console.warn(\n \"[eve-lark] ack reaction cleanup failed:\",\n e instanceof Error ? e.message : e,\n );\n }\n }\n\n /**\n * Cascade-deliver a reply to the user. Tries (in order):\n *\n * post mode (default — native chat size + markdown):\n * 1. sendPost (msg_type: \"post\", renders at native size)\n * 2. sendText (post POST rejected)\n *\n * streaming mode (live card patches during the turn):\n * 1. streaming finalize (patches existing card OR creates one)\n * 2. sendCard (finalize failed)\n * 3. sendText (card POST rejected)\n *\n * static mode (one-shot card):\n * 1. sendCard (single card with the full text)\n * 2. sendText (card POST rejected)\n *\n * Each failure logs; we never throw out of here.\n */\n async function deliverReply(sessionId: string, info: ResolvedSessionInfo, text: string): Promise<void> {\n if (options.replyMode === \"post\") {\n try {\n await client.sendPost({\n chatId: info.chatId,\n content: text,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendPost (sessionId=${sessionId})`);\n return;\n } catch (postErr) {\n console.warn(\n `[eve-lark] sendPost failed; falling back to plain text (sessionId=${sessionId}):`,\n postErr instanceof Error ? postErr.message : postErr,\n );\n // Fall through to sendText.\n }\n // post-specific fallback (skip the card cascade below).\n try {\n await client.sendText({\n chatId: info.chatId,\n content: text,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendText fallback (sessionId=${sessionId})`);\n } catch (textErr) {\n console.error(\n `[eve-lark] sendText fallback ALSO failed; the user will not see this reply (sessionId=${sessionId}):`,\n textErr instanceof Error ? textErr.message : textErr,\n );\n }\n return;\n }\n\n if (options.replyMode === \"streaming\") {\n const ctrl = controllers.get(sessionId) ?? getController(sessionId, info);\n try {\n await ctrl.finalize(text);\n console.log(`[eve-lark] delivered via streaming finalize (sessionId=${sessionId})`);\n return;\n } catch (e) {\n console.warn(\n `[eve-lark] streaming finalize failed; falling back to fresh card (sessionId=${sessionId}):`,\n e instanceof Error ? e.message : e,\n );\n }\n }\n\n try {\n await client.sendCard({\n chatId: info.chatId,\n card: buildTextCard(text),\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendCard (sessionId=${sessionId})`);\n return;\n } catch (cardErr) {\n console.warn(\n `[eve-lark] sendCard failed; falling back to plain text (sessionId=${sessionId}):`,\n cardErr instanceof Error ? cardErr.message : cardErr,\n );\n }\n\n try {\n await client.sendText({\n chatId: info.chatId,\n content: text,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n console.log(`[eve-lark] delivered via sendText fallback (sessionId=${sessionId})`);\n } catch (textErr) {\n console.error(\n `[eve-lark] sendText fallback ALSO failed; the user will not see this reply (sessionId=${sessionId}):`,\n textErr instanceof Error ? textErr.message : textErr,\n );\n }\n }\n\n // Lazy sweep: drop controllers whose session hasn't been touched in\n // STALE_SESSION_MS. Guards against the case where eve crashes mid-turn.\n // Also drops stale pending input requests.\n let lastSweepAt = 0;\n function maybeSweep(): void {\n const now = Date.now();\n if (now - lastSweepAt < SWEEP_INTERVAL_MS) return;\n lastSweepAt = now;\n const cutoff = now - STALE_SESSION_MS;\n for (const [id, meta] of sessionMeta) {\n if (meta.touchedAt < cutoff) {\n controllers.delete(id);\n sessionMeta.delete(id);\n }\n }\n for (const [reqId, p] of pendingInputsByRequestId) {\n if (p.touchedAt < cutoff) {\n pendingInputsByRequestId.delete(reqId);\n const tokenKey = chatTokenKey(p.chatId, p.rootId, p.parentId);\n if (pendingInputsByChatToken.get(tokenKey)?.requestId === reqId) {\n pendingInputsByChatToken.delete(tokenKey);\n }\n }\n }\n }\n\n /** Compose the chat-scoped key used for freeform interception. */\n function chatTokenKey(chatId: string, rootId?: string, parentId?: string): string {\n return `${chatId}:${parentId ?? rootId ?? \"_\"}`;\n }\n\n function dropPendingInput(p: PendingInput): void {\n pendingInputsByRequestId.delete(p.requestId);\n const tokenKey = chatTokenKey(p.chatId, p.rootId, p.parentId);\n if (pendingInputsByChatToken.get(tokenKey)?.requestId === p.requestId) {\n pendingInputsByChatToken.delete(tokenKey);\n }\n }\n\n const webhookHandler = async (\n req: Request,\n helpers: RouteHandlerArgs[\"send\"] extends never ? never : RouteHandlerArgs,\n ): Promise<Response> => {\n maybeSweep();\n\n // 0) Body size cap — refuse gigantic bodies before allocating.\n const contentLength = Number(req.headers.get(\"content-length\") ?? \"0\");\n if (Number.isFinite(contentLength) && contentLength > MAX_BODY_BYTES) {\n return new Response(\"request body too large\", { status: 413 });\n }\n const rawBody = Buffer.from(await req.arrayBuffer());\n if (rawBody.byteLength > MAX_BODY_BYTES) {\n return new Response(\"request body too large\", { status: 413 });\n }\n\n // 1) Skew check (only enforced when a real timestamp header is present)\n const tsHeader = req.headers.get(\"x-lark-request-timestamp\") ?? \"\";\n const ts = Number(tsHeader);\n if (\n tsHeader &&\n Number.isFinite(ts) &&\n ts > 0 &&\n Math.abs(Date.now() / 1000 - ts) > options.signatureSkewMs / 1000\n ) {\n return new Response(\"request timestamp out of skew window\", { status: 408 });\n }\n\n // 2) Signature verify + AES decrypt when encryptKey configured\n let workingBody: Buffer = rawBody;\n if (options.encryptKey) {\n const nonce = req.headers.get(\"x-lark-request-nonce\") ?? \"\";\n const sigHeader = req.headers.get(\"x-lark-signature\");\n if (!sigHeader) return new Response(\"missing signature\", { status: 401 });\n const ok = verifySignature({\n timestamp: tsHeader,\n nonce,\n encryptKey: options.encryptKey,\n rawBody,\n signatureHeader: sigHeader,\n });\n if (!ok) return new Response(\"bad signature\", { status: 401 });\n\n try {\n const envelope = JSON.parse(rawBody.toString(\"utf8\")) as LarkEncryptedBody;\n if (envelope.encrypt) {\n workingBody = decryptPayload(envelope.encrypt, options.encryptKey) as Buffer;\n }\n } catch {\n return new Response(\"decrypt failed\", { status: 400 });\n }\n }\n\n // 3) Parse body\n let body: LarkEventBody;\n try {\n body = JSON.parse(workingBody.toString(\"utf8\")) as LarkEventBody;\n } catch {\n return new Response(\"invalid json\", { status: 400 });\n }\n\n // 4) url_verification short-circuit\n if (body.type === \"url_verification\") {\n return Response.json({ challenge: body.challenge ?? \"\" });\n }\n\n // 5) Schema check\n if (body.schema !== \"2.0\") {\n return ackOk();\n }\n\n // 6) Verification-token check\n if (body.header?.token !== options.verificationToken) {\n return new Response(\"verification token mismatch\", { status: 401 });\n }\n\n // 7) Dedup. Card-action callbacks dedup on open_message_id (one click\n // per render — re-clicks after patch are no-ops because we replaced\n // the buttons with text).\n const evtMsg = body.event as { message?: { message_id?: string }; open_message_id?: string } | undefined;\n const dedupKey = body.header?.event_id ?? evtMsg?.message?.message_id ?? evtMsg?.open_message_id;\n if (dedupKey) {\n if (dedup.has(dedupKey)) return ackOk();\n dedup.set(dedupKey);\n }\n\n // 8) Event-type dispatch. Two event types we own:\n // - \"im.message.receive_v1\" — normal inbound message (existing flow).\n // - \"card.action.trigger\" — user clicked a button on an ask_card.\n // Anything else is ack-and-skip.\n const eventType = body.header?.event_type;\n if (eventType === \"card.action.trigger\") {\n return handleCardAction(body.event as LarkCardActionTriggerEvent, helpers);\n }\n if (eventType !== \"im.message.receive_v1\") {\n return ackOk();\n }\n if (!body.event) return ackOk();\n\n // 9) Parse — body.event is now narrowed to the message-event branch.\n const parsed = parseInbound(body.event as LarkInboundEvent, options.botOpenId);\n\n // 10) Self-message suppression\n if (parsed.senderType === \"app\") {\n return ackOk();\n }\n\n // 11) Skip unsupported message types\n if (parsed.text === \"\" && parsed.files.length === 0) {\n return ackOk();\n }\n\n // 11.5) Freeform-input interception. If this chat has a pending\n // ask_question awaiting a freeform text reply, treat this inbound\n // message as the answer instead of starting a new turn. eve resumes\n // the parked session with the user's text as InputResponse.text.\n const tokenKey = chatTokenKey(parsed.chatId, parsed.rootId ?? undefined, parsed.parentId ?? undefined);\n const pending = pendingInputsByChatToken.get(tokenKey);\n if (pending && pending.awaitingFreeform && parsed.text.length > 0) {\n const resp: LarkInputResponse = { requestId: pending.requestId, text: parsed.text };\n const resumeAuth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: parsed.senderOpenId,\n attributes: {\n chatId: parsed.chatId,\n rootMessageId: parsed.rootId,\n messageId: parsed.messageId,\n chatType: parsed.chatType,\n },\n };\n const resumeToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);\n try {\n await helpers.send(\n { inputResponses: [resp] } as never,\n { auth: resumeAuth as never, continuationToken: resumeToken },\n );\n // Update the card (if any) to show the typed answer.\n if (pending.cardMessageId) {\n try {\n await client.patchCard({\n messageId: pending.cardMessageId,\n card: buildAskAnsweredCard(pending.request, { kind: \"freeform\", text: parsed.text }),\n });\n } catch (e) {\n console.warn(\"[eve-lark] patchCard after freeform answer failed:\", e instanceof Error ? e.message : e);\n }\n }\n } catch (e) {\n console.error(\"[eve-lark] freeform input-response send failed:\", e instanceof Error ? e.message : e);\n } finally {\n dropPendingInput(pending);\n }\n return ackOk();\n }\n\n // 12) Build session inputs\n const userContent = buildUserContent(parsed.text, parsed.files, options, parsed.messageId);\n const continuationToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);\n const auth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: parsed.senderOpenId,\n attributes: {\n chatId: parsed.chatId,\n rootMessageId: parsed.rootId,\n messageId: parsed.messageId,\n chatType: parsed.chatType,\n },\n };\n\n // 13) Start/resume session.\n const session = await helpers.send(userContent as never, {\n auth: auth as never,\n continuationToken,\n });\n\n // 14) Remember chat metadata keyed by session.id so terminal handlers\n // can look up where to deliver replies and which reaction to clean up.\n sessionMeta.set(session.id, {\n chatId: parsed.chatId,\n rootId: parsed.rootId ?? undefined,\n parentId: parsed.parentId ?? undefined,\n messageId: parsed.messageId,\n touchedAt: Date.now(),\n });\n\n // 15) Ack reaction — fire-and-forget. Stash the resulting reaction id\n // so terminal handlers can remove it once the reply has been delivered.\n const emoji = pickAckEmoji(options.ackReaction);\n if (emoji) {\n const sessionId = session.id;\n helpers.waitUntil(\n client\n .addReaction({ messageId: parsed.messageId, emojiType: emoji })\n .then(({ reactionId }) => {\n const m = sessionMeta.get(sessionId);\n if (m) m.ackReactionId = reactionId;\n })\n .catch((e) => {\n console.warn(\n \"[eve-lark] ack reaction failed:\",\n e instanceof Error ? e.message : e,\n );\n }),\n );\n }\n\n return ackOk();\n };\n\n /**\n * Handle a `card.action.trigger` callback from Feishu. The user clicked a\n * button on an ask_card we rendered earlier. Extract the requestId +\n * optionId from `action.value`, resume the parked eve session with an\n * InputResponse, and patch the card to show the selection.\n *\n * Buttons we created carry `{__eveLarkAsk, requestId, optionId}` in their\n * `value`. Buttons from any other source are ignored.\n */\n async function handleCardAction(\n evt: LarkCardActionTriggerEvent,\n helpers: RouteHandlerArgs,\n ): Promise<Response> {\n const value = evt.action?.value;\n if (!value || value[ASK_BUTTON_VALUE_MARKER] !== true) {\n // Not our button — ignore (could be from another integration).\n return ackOk();\n }\n const requestId = typeof value.requestId === \"string\" ? value.requestId : \"\";\n const optionId = typeof value.optionId === \"string\" ? value.optionId : \"\";\n if (!requestId) return ackOk();\n\n const pending = pendingInputsByRequestId.get(requestId);\n if (!pending) {\n console.warn(`[eve-lark] card action for unknown requestId=${requestId} (already answered or expired)`);\n return ackOk();\n }\n\n // Build the InputResponse and resume the parked session.\n const resp: LarkInputResponse = { requestId, optionId: optionId || undefined };\n const resumeToken = larkContinuationToken(pending.chatId, pending.parentId ?? pending.rootId ?? null);\n const resumeAuth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: evt.open_id,\n attributes: {\n chatId: pending.chatId,\n rootMessageId: pending.rootId,\n messageId: evt.open_message_id,\n chatType: pending.request.display === \"confirmation\" ? \"p2p\" : \"group\",\n },\n };\n\n try {\n await helpers.send(\n { inputResponses: [resp] } as never,\n { auth: resumeAuth as never, continuationToken: resumeToken },\n );\n console.log(`[eve-lark] ask answered via button click requestId=${requestId} optionId=${optionId}`);\n } catch (e) {\n console.error(\n `[eve-lark] ask input-response send failed (requestId=${requestId}):`,\n e instanceof Error ? e.message : e,\n );\n }\n\n // Patch the card to show the selection (disable buttons by replacing\n // the card body with prompt + \"✓ <label>\"). Best-effort.\n const selectedOpt = pending.request.options?.find((o) => o.id === optionId);\n if (pending.cardMessageId && selectedOpt) {\n try {\n await client.patchCard({\n messageId: pending.cardMessageId,\n card: buildAskAnsweredCard(pending.request, { kind: \"option\", label: selectedOpt.label }),\n });\n } catch (e) {\n console.warn(\"[eve-lark] patchCard after ask-answer failed:\", e instanceof Error ? e.message : e);\n }\n }\n\n dropPendingInput(pending);\n return ackOk();\n }\n\n // Channel event handlers — declared as a standalone const so tests can\n // invoke them directly (eve's defineChannel hides events on the returned\n // Channel object). createLarkChannel attaches them as `__testEvents` on\n // the returned channel for that purpose; production code never reads it.\n const channelEvents = {\n // Streaming delta — patch the card.\n \"message.appended\"(data: unknown, _channel: unknown, ctx: { session: { id: string } }) {\n if (options.replyMode !== \"streaming\") return;\n const sessionId = ctx.session.id;\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) return;\n const d = data as { messageDelta?: string };\n if (typeof d.messageDelta !== \"string\") return;\n const ctrl = getController(sessionId, info);\n ctrl.appendDelta(d.messageDelta);\n },\n\n // eve's ask_question (and similar HITL tools) fire this event with a\n // list of input requests. Each request becomes a Feishu card with\n // buttons (one per option) plus optional freeform hint.\n async \"input.requested\"(data: unknown, _channel: unknown, ctx: { session: { id: string } }) {\n const sessionId = ctx.session.id;\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) {\n console.warn(`[eve-lark] input.requested: no session info (sessionId=${sessionId})`);\n return;\n }\n const d = data as { requests?: readonly LarkInputRequest[] };\n const requests = d.requests ?? [];\n if (requests.length === 0) return;\n\n console.log(\n `[eve-lark] input.requested sessionId=${sessionId} chatId=${info.chatId} count=${requests.length}`,\n );\n\n for (const req of requests) {\n const card = buildAskCard(req);\n let cardMessageId: string | undefined;\n try {\n const res = await client.sendCard({\n chatId: info.chatId,\n card,\n rootId: info.rootId,\n parentId: info.parentId,\n });\n cardMessageId = res.messageId;\n } catch (e) {\n console.error(\n `[eve-lark] ask card send failed (requestId=${req.requestId}):`,\n e instanceof Error ? e.message : e,\n );\n continue;\n }\n\n const pending: PendingInput = {\n requestId: req.requestId,\n sessionId,\n chatId: info.chatId,\n rootId: info.rootId,\n parentId: info.parentId,\n cardMessageId,\n request: req,\n createdAt: Date.now(),\n touchedAt: Date.now(),\n awaitingFreeform: req.allowFreeform === true,\n };\n pendingInputsByRequestId.set(req.requestId, pending);\n if (pending.awaitingFreeform) {\n const tokenKey = chatTokenKey(info.chatId, info.rootId, info.parentId);\n pendingInputsByChatToken.set(tokenKey, pending);\n }\n }\n },\n\n // Terminal — deliver the final reply, then clean up the ack reaction.\n async \"message.completed\"(data: unknown, _channel: unknown, ctx: { session: { id: string } }) {\n const sessionId = ctx.session.id;\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) {\n console.warn(`[eve-lark] message.completed: no session info, cannot deliver (sessionId=${sessionId})`);\n return;\n }\n const d = data as { message?: string | null };\n const rawText = typeof d.message === \"string\" ? d.message : \"\";\n console.log(\n `[eve-lark] message.completed sessionId=${sessionId} chatId=${info.chatId} msgLen=${rawText.length}`,\n );\n const text = rawText.length > 0 ? rawText : EMPTY_REPLY_TEXT;\n\n try {\n await deliverReply(sessionId, info, text);\n } finally {\n await cleanupAckReaction(sessionId);\n dropController(sessionId);\n }\n },\n\n async \"turn.failed\"(data: unknown, _channel: unknown, ctx: { session?: { id: string } } | null) {\n const sessionId = ctx?.session?.id;\n if (!sessionId) {\n console.warn(\"[eve-lark] turn.failed: no sessionId on ctx\");\n return;\n }\n const info = sessionInfoFromCtx(ctx as never);\n if (!info) {\n console.warn(`[eve-lark] turn.failed: no session info (sessionId=${sessionId})`);\n return;\n }\n const userText = formatFailureMessage(data, \"turn failed\", { sentence: \"turn\" });\n const errorId = extractErrorId((data as { details?: unknown } | null)?.details);\n console.warn(\n `[eve-lark] turn.failed sessionId=${sessionId} chatId=${info.chatId}` +\n ` err=\"${userText.slice(0, 200)}\"` +\n (errorId ? ` errorId=${errorId}` : \"\"),\n );\n\n const ctrl = controllers.get(sessionId);\n if (ctrl) {\n try {\n await ctrl.abort(userText);\n console.log(`[eve-lark] error shown via streaming abort (sessionId=${sessionId})`);\n } catch (e) {\n console.warn(\n `[eve-lark] turn.failed: streaming abort failed, will deliver fresh error (sessionId=${sessionId}):`,\n e instanceof Error ? e.message : e,\n );\n try {\n await deliverReply(sessionId, info, userText);\n } catch {\n // unreachable\n }\n }\n } else {\n try {\n await deliverReply(sessionId, info, userText);\n } catch {\n // unreachable\n }\n }\n\n await cleanupAckReaction(sessionId);\n dropController(sessionId);\n },\n\n async \"session.failed\"(data: unknown) {\n const userText = formatFailureMessage(data, \"session failed\", { sentence: \"session\" });\n const errorId = extractErrorId((data as { details?: unknown } | null)?.details);\n console.error(\n `[eve-lark] session.failed: ${userText}` + (errorId ? ` (errorId=${errorId})` : \"\"),\n );\n },\n };\n\n const channel = defineChannel({\n routes: [POST(options.webhookPath, webhookHandler as never)],\n\n fetchFile: async (url: string) => {\n if (!url.startsWith(options.baseUrl)) return null;\n const m = url.match(/\\/messages\\/([^/]+)\\/resources\\/([^?]+)\\?type=(image|file)/);\n if (!m || !m[1] || !m[2] || !m[3]) return null;\n return client.downloadResource({\n messageId: m[1],\n fileKey: m[2],\n type: m[3] as \"image\" | \"file\",\n });\n },\n\n events: channelEvents,\n });\n\n // Test seam: expose the events map so tests can drive handlers directly\n // (eve's defineChannel hides them on the returned Channel). Production\n // code MUST NOT read this — it's typing-loose and not part of the API.\n (channel as Channel<undefined, Record<string, unknown>, Record<string, unknown>> & {\n __testEvents?: typeof channelEvents;\n }).__testEvents = channelEvents;\n\n return channel;\n}\n","/**\n * Typed error hierarchy for eve-lark.\n *\n * All errors extend a common base so consumers can `instanceof LarkChannelError`\n * to catch anything thrown by the channel.\n */\n\nexport class LarkChannelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\nexport class LarkConfigError extends LarkChannelError {}\n\nexport class LarkSignatureError extends LarkChannelError {}\n\nexport class LarkDecryptError extends LarkChannelError {}\n\nexport interface LarkApiErrorBody {\n code?: number | undefined;\n msg?: string | undefined;\n}\n\nexport class LarkApiError extends LarkChannelError {\n readonly code: number | undefined;\n readonly body: LarkApiErrorBody | undefined;\n readonly status: number | undefined;\n\n constructor(\n message: string,\n opts?: {\n code?: number | undefined;\n body?: LarkApiErrorBody | undefined;\n status?: number | undefined;\n cause?: unknown;\n },\n ) {\n super(message, { cause: opts?.cause });\n this.code = opts?.code;\n this.body = opts?.body;\n this.status = opts?.status;\n }\n}\n","import { LarkApiError, type LarkApiErrorBody } from \"./errors.js\";\nimport type { LarkCard, ResolvedLarkOptions } from \"./types.js\";\n\ninterface TokenState {\n value: string;\n expiresAt: number;\n}\n\nconst TOKEN_INVALID_CODES = new Set<number>([99991663, 99991664, 99991661]);\n\ninterface RequestResult {\n status: number;\n body: unknown;\n retryAfter: number | null;\n}\n\nexport class LarkClient {\n private readonly options: ResolvedLarkOptions;\n private token: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(options: ResolvedLarkOptions) {\n this.options = options;\n }\n\n async getTenantAccessToken(): Promise<string> {\n if (\n this.token &&\n Date.now() + this.options.tokenRefreshBufferMs < this.token.expiresAt\n ) {\n return this.token.value;\n }\n if (this.refreshPromise) return this.refreshPromise;\n this.refreshPromise = this.#refresh();\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n async #refresh(): Promise<string> {\n const body = {\n app_id: this.options.appId,\n app_secret: this.options.appSecret,\n };\n const res = await this.options.fetch(\n `${this.options.baseUrl}/open-apis/auth/v3/tenant_access_token/internal`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n },\n );\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: token refresh failed (HTTP ${res.status})`,\n { status: res.status },\n );\n }\n const json = (await res.json()) as { code?: number; tenant_access_token?: string; expire?: number; msg?: string };\n if (json.code !== 0 || !json.tenant_access_token) {\n throw new LarkApiError(\n `eve-lark: token refresh returned code=${json.code ?? \"?\"} msg=${json.msg ?? \"?\"}`,\n { body: json, code: json.code },\n );\n }\n const expireSec = typeof json.expire === \"number\" ? json.expire : 7200;\n this.token = {\n value: json.tenant_access_token,\n expiresAt: Date.now() + expireSec * 1000,\n };\n return this.token.value;\n }\n\n async sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify({ text: args.content });\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"text\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify(args.card);\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"interactive\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async sendPost(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n // `msg_type: \"post\"` renders at native chat-message size with full\n // markdown support (bold, links, code, <font> color tags) via the\n // inner `{tag: \"md\"}` element. Cards render noticeably smaller because\n // Feishu treats them as \"structured content\"; post does not.\n //\n // The content schema is post > zh_cn > content > lines > inline nodes.\n // We put the whole reply in one md node — the md tag honors embedded\n // newlines, so multi-paragraph replies work as a single text string.\n const post = {\n zh_cn: {\n content: [[{ tag: \"md\", text: args.content }]],\n },\n };\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"post\",\n content: JSON.stringify(post),\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async #sendMessage(body: Record<string, unknown>): Promise<{ messageId: string }> {\n const payload = Object.fromEntries(\n Object.entries(body).filter(([, v]) => v !== undefined),\n );\n const json = await this.#request(\"POST\", \"/open-apis/im/v1/messages?receive_id_type=chat_id\", payload);\n const messageId = (json as { data?: { message_id?: string } }).data?.message_id;\n if (!messageId) {\n throw new LarkApiError(\"eve-lark: send missing message_id in response\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { messageId };\n }\n\n async patchCard(args: { messageId: string; card: LarkCard }): Promise<void> {\n await this.#request(\n \"PATCH\",\n `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}`,\n { content: JSON.stringify(args.card) },\n );\n }\n\n async downloadResource(args: {\n messageId: string;\n fileKey: string;\n type: \"image\" | \"file\";\n }): Promise<Buffer> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/resources/${encodeURIComponent(args.fileKey)}?type=${args.type}`;\n const token = await this.getTenantAccessToken();\n const res = await this.options.fetch(`${this.options.baseUrl}${path}`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: downloadResource HTTP ${res.status}`,\n { status: res.status },\n );\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n async addReaction(args: {\n messageId: string;\n emojiType: string;\n }): Promise<{ reactionId: string }> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions`;\n const json = (await this.#request(\"POST\", path, {\n reaction_type: { emoji_type: args.emojiType },\n })) as { data?: { reaction_id?: string } };\n const reactionId = json.data?.reaction_id;\n if (!reactionId) {\n throw new LarkApiError(\"eve-lark: addReaction missing reaction_id\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { reactionId };\n }\n\n async removeReaction(args: { messageId: string; reactionId: string }): Promise<void> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions/${encodeURIComponent(args.reactionId)}`;\n await this.#request(\"DELETE\", path, undefined);\n }\n\n /**\n * Central request wrapper with auth, retry, and Feishu error decoding.\n *\n * Retry policy:\n * - 429 (rate limit): always retry with `Retry-After` backoff. Safe —\n * server rejected the request before processing.\n * - 5xx: retry ONLY for idempotent methods (GET / PATCH / DELETE). POST\n * is NOT retried on 5xx because Feishu's POST /messages and POST\n * /reactions are non-idempotent — the server may have created the\n * resource before returning the error, and retrying would silently\n * double-send.\n * - 401 / token-invalid code: refresh and retry once.\n * - Other 4xx: throw LarkApiError with the Feishu code/msg.\n */\n async #request(method: string, path: string, body: unknown): Promise<unknown> {\n const url = `${this.options.baseUrl}${path}`;\n let token = await this.getTenantAccessToken();\n let tokenRefreshed = false;\n const methodNorm = method.toUpperCase();\n const retryableMethod = methodNorm !== \"POST\";\n\n for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {\n const res = await this.options.fetch(url, {\n method,\n headers: {\n authorization: `Bearer ${token}`,\n \"content-type\": \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n\n const result = await this.#consumeResponse(res);\n const status = result.status;\n\n if (status >= 200 && status < 300) {\n const jsonBody = result.body as { code?: number; msg?: string };\n if (jsonBody && typeof jsonBody.code === \"number\" && jsonBody.code !== 0) {\n if (TOKEN_INVALID_CODES.has(jsonBody.code) && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed code=${jsonBody.code} msg=${jsonBody.msg ?? \"?\"}`,\n { code: jsonBody.code, body: jsonBody as LarkApiErrorBody, status },\n );\n }\n return result.body;\n }\n\n if (status === 401 && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n\n const isRateLimited = status === 429;\n const isServerErr = status >= 500 && status < 600;\n const retryable = isRateLimited || (isServerErr && retryableMethod);\n if (retryable && attempt < this.options.maxRetries) {\n const delayMs = this.#computeBackoff(status, result.retryAfter, attempt);\n await sleep(delayMs);\n continue;\n }\n\n const bodyObj = result.body as LarkApiErrorBody | undefined;\n const code = bodyObj?.code;\n const msg = bodyObj?.msg;\n const detail = msg ? ` code=${code ?? \"?\"} msg=${msg}` : \"\";\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed HTTP ${status}${detail}`,\n { status, body: bodyObj, code },\n );\n }\n throw new LarkApiError(`eve-lark: ${method} ${path} exhausted retries`);\n }\n\n async #consumeResponse(res: Response): Promise<RequestResult> {\n const retryAfterRaw = res.headers.get(\"retry-after\");\n const retryAfter = retryAfterRaw ? parseRetryAfter(retryAfterRaw) : null;\n const text = await res.text();\n if (!text) {\n return { status: res.status, body: undefined, retryAfter };\n }\n try {\n return { status: res.status, body: JSON.parse(text), retryAfter };\n } catch {\n return { status: res.status, body: { raw: text }, retryAfter };\n }\n }\n\n #computeBackoff(status: number, retryAfter: number | null, attempt: number): number {\n if (status === 429 && retryAfter !== null) {\n return Math.min(retryAfter * 1000, 10_000);\n }\n const base = 300 * Math.pow(2, attempt);\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\n return Math.max(0, Math.round(base + jitter));\n }\n}\n\nfunction parseRetryAfter(raw: string): number | null {\n const sec = Number(raw);\n if (Number.isFinite(sec) && sec >= 0) return sec;\n const date = Date.parse(raw);\n if (Number.isFinite(date)) return Math.max(0, (date - Date.now()) / 1000);\n return null;\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * In-process deduplication for Feishu webhook events.\n *\n * Feishu retries delivery on non-2xx responses and during brief outage windows,\n * so consumers must idempotently ack events they've already seen. We key by\n * `header.event_id` (or `message.message_id` as a fallback) and remember each\n * key for the TTL window.\n *\n * Backed by an insertion-ordered Map so FIFO eviction is O(1) at the front.\n * Lazy sweep on every insert prevents unbounded growth of expired entries;\n * no `setInterval` so this is safe in serverless.\n */\nexport class DedupMap {\n private readonly entries = new Map<string, number>();\n private readonly ttlMs: number;\n private readonly maxEntries: number;\n private insertsSinceSweep = 0;\n\n constructor(ttlMs: number, maxEntries: number) {\n this.ttlMs = ttlMs;\n this.maxEntries = maxEntries;\n }\n\n has(key: string): boolean {\n const at = this.entries.get(key);\n if (at === undefined) return false;\n if (Date.now() - at > this.ttlMs) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n set(key: string): void {\n this.maybeSweep();\n // Refresh timestamp by re-inserting at the tail of insertion order.\n this.entries.delete(key);\n this.entries.set(key, Date.now());\n\n while (this.entries.size > this.maxEntries) {\n const oldestKey = this.entries.keys().next().value;\n if (oldestKey === undefined) break;\n this.entries.delete(oldestKey);\n }\n }\n\n /**\n * Walk the insertion-ordered map from the front and drop expired entries.\n * Stops at the first non-expired entry since events arrive roughly in time\n * order. Called on every set, so cost is amortized.\n */\n private maybeSweep(): void {\n this.insertsSinceSweep += 1;\n if (this.insertsSinceSweep < 64 && this.entries.size < this.maxEntries) {\n return;\n }\n this.insertsSinceSweep = 0;\n const now = Date.now();\n for (const [key, at] of this.entries) {\n if (now - at <= this.ttlMs) break;\n this.entries.delete(key);\n }\n }\n}\n","import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport { LarkDecryptError } from \"./errors.js\";\n\n/**\n * Verify an `X-Lark-Signature` header against the raw webhook body.\n *\n * Feishu computes: `sha256(timestamp + nonce + encrypt_key + body)` and ships\n * the hex digest (optionally prefixed with `sha256=`) in `X-Lark-Signature`.\n * We concatenate the string parts first, then the raw bytes of the body, to\n * avoid a UTF-8 round-trip on the request body.\n *\n * Constant-time compare. Returns false on length mismatch instead of throwing.\n */\nexport function verifySignature(opts: {\n timestamp: string;\n nonce: string;\n encryptKey: string;\n rawBody: Buffer;\n signatureHeader: string;\n}): boolean {\n const expected = opts.signatureHeader.replace(/^sha256=/, \"\");\n const computed = createHash(\"sha256\")\n .update(opts.timestamp + opts.nonce + opts.encryptKey)\n .update(opts.rawBody)\n .digest(\"hex\");\n const a = Buffer.from(computed, \"hex\");\n const b = Buffer.from(expected, \"hex\");\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/**\n * Decrypt the `encrypt` field from a Feishu webhook body.\n *\n * Layout:\n * key = SHA256(encrypt_key) // 32 bytes → AES-256\n * buf = base64decode(encrypt_field)\n * iv = buf[0:16]\n * ct = buf[16:] // AES-256-CBC ciphertext\n * plaintext = AES_256_CBC_decrypt(key, iv, ct) // PKCS#7 unpadded\n *\n * Returns the raw plaintext bytes. The caller is expected to JSON.parse them.\n */\nexport function decryptPayload(encryptB64: string, encryptKey: string): Buffer {\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const buf = Buffer.from(encryptB64, \"base64\");\n\n if (buf.length < 32) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext too short (${buf.length} bytes; need >= 32 for IV + one block)`,\n );\n }\n if ((buf.length - 16) % 16 !== 0) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext length ${buf.length} is not 16 + N*16`,\n );\n }\n\n const iv = buf.subarray(0, 16);\n const ct = buf.subarray(16);\n const dec = createDecipheriv(\"aes-256-cbc\", key, iv);\n\n try {\n return Buffer.concat([dec.update(ct), dec.final()]);\n } catch (e) {\n throw new LarkDecryptError(\"eve-lark: AES decrypt failed (bad padding or wrong key)\", {\n cause: e,\n });\n }\n}\n","import type {\n LarkInboundEvent,\n LarkInboundFile,\n LarkInboundResult,\n LarkMention,\n LarkRawMention,\n} from \"./types.js\";\n\nconst MIME_BY_EXT: Record<string, string> = {\n pdf: \"application/pdf\",\n zip: \"application/zip\",\n gz: \"application/gzip\",\n tar: \"application/x-tar\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n csv: \"text/csv\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n json: \"application/json\",\n xml: \"application/xml\",\n html: \"text/html\",\n htm: \"text/html\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n mp4: \"video/mp4\",\n mov: \"video/quicktime\",\n};\n\nfunction mimeFromExt(filename: string | undefined): string {\n if (!filename) return \"application/octet-stream\";\n const dot = filename.lastIndexOf(\".\");\n if (dot < 0) return \"application/octet-stream\";\n return MIME_BY_EXT[filename.slice(dot + 1).toLowerCase()] ?? \"application/octet-stream\";\n}\n\nfunction mentionFromRaw(m: LarkRawMention, botOpenId: string | undefined): LarkMention {\n const isOpenIdOfBot =\n !!botOpenId && !!m.id.open_id && m.id.open_id === botOpenId;\n const isAll = !!m.id.open_id && m.id.open_id === \"all\";\n return {\n key: m.key,\n id: {\n openId: m.id.open_id,\n userId: m.id.user_id,\n unionId: m.id.union_id,\n },\n name: m.name,\n idType: m.id_type ?? \"open_id\",\n isOpenIdOfBot,\n isAll,\n };\n}\n\nfunction stripBotMentions(text: string, mentions: LarkMention[]): string {\n // Feishu ships mentions as opaque placeholders (e.g. \"@_user_1\") in the text\n // body alongside a structured mentions array. Rewrite them to something the\n // model can read:\n // - the bot itself: dropped (the model already knows it's being addressed)\n // - @all: replaced with a literal \"@all\" token\n // - other users: replaced with \"@<display name>\"\n let out = text;\n for (const m of mentions) {\n if (!m.key) continue;\n if (m.isOpenIdOfBot) {\n out = out.split(m.key).join(\"\");\n } else if (m.isAll) {\n out = out.split(m.key).join(\"@all\");\n } else {\n out = out.split(m.key).join(`@${m.name}`);\n }\n }\n return out.replace(/\\s+/g, \" \").trim();\n}\n\ninterface ParsedContent {\n text: string;\n files: LarkInboundFile[];\n}\n\nfunction parseContent(messageType: string, rawContent: string): ParsedContent {\n if (!rawContent) return { text: \"\", files: [] };\n let content: Record<string, unknown>;\n try {\n content = JSON.parse(rawContent) as Record<string, unknown>;\n } catch {\n return { text: \"\", files: [] };\n }\n\n switch (messageType) {\n case \"text\": {\n const text = typeof content.text === \"string\" ? content.text : \"\";\n return { text, files: [] };\n }\n case \"image\": {\n const imageKey = typeof content.image_key === \"string\" ? content.image_key : \"\";\n if (!imageKey) return { text: \"\", files: [] };\n return {\n text: \"\",\n files: [{ fileKey: imageKey, mediaType: \"image/png\", kind: \"image\" }],\n };\n }\n case \"file\": {\n const fileKey = typeof content.file_key === \"string\" ? content.file_key : \"\";\n if (!fileKey) return { text: \"\", files: [] };\n const fileName = typeof content.file_name === \"string\" ? content.file_name : undefined;\n return {\n text: \"\",\n files: [{ fileKey, mediaType: mimeFromExt(fileName), kind: \"file\" }],\n };\n }\n case \"post\": {\n const locale = (content.zh_cn ?? content.en_us ?? content.ja_jp ?? null) as\n | { content?: unknown[][] }\n | null;\n if (!locale?.content) return { text: \"\", files: [] };\n const text = locale.content\n .flatMap((line) =>\n (line ?? [])\n .filter((node): node is { tag: string; text?: unknown } => {\n if (typeof node !== \"object\" || node === null) return false;\n const tag = (node as { tag?: unknown }).tag;\n const text = (node as { text?: unknown }).text;\n return tag === \"text\" && typeof text === \"string\";\n })\n .map((node) => node.text as string),\n )\n .join(\" \");\n return { text, files: [] };\n }\n default:\n // audio, media, sticker, share_chat, share_user, interactive — not in v1 scope.\n return { text: \"\", files: [] };\n }\n}\n\nexport function parseInbound(\n event: LarkInboundEvent,\n botOpenId?: string,\n): LarkInboundResult {\n const messageType = event.message.message_type;\n const parsed = parseContent(messageType, event.message.content);\n const rawMentions = event.message.mentions ?? [];\n const mentions = rawMentions.map((m) => mentionFromRaw(m, botOpenId));\n\n const senderOpenId =\n event.sender.sender_id.open_id ??\n event.sender.sender_id.user_id ??\n event.sender.sender_id.union_id ??\n \"\";\n\n const text =\n messageType === \"text\"\n ? stripBotMentions(parsed.text, mentions)\n : parsed.text;\n\n const chatType = event.chat_type === \"group\" ? \"group\" : \"p2p\";\n const senderType = event.sender.sender_type === \"app\" ? \"app\" : \"user\";\n\n return {\n text,\n files: parsed.files,\n chatId: event.message.chat_id,\n rootId: event.message.root_id ?? null,\n parentId: event.message.parent_id ?? null,\n messageId: event.message.message_id,\n senderOpenId,\n senderType,\n chatType,\n mentions,\n };\n}\n","import type { LarkCard, LarkCardButton, LarkInputRequest } from \"./types.js\";\n\nconst BASE_CONFIG = {\n wide_screen_mode: true,\n update_multi: true,\n} as const;\n\n/**\n * Build a simple single-shot card with the given markdown text. Renders via\n * `div` + `lark_md` so the font size is close to a native chat message\n * (the bare `markdown` element renders noticeably smaller).\n */\nexport function buildTextCard(text: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"div\", text: { tag: \"lark_md\", content: text } }],\n };\n}\n\n/**\n * Build a streaming card with an optional status prefix and an answer buffer.\n */\nexport function buildStreamingCard(opts: { buffer: string; status?: string | undefined }): LarkCard {\n const lines: string[] = [];\n if (opts.status) {\n lines.push(`<font color='grey'>${opts.status}</font>`);\n }\n lines.push(opts.buffer.length > 0 ? opts.buffer : \"_…_\");\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"div\", text: { tag: \"lark_md\", content: lines.join(\"\\n\\n\") } }],\n };\n}\n\n/**\n * Build an error card displayed when a turn fails.\n */\nexport function buildErrorCard(message: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [\n { tag: \"div\", text: { tag: \"lark_md\", content: `<font color='red'>⚠ ${message}</font>` } },\n ],\n };\n}\n\n/** Marker placed in every ask-card button value so the card-action handler\n * can recognise our own callbacks (and ignore card actions from other\n * sources on the same message). */\nexport const ASK_BUTTON_VALUE_MARKER = \"__eveLarkAsk\";\n\n/**\n * Build a Feishu interactive card that surfaces an eve `ask_question`\n * input request. Each selectable option becomes a button whose `value`\n * carries `{__eveLarkAsk, requestId, optionId}` — when the user clicks,\n * Feishu's `card.action.trigger` callback returns that JSON to us.\n *\n * For `allowFreeform: true` with no options, renders just the prompt\n * (the user replies with a normal chat message, which the channel\n * intercepts as the freeform response).\n *\n * For `allowFreeform: true` WITH options, renders buttons AND a footer\n * hint that the user can also type a reply.\n */\nexport function buildAskCard(request: LarkInputRequest): LarkCard {\n const elements: LarkCard[\"elements\"] = [\n { tag: \"div\", text: { tag: \"lark_md\", content: request.prompt } },\n ];\n\n if (request.options && request.options.length > 0) {\n const buttons: LarkCardButton[] = request.options.map((opt) => ({\n tag: \"button\",\n text: { tag: \"plain_text\", content: opt.label },\n type: opt.style ?? \"default\",\n value: {\n [ASK_BUTTON_VALUE_MARKER]: true,\n requestId: request.requestId,\n optionId: opt.id,\n },\n ...(opt.description\n ? { confirm: { title: { tag: \"plain_text\", content: opt.label }, text: { tag: \"plain_text\", content: opt.description } } }\n : {}),\n }));\n elements.push({ tag: \"action\", actions: buttons });\n }\n\n if (request.allowFreeform) {\n const hint =\n request.options && request.options.length > 0\n ? \"_…or reply to this chat with your own answer_\"\n : \"_Reply to this chat with your answer_\";\n elements.push({ tag: \"div\", text: { tag: \"lark_md\", content: hint } });\n }\n\n return { config: { ...BASE_CONFIG }, elements };\n}\n\n/**\n * Build the \"post-click\" card body that replaces the ask-card once the user\n * has answered. Disables further clicks by removing the action row and\n * appending a \"✓ <selected label>\" line.\n */\nexport function buildAskAnsweredCard(\n request: LarkInputRequest,\n selected: { kind: \"option\"; label: string } | { kind: \"freeform\"; text: string },\n): LarkCard {\n const elements: LarkCard[\"elements\"] = [\n { tag: \"div\", text: { tag: \"lark_md\", content: request.prompt } },\n ];\n const summary =\n selected.kind === \"option\"\n ? `<font color='green'>✓ ${escapeMarkdown(selected.label)}</font>`\n : `<font color='green'>✓ ${escapeMarkdown(selected.text)}</font>`;\n elements.push({ tag: \"div\", text: { tag: \"lark_md\", content: summary } });\n return { config: { ...BASE_CONFIG }, elements };\n}\n\n/** Escape characters that have special meaning in lark_md so user-controlled\n * strings can't inject formatting. */\nfunction escapeMarkdown(s: string): string {\n return s.replace(/[*_`~\\[\\]]/g, (m) => `\\\\${m}`);\n}\n\n","import { buildErrorCard, buildStreamingCard, buildTextCard } from \"./card.js\";\nimport type { LarkCard } from \"./types.js\";\n\ntype State = \"idle\" | \"creating\" | \"streaming\" | \"completed\" | \"aborted\";\n\ninterface ControllerDeps {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n patchIntervalMs: number;\n createThresholdMs: number;\n}\n\ninterface LarkClientLike {\n sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n patchCard(args: { messageId: string; card: LarkCard }): Promise<void>;\n sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n}\n\n/**\n * Streaming interactive-card state machine.\n *\n * idle ──first delta──> creating ──sendCard ok──> streaming ──finalize──> completed\n * │\n * └──sendCard fail──> aborted (flag; static fallback on message.completed)\n *\n * The card is created lazily after `createThresholdMs` of the first delta, so\n * short turns can short-circuit straight to `finalize` (which sends the card\n * with the full answer in one shot). Once streaming, patches are throttled to\n * `patchIntervalMs`.\n *\n * If `sendCard` fails on creation, the controller flips to `fallbackToText`\n * and `finalize`/`ensureFinalized` deliver via `sendText` instead.\n */\nexport class StreamingCardController {\n private readonly deps: ControllerDeps;\n private readonly client: LarkClientLike;\n\n private state: State = \"idle\";\n private buffer = \"\";\n private status: string | undefined;\n private messageId: string | undefined;\n private fallbackToText = false;\n\n private createTimer: ReturnType<typeof setTimeout> | null = null;\n private patchTimer: ReturnType<typeof setTimeout> | null = null;\n private patchInFlight: Promise<void> | null = null;\n private patchScheduled = false;\n private lastPatchAt = 0;\n\n constructor(client: LarkClientLike, deps: ControllerDeps) {\n this.client = client;\n this.deps = deps;\n }\n\n appendDelta(text: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.buffer += text;\n if (this.state === \"idle\") {\n this.scheduleCreate();\n } else if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n setStatus(status: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.status = status;\n if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n async finalize(fullText: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n this.cancelPatchTimer();\n this.buffer = fullText;\n\n if (this.fallbackToText) {\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n return;\n }\n\n if (this.messageId === undefined) {\n // Never managed to create a card. Send one with the full text in a\n // single shot so the user still gets a card reply.\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildTextCard(fullText),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"completed\";\n } catch {\n // Last-resort fallback: plain text.\n this.fallbackToText = true;\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n }\n return;\n }\n\n // Card already exists; flush the final state.\n if (this.patchInFlight) {\n try {\n await this.patchInFlight;\n } catch {\n // swallow; we'll attempt the final patch below\n }\n }\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildStreamingCard({ buffer: fullText, status: undefined }),\n });\n this.state = \"completed\";\n }\n\n async abort(error: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n this.cancelPatchTimer();\n if (this.messageId === undefined) {\n // No card to patch; mark fallback and let finalize/ensureFinalized\n // deliver a plain-text error if asked.\n this.fallbackToText = true;\n this.state = \"aborted\";\n return;\n }\n try {\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildErrorCard(error),\n });\n } finally {\n this.state = \"aborted\";\n }\n }\n\n async ensureFinalized(): Promise<void> {\n if (this.state !== \"completed\" && this.state !== \"aborted\") {\n await this.finalize(this.buffer);\n }\n }\n\n isStreaming(): boolean {\n return this.state === \"streaming\" || this.state === \"creating\";\n }\n\n isCompleted(): boolean {\n return this.state === \"completed\" || this.state === \"aborted\";\n }\n\n private scheduleCreate(): void {\n if (this.createTimer) return;\n this.state = \"creating\";\n this.createTimer = setTimeout(() => {\n this.createTimer = null;\n void this.doCreate();\n }, this.deps.createThresholdMs);\n }\n\n private cancelCreateTimer(): void {\n if (this.createTimer) {\n clearTimeout(this.createTimer);\n this.createTimer = null;\n }\n }\n\n private async doCreate(): Promise<void> {\n if (this.state !== \"creating\") return;\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildStreamingCard({ buffer: this.buffer, status: this.status }),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"streaming\";\n this.lastPatchAt = Date.now();\n } catch (e) {\n console.warn(\n \"[eve-lark] streaming card create failed; will deliver via plain text on finalize:\",\n e instanceof Error ? e.message : e,\n );\n this.fallbackToText = true;\n this.state = \"streaming\"; // keep accepting deltas; finalize will deliver as text\n }\n }\n\n private schedulePatch(): void {\n if (this.patchScheduled) return;\n this.patchScheduled = true;\n const elapsed = Date.now() - this.lastPatchAt;\n const wait = Math.max(0, this.deps.patchIntervalMs - elapsed);\n this.patchTimer = setTimeout(() => {\n this.patchTimer = null;\n this.patchScheduled = false;\n void this.maybeFlushPatch();\n }, wait);\n }\n\n private cancelPatchTimer(): void {\n if (this.patchTimer) {\n clearTimeout(this.patchTimer);\n this.patchTimer = null;\n }\n this.patchScheduled = false;\n }\n\n private async maybeFlushPatch(): Promise<void> {\n if (this.state !== \"streaming\") return;\n if (this.patchInFlight) return;\n if (this.messageId === undefined) return;\n const card = buildStreamingCard({ buffer: this.buffer, status: this.status });\n this.patchInFlight = this.client\n .patchCard({ messageId: this.messageId, card })\n .catch((e) => {\n // Best-effort: the next delta will retry. Log so operators can see\n // when the card stream is degraded.\n console.warn(\n \"[eve-lark] streaming card patch failed:\",\n e instanceof Error ? e.message : e,\n );\n })\n .finally(() => {\n this.patchInFlight = null;\n this.lastPatchAt = Date.now();\n });\n await this.patchInFlight;\n }\n}\n","import { LarkConfigError } from \"./errors.js\";\nimport type {\n LarkChannelOptions,\n LarkReplyMode,\n LarkTransportMode,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\nconst DEFAULTS = {\n baseUrl: \"https://open.feishu.cn\",\n webhookPath: \"/lark/webhook\",\n // \"post\" renders at native chat-message size with full markdown support\n // (bold, links, code, color tags). Cards render noticeably smaller because\n // Feishu treats them as \"structured content\". The tradeoff: post can't be\n // live-patched during streaming — users who want streaming should set\n // replyMode: \"streaming\" explicitly.\n replyMode: \"post\" as LarkReplyMode,\n streamPatchIntervalMs: 1000,\n streamCreateThresholdMs: 400,\n dedupTtlMs: 30 * 60 * 1000,\n dedupMaxEntries: 5000,\n requestTimeoutMs: 15000,\n maxRetries: 2,\n tokenRefreshBufferMs: 5 * 60 * 1000,\n signatureSkewMs: 5 * 60 * 1000,\n ackReaction: \"Typing\" as string | false,\n mode: \"long-connection\" as LarkTransportMode,\n};\n\nconst ENV_KEYS = {\n appId: \"LARK_APP_ID\",\n appSecret: \"LARK_APP_SECRET\",\n verificationToken: \"LARK_VERIFICATION_TOKEN\",\n encryptKey: \"LARK_ENCRYPT_KEY\",\n baseUrl: \"LARK_BASE_URL\",\n botOpenId: \"LARK_BOT_OPEN_ID\",\n replyMode: \"LARK_REPLY_MODE\",\n mode: \"LARK_MODE\",\n} as const;\n\nexport type ResolveEnv = Record<string, string | undefined>;\n\nfunction defaultEnv(): ResolveEnv {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env as ResolveEnv;\n }\n return {};\n}\n\nfunction pick(input: string | undefined, envValue: string | undefined): string | undefined {\n return input ?? envValue;\n}\n\nexport function resolveOptions(\n options: LarkChannelOptions,\n env: ResolveEnv = defaultEnv(),\n): ResolvedLarkOptions {\n const appId = pick(options.appId, env[ENV_KEYS.appId]);\n const appSecret = pick(options.appSecret, env[ENV_KEYS.appSecret]);\n const verificationToken = pick(\n options.verificationToken,\n env[ENV_KEYS.verificationToken],\n );\n\n if (!appId) {\n throw new LarkConfigError(\n `eve-lark: appId is required (option \\`appId\\` or env \\`${ENV_KEYS.appId}\\`)`,\n );\n }\n if (!appSecret) {\n throw new LarkConfigError(\n `eve-lark: appSecret is required (option \\`appSecret\\` or env \\`${ENV_KEYS.appSecret}\\`)`,\n );\n }\n if (!verificationToken) {\n throw new LarkConfigError(\n `eve-lark: verificationToken is required (option \\`verificationToken\\` or env \\`${ENV_KEYS.verificationToken}\\`)`,\n );\n }\n\n const rawBaseUrl = pick(options.baseUrl, env[ENV_KEYS.baseUrl]) ?? DEFAULTS.baseUrl;\n const baseUrl = rawBaseUrl.replace(/\\/+$/, \"\");\n\n const replyModeEnv = env[ENV_KEYS.replyMode];\n const replyMode: LarkReplyMode =\n options.replyMode ??\n (replyModeEnv === \"post\" || replyModeEnv === \"static\" || replyModeEnv === \"streaming\"\n ? replyModeEnv\n : DEFAULTS.replyMode);\n\n const modeEnv = env[ENV_KEYS.mode];\n const mode: LarkTransportMode =\n options.mode ??\n (modeEnv === \"webhook\" || modeEnv === \"long-connection\" ? modeEnv : DEFAULTS.mode);\n\n return {\n appId,\n appSecret,\n verificationToken,\n encryptKey: pick(options.encryptKey, env[ENV_KEYS.encryptKey]),\n baseUrl,\n botOpenId: pick(options.botOpenId, env[ENV_KEYS.botOpenId]),\n webhookPath: options.webhookPath ?? DEFAULTS.webhookPath,\n replyMode,\n streamPatchIntervalMs: options.streamPatchIntervalMs ?? DEFAULTS.streamPatchIntervalMs,\n streamCreateThresholdMs: options.streamCreateThresholdMs ?? DEFAULTS.streamCreateThresholdMs,\n dedupTtlMs: options.dedupTtlMs ?? DEFAULTS.dedupTtlMs,\n dedupMaxEntries: options.dedupMaxEntries ?? DEFAULTS.dedupMaxEntries,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n tokenRefreshBufferMs: options.tokenRefreshBufferMs ?? DEFAULTS.tokenRefreshBufferMs,\n signatureSkewMs: options.signatureSkewMs ?? DEFAULTS.signatureSkewMs,\n fetch: options.fetch ?? globalThis.fetch,\n ackReaction: options.ackReaction ?? DEFAULTS.ackReaction,\n mode,\n port:\n options.port ??\n (process.env.PORT ? Number(process.env.PORT) : 2000),\n };\n}\n","/**\n * Long-connection transport: when `mode: \"long-connection\"` is set on\n * {@link createLarkChannel} (the default), the channel starts a Feishu\n * `@larksuiteoapi/node-sdk` WSClient as a side effect of construction. Each\n * inbound event is re-encrypted + re-signed and POSTed to the channel's own\n * webhook on localhost, where the standard webhook handler runs (with full\n * access to `send()` etc.). This lets users run the bot against a real Feishu\n * app from `eve dev` alone — no public webhook URL, no second process.\n *\n * The SDK is a hard runtime dependency of this package (declared in\n * `dependencies`), so `pnpm add eve-lark` brings it in automatically. The\n * `import()` below is dynamic only so `mode: \"webhook\"` code paths don't\n * eagerly load the SDK at module import time.\n */\n\nimport {\n createCipheriv,\n createHash,\n randomBytes,\n} from \"node:crypto\";\nimport type { ResolvedLarkOptions } from \"./types.js\";\n\n/** A Feishu v2 envelope we forward to the channel webhook. */\nexport type LarkEvent = {\n schema?: string;\n type?: string;\n challenge?: string;\n token?: string;\n header?: Record<string, unknown>;\n event?: unknown;\n [k: string]: unknown;\n};\n\nexport interface PostEventOptions {\n eveWebhookUrl: string;\n /** When set, the body is AES-encrypted and the request is signed. */\n encryptKey?: string | undefined;\n /** Override for tests. Defaults to globalThis.fetch. */\n fetch?: typeof fetch | undefined;\n}\n\nfunction aesEncrypt(plaintext: Buffer, key: string): Buffer {\n const keyBuf = createHash(\"sha256\").update(key).digest();\n const iv = randomBytes(16);\n const cipher = createCipheriv(\"aes-256-cbc\", keyBuf, iv);\n return Buffer.concat([iv, cipher.update(plaintext), cipher.final()]);\n}\n\nfunction signBody(timestamp: string, nonce: string, body: Buffer, key: string): string {\n return createHash(\"sha256\")\n .update(timestamp + nonce + key)\n .update(body)\n .digest(\"hex\");\n}\n\n/**\n * Forward one Feishu event to the local eve webhook. Re-encrypts and signs\n * when an `encryptKey` is set, so the channel handler exercises its own\n * signature + AES pipeline on every event.\n */\nexport async function postEventToWebhook(\n event: LarkEvent,\n opts: PostEventOptions,\n): Promise<void> {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const plain = Buffer.from(JSON.stringify(event), \"utf8\");\n let body: Buffer;\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n\n if (opts.encryptKey) {\n const encrypted = aesEncrypt(plain, opts.encryptKey).toString(\"base64\");\n body = Buffer.from(JSON.stringify({ encrypt: encrypted }), \"utf8\");\n const ts = Math.floor(Date.now() / 1000).toString();\n const nonce = randomBytes(8).toString(\"hex\");\n const sig = signBody(ts, nonce, body, opts.encryptKey);\n headers[\"x-lark-request-timestamp\"] = ts;\n headers[\"x-lark-request-nonce\"] = nonce;\n headers[\"x-lark-signature\"] = `sha256=${sig}`;\n } else {\n body = plain;\n }\n\n const res = await fetchImpl(opts.eveWebhookUrl, {\n method: \"POST\",\n headers,\n body: new Uint8Array(body.buffer, body.byteOffset, body.byteLength) as never,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `eve-lark: forward to ${opts.eveWebhookUrl} failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : \"\"}`,\n );\n }\n}\n\n/**\n * Post-event wrapper with a single exponential-backoff retry. Catches the\n * common case where `eve dev` is momentarily unavailable (between HMR\n * reloads, mid-restart, GC pause). Without this the event would be dropped\n * on the floor with just a log line — bad UX during dev.\n */\nexport async function postEventToWebhookRetry(\n event: LarkEvent,\n opts: PostEventOptions,\n): Promise<void> {\n try {\n await postEventToWebhook(event, opts);\n } catch (firstErr) {\n await new Promise((r) => setTimeout(r, 300));\n // Regenerate signature: timestamp/nonce moved on by ~300ms, and the\n // skew check at the channel handler would reject a stale signature.\n await postEventToWebhook(event, opts).catch((retryErr) => {\n throw retryErr instanceof Error\n ? retryErr\n : new Error(String(retryErr), { cause: firstErr });\n });\n }\n}\n\n/**\n * The Feishu SDK's EventDispatcher passes handlers a payload that may or may\n * not include the outer envelope. Rebuild a v2-shaped envelope so the channel\n * webhook can parse it the same way it parses a raw Feishu POST.\n */\nexport function rebuildEnvelopeFromSdkEvent(\n eventType: string,\n data: unknown,\n ctx: { appId: string; verificationToken: string },\n): LarkEvent {\n const maybeHeader = (data as { header?: Record<string, unknown> })?.header;\n const header =\n maybeHeader && typeof maybeHeader === \"object\"\n ? maybeHeader\n : {\n event_id: `lc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n event_type: eventType,\n create_time: String(Math.floor(Date.now() / 1000)),\n token: ctx.verificationToken,\n app_id: ctx.appId,\n };\n const maybeEvent = (data as { event?: unknown })?.event;\n return {\n schema: \"2.0\",\n header,\n event: maybeEvent ?? data,\n };\n}\n\nexport interface StartLongConnectionArgs {\n resolved: ResolvedLarkOptions;\n eveWebhookUrl: string;\n /** Override logger. */\n log?: ((msg: string) => void) | undefined;\n logError?: ((msg: string, err?: unknown) => void) | undefined;\n /** Test seam: inject a custom SDK module. */\n sdk?: unknown;\n}\n\n/**\n * Detect whether this process is the `eve start` launcher (NOT the nitro\n * server it spawns). The launcher loads the channel module for build\n * discovery but never serves HTTP; the spawned `.output/server/index.mjs`\n * child is what actually runs the server. Without this check, both\n * processes would start a WSClient and Feishu would deliver every event\n * twice.\n *\n * Discriminator: the launcher's `argv[1]` is the eve CLI binary\n * (`.../eve/bin/eve.js` or `.mjs`/`.cjs`/no-ext shim) and `argv[2]` is\n * `start`. The spawned server child has `argv[1] = .output/server/index.mjs`\n * and `argv[2]` unset. `eve dev` runs nitro in-process (no fork), so we\n * DON'T treat it as a launcher — the WSClient must start there.\n *\n * Override: set `EVE_LARK_FORCE_WS=1` to always start (escape hatch in\n * case the heuristic breaks for some package-manager layout).\n */\nexport function isEveStartLauncher(): boolean {\n if (process.env.EVE_LARK_FORCE_WS === \"1\") return false;\n const arg1 = process.argv[1] ?? \"\";\n const isEveBinary = /[/\\\\]eve[/\\\\]bin[/\\\\]eve(?:\\.[cm]?js)?$/.test(arg1);\n return isEveBinary && process.argv[2] === \"start\";\n}\n\n/**\n * Active connections keyed by `${appId}:${eveWebhookUrl}`.\n *\n * Eve's lifecycle can construct the channel module more than once (e.g.,\n * build-time scan + serve-time import, or HMR reload). Each construction\n * would naively start a fresh WSClient — Feishu then delivers every event\n * to BOTH connections, and the user sees double replies.\n *\n * Guard: if a connection for the same key is already running (or starting),\n * the second call resolves immediately without touching the SDK. On\n * failure, the slot is cleared so a retry can succeed.\n *\n * The map lives on `globalThis` so it survives module reloads (HMR, build\n * then serve) — otherwise a reloaded module instance would have its own\n * fresh map and the guard would silently fail.\n *\n * Single-process, so no lock is needed — `Map.has` + `Map.set` from the\n * same synchronous block is atomic in JS's single-threaded runtime.\n */\nconst GLOBAL_KEY = \"__eveLarkActiveConnections\";\ntype GlobalWithLark = typeof globalThis & {\n [GLOBAL_KEY]?: Map<string, Promise<void>>;\n};\nfunction getActiveConnections(): Map<string, Promise<void>> {\n const g = globalThis as GlobalWithLark;\n if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = new Map();\n return g[GLOBAL_KEY]!;\n}\n\n/** @internal — test-only seam for resetting module state between cases. */\nexport function __resetLongConnectionSingletonsForTests(): void {\n getActiveConnections().clear();\n}\n\n/**\n * Start the Feishu WSClient side effect. Connects via the official SDK,\n * registers a handler that re-signs each event and POSTs it to the local eve\n * webhook. Resolves once the connection is established; the WSClient then\n * runs in the background for the lifetime of the process.\n *\n * Idempotent: a second call with the same `appId` + `eveWebhookUrl` is a\n * no-op (see {@link getActiveConnections}). Different keys (different app,\n * or different webhook URL — e.g. different `--port`) get separate WSClients.\n *\n * @throws if @larksuiteoapi/node-sdk is not installed, or the WSClient\n * fails to establish its first connection.\n */\nexport async function startLongConnection(args: StartLongConnectionArgs): Promise<void> {\n const key = `${args.resolved.appId}:${args.eveWebhookUrl}`;\n const activeConnections = getActiveConnections();\n const existing = activeConnections.get(key);\n if (existing) {\n console.log(\n `[eve-lark] startLongConnection: skip (already running) key=${key} pid=${process.pid}`,\n );\n return;\n }\n console.log(\n `[eve-lark] startLongConnection: start new WSClient key=${key} pid=${process.pid}`,\n );\n\n const promise = (async () => {\n await doStartLongConnection(args);\n })().catch((e) => {\n // On failure, clear the slot so a future call can retry.\n activeConnections.delete(key);\n throw e;\n });\n\n // Synchronous set after the synchronous has-check above — atomic in JS's\n // single-threaded runtime. No race with another concurrent caller.\n activeConnections.set(key, promise);\n await promise;\n}\n\nasync function doStartLongConnection(args: StartLongConnectionArgs): Promise<void> {\n const log = args.log ?? ((m: string) => console.log(`[eve-lark] ${m}`));\n const logError = args.logError ?? ((m: string, e?: unknown) => console.error(`[eve-lark] ${m}`, e ?? \"\"));\n\n const sdk = (args.sdk ?? (await loadLarkSdk())) as LarkSdk;\n\n const dispatcher = new sdk.EventDispatcher({\n verificationToken: args.resolved.verificationToken,\n encryptKey: args.resolved.encryptKey,\n });\n\n dispatcher.register({\n \"im.message.receive_v1\": async (data: unknown) => {\n try {\n const envelope = rebuildEnvelopeFromSdkEvent(\"im.message.receive_v1\", data, {\n appId: args.resolved.appId,\n verificationToken: args.resolved.verificationToken,\n });\n await postEventToWebhookRetry(envelope, {\n eveWebhookUrl: args.eveWebhookUrl,\n encryptKey: args.resolved.encryptKey,\n });\n } catch (e) {\n logError(`forward failed (event dropped)`, e);\n }\n },\n\n // Card-button clicks. Feishu's card.action.trigger fires when a user\n // taps a button on a card we rendered. Forward to the channel webhook\n // — the webhook handler dispatches by event_type and feeds the click\n // back into eve as an InputResponse.\n \"card.action.trigger\": async (data: unknown) => {\n try {\n const envelope = rebuildEnvelopeFromSdkEvent(\"card.action.trigger\", data, {\n appId: args.resolved.appId,\n verificationToken: args.resolved.verificationToken,\n });\n await postEventToWebhookRetry(envelope, {\n eveWebhookUrl: args.eveWebhookUrl,\n encryptKey: args.resolved.encryptKey,\n });\n } catch (e) {\n logError(`card action forward failed (event dropped)`, e);\n }\n },\n });\n\n const domain = args.resolved.baseUrl.includes(\"larksuite.com\")\n ? sdk.Domain.Lark\n : sdk.Domain.Feishu;\n\n const wsClient = new sdk.WSClient({\n appId: args.resolved.appId,\n appSecret: args.resolved.appSecret,\n domain,\n onReady: () => log(`WS connected to Feishu (${args.resolved.baseUrl})`),\n onError: (err: Error) => logError(`WS error`, err),\n onReconnecting: () => log(`WS reconnecting…`),\n onReconnected: () => log(`WS reconnected`),\n autoReconnect: true,\n });\n\n await wsClient.start({ eventDispatcher: dispatcher });\n}\n\n/* eslint-disable @typescript-eslint/consistent-type-imports */\n// The `import()` type query is the canonical way to express \"the runtime\n// namespace of this dynamic import\" without making the SDK a hard dep.\ntype LarkSdk = typeof import(\"@larksuiteoapi/node-sdk\");\n/* eslint-enable @typescript-eslint/consistent-type-imports */\n\nasync function loadLarkSdk(): Promise<LarkSdk> {\n try {\n return await import(\"@larksuiteoapi/node-sdk\");\n } catch {\n throw new Error(\n \"eve-lark: mode:\\\"long-connection\\\" requires @larksuiteoapi/node-sdk. Install it: pnpm add @larksuiteoapi/node-sdk (or npm/yarn equivalent).\",\n );\n }\n}\n","/**\n * Valid Feishu emoji type strings for the message-reactions API.\n *\n * Feishu emoji types are case-sensitive and inconsistent: most are uppercase\n * (`THUMBSUP`, `OK`), but a meaningful subset is CamelCase (`Typing`,\n * `CrossMark`, `EatingFood`, `Drumstick`, …). Passing the wrong case fails\n * with HTTP 400 code=231001 \"reaction type is invalid\" — silent unless the\n * caller is watching logs.\n *\n * Source: openclaw-lark's `VALID_FEISHU_EMOJI_TYPES` (which references the\n * official Feishu emoji doc).\n */\nexport const VALID_FEISHU_EMOJI_TYPES: ReadonlySet<string> = new Set([\n \"OK\", \"THUMBSUP\", \"THANKS\", \"MUSCLE\", \"FINGERHEART\", \"APPLAUSE\", \"FISTBUMP\",\n \"JIAYI\", \"DONE\", \"SMILE\", \"BLUSH\", \"LAUGH\", \"SMIRK\", \"LOL\", \"FACEPALM\",\n \"LOVE\", \"WINK\", \"PROUD\", \"WITTY\", \"SMART\", \"SCOWL\", \"THINKING\", \"SOB\",\n \"CRY\", \"ERROR\", \"NOSEPICK\", \"HAUGHTY\", \"SLAP\", \"SPITBLOOD\", \"TOASTED\",\n \"GLANCE\", \"DULL\", \"INNOCENTSMILE\", \"JOYFUL\", \"WOW\", \"TRICK\", \"YEAH\",\n \"ENOUGH\", \"TEARS\", \"EMBARRASSED\", \"KISS\", \"SMOOCH\", \"DROOL\", \"OBSESSED\",\n \"MONEY\", \"TEASE\", \"SHOWOFF\", \"COMFORT\", \"CLAP\", \"PRAISE\", \"STRIVE\",\n \"XBLUSH\", \"SILENT\", \"WAVE\", \"WHAT\", \"FROWN\", \"SHY\", \"DIZZY\", \"LOOKDOWN\",\n \"CHUCKLE\", \"WAIL\", \"CRAZY\", \"WHIMPER\", \"HUG\", \"BLUBBER\", \"WRONGED\",\n \"HUSKY\", \"SHHH\", \"SMUG\", \"ANGRY\", \"HAMMER\", \"SHOCKED\", \"TERROR\",\n \"PETRIFIED\", \"SKULL\", \"SWEAT\", \"SPEECHLESS\", \"SLEEP\", \"DROWSY\", \"YAWN\",\n \"SICK\", \"PUKE\", \"BETRAYED\", \"HEADSET\", \"EatingFood\", \"MeMeMe\", \"Sigh\",\n \"Typing\", \"SLIGHT\", \"TONGUE\", \"EYESCLOSED\", \"RoarForYou\", \"CALF\", \"BEAR\",\n \"BULL\", \"RAINBOWPUKE\", \"Lemon\", \"ROSE\", \"HEART\", \"PARTY\", \"LIPS\", \"BEER\",\n \"CAKE\", \"GIFT\", \"CUCUMBER\", \"Drumstick\", \"Pepper\", \"CANDIEDHAWS\",\n \"BubbleTea\", \"Coffee\", \"Get\", \"LGTM\", \"OnIt\", \"OneSecond\", \"VRHeadset\",\n \"YouAreTheBest\", \"SALUTE\", \"SHAKE\", \"HIGHFIVE\", \"UPPERLEFT\", \"ThumbsDown\",\n \"Yes\", \"No\", \"OKR\", \"CheckMark\", \"CrossMark\", \"MinusOne\", \"Hundred\",\n \"AWESOMEN\", \"Pin\", \"Alarm\", \"Loudspeaker\", \"Trophy\", \"Fire\", \"BOMB\",\n \"Music\", \"XmasTree\", \"Snowman\", \"XmasHat\", \"FIREWORKS\", \"2022\",\n \"REDPACKET\", \"FORTUNE\", \"LUCK\", \"FIRECRACKER\", \"StickyRiceBalls\",\n \"HEARTBROKEN\", \"POOP\", \"StatusFlashOfInspiration\", \"18X\", \"CLEAVER\",\n \"Soccer\", \"Basketball\", \"GeneralDoNotDisturb\", \"Status_PrivateMessage\",\n \"GeneralInMeetingBusy\", \"StatusReading\", \"StatusInFlight\",\n \"GeneralBusinessTrip\", \"GeneralWorkFromHome\", \"StatusEnjoyLife\",\n \"GeneralTravellingCar\", \"StatusBus\", \"GeneralSun\", \"GeneralMoonRest\",\n \"MoonRabbit\", \"Mooncake\", \"JubilantRabbit\", \"TV\", \"Movie\", \"Pumpkin\",\n \"BeamingFace\", \"Delighted\", \"ColdSweat\", \"FullMoonFace\", \"Partying\",\n \"GoGoGo\", \"ThanksFace\", \"SaluteFace\", \"Shrug\", \"ClownFace\", \"HappyDragon\",\n]);\n\n/** Returns true iff `s` is a valid Feishu emoji type string (case-sensitive). */\nexport function isValidFeishuEmojiType(s: string): boolean {\n return VALID_FEISHU_EMOJI_TYPES.has(s);\n}\n"],"mappings":"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","names":["text","createHash","createHash"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-lark",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Lark/Feishu channel for the eve agent framework",
5
5
  "type": "module",
6
6
  "license": "MIT",