opencode-feishu-plugin 0.2.10 → 0.2.11
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.en.md +29 -5
- package/README.md +29 -5
- package/dist/index.js +106 -8
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,22 +22,29 @@ Connect [OpenCode](https://opencode.ai) to Feishu/Lark: **one Feishu topic = one
|
|
|
22
22
|
| 📊 **Real-time visible** | "Thinking" receipt → live tool-call cards → streaming text updates; footer shows the current model |
|
|
23
23
|
| ⏹ **Controllable** | Every reply card has a "force stop" button; a watchdog auto-interrupts stuck sessions; native queue with `/steer` `/now` to cut in |
|
|
24
24
|
| 📎 **Images / files** | Images and files sent in Feishu are downloaded and attached to the session, so vision/file-capable models can see and read them |
|
|
25
|
+
| 🖱 **Main-window menu** | Two fixed quick buttons above the chat input — "New session" and "Session list" (bot custom menu) — one tap to fire them |
|
|
25
26
|
| 🚫 **No ports** | Full long-connection; no inbound port needed on the server |
|
|
26
27
|
|
|
27
28
|
## 1. Feishu app setup (~3 minutes)
|
|
28
29
|
|
|
29
30
|
1. Open the [Feishu Open Platform](https://open.feishu.cn/app) → **Create an enterprise self-built app**.
|
|
30
31
|
2. **Add app capability → Bot**.
|
|
31
|
-
3. **Permission management
|
|
32
|
+
3. **Permission management → API permissions**: enable these two **required** scopes first:
|
|
32
33
|
- `im:message.p2p_msg:readonly` — read messages users send to the bot in p2p chat
|
|
33
34
|
- `im:message:send_as_bot` — send messages as the app (also used to update cards)
|
|
34
35
|
|
|
35
|
-
To
|
|
36
|
-
- `im:message:readonly` — fetch message resources (required to download images / files)
|
|
36
|
+
**To receive images / files, add one more** (skip if you don't need it):
|
|
37
|
+
- `im:message:readonly` — fetch message resources (**required** to download images / files)
|
|
38
|
+
|
|
39
|
+
> ⚠️ Without `im:message:readonly`: images/files are **not downloaded** — the message still reaches the AI, but with placeholder text ("…download failed…"). After enabling it, **re-create and publish an app version** for it to take effect.
|
|
37
40
|
4. **Events & Callbacks → Event configuration**: subscription method **"Use long connection to receive events"** (do **not** pick Webhook), add event `im.message.receive_v1`.
|
|
38
41
|
5. **Events & Callbacks → Callback configuration**: same long-connection method, add callback `card.action.trigger` (zero permission requirement).
|
|
39
|
-
6. **
|
|
40
|
-
|
|
42
|
+
6. **Bot menu (optional, recommended)**: **App capabilities → Bot → Bot custom menu** — enable the menu, pick the **floating menu** style, and add two items (any name/icon, **action = push event**):
|
|
43
|
+
- "➕ New session" → `event_key` = `new`
|
|
44
|
+
- "📋 Session list" → `event_key` = `sessions`
|
|
45
|
+
Then add the event `application.bot.menu_v6` under **Event configuration** (zero permission requirement). Result: two quick buttons above the chat input that act like sending `/new` / `/sessions`.
|
|
46
|
+
7. **Version management & release**: availability = **only yourself**, create a version and **publish**. ⚠️ Without publishing the app stays in "development" state and the long connection cannot connect — the bot will never respond.
|
|
47
|
+
8. Note down the **App ID** (`cli_…`) and **App Secret**.
|
|
41
48
|
|
|
42
49
|
> **Why no group permission?** This plugin is a "single-user remote control". Without group permission the bot **physically cannot receive group messages** — the single-user boundary is guaranteed by the platform scope layer, not only by code.
|
|
43
50
|
|
|
@@ -104,6 +111,22 @@ The main chat **only manages**; plain text never enters any session.
|
|
|
104
111
|
|
|
105
112
|
 
|
|
106
113
|
|
|
114
|
+
### Bot menu (quick buttons above the input box)
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
After configuring the menu (setup step 6), two quick buttons stay pinned **above the chat input** in the bot window:
|
|
119
|
+
|
|
120
|
+
| Button | Equivalent command | Effect |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `new` | `/new` | Opens the create-session form card |
|
|
123
|
+
| `sessions` | `/sessions` | Opens the session list card |
|
|
124
|
+
|
|
125
|
+
- Menu `event_key`s must be **`new` / `sessions`** (the literal `/new`, `/sessions` also accepted);
|
|
126
|
+
- Clicks arrive over the long connection (`application.bot.menu_v6`, zero permission requirement); the plugin synthesizes an **equivalent command message** — allowlist, dedup and command matrix behave exactly like typed commands; unknown keys are ignored silently;
|
|
127
|
+
- After changing the menu, **re-publish the app version** (per the console: takes effect within ~5 minutes after publishing);
|
|
128
|
+
- Message the bot at least once before using the menu (the plugin remembers the p2p chat from messages; any normal usage satisfies this).
|
|
129
|
+
|
|
107
130
|
### Inside a topic (work)
|
|
108
131
|
|
|
109
132
|
A topic = a session; sending plain text is giving the AI a command.
|
|
@@ -160,6 +183,7 @@ Full config (including `cardMaxTables`, `topicStatus*`, `resumeSummary*`, `keepa
|
|
|
160
183
|
| Symptom | Fix |
|
|
161
184
|
|---|---|
|
|
162
185
|
| No response to messages | ① Is the app **published** and does availability include you? ② Is the subscription **long connection** (not Webhook)? ③ Is `im:message.p2p_msg:readonly` enabled? |
|
|
186
|
+
| Images / files not received (placeholder / "download failed") | Enable `im:message:readonly` (Permission management → API permissions, search "fetch message resources") → **re-create and publish an app version**; check the `feishu.log` "附件下载失败" entry for the exact reason |
|
|
163
187
|
| `feishu.json` changes don't apply | Check the path, then `opencode reload` |
|
|
164
188
|
| Plugin not loaded at all | npm: confirm the package name is in the `plugins` array; directory: confirm `plugins/<name>/index.js` exists |
|
|
165
189
|
| No approval cards | That session wasn't started from Feishu (no mapping); by design the plugin doesn't take it over |
|
package/README.md
CHANGED
|
@@ -22,22 +22,29 @@
|
|
|
22
22
|
| 📊 **实时可见** | 「思考中」回执 → 工具调用实时上卡 → 文本流式更新,页脚显示当前模型 |
|
|
23
23
|
| ⏹ **可控可停** | 每张回复卡带「强制停止」;看门狗自动中断卡死会话;忙时原生排队,`/steer` `/now` 可插队 |
|
|
24
24
|
| 📎 **图片 / 文件** | 飞书里的图片 / 文件自动下载并挂进会话,支持视觉 / 文件的模型直接看图、读文件 |
|
|
25
|
+
| 🖱 **主窗口菜单** | 机器人输入框上固定「新建会话」「会话列表」快捷按钮(机器人自定义菜单),一点即用 |
|
|
25
26
|
| 🚫 **无端口** | 全程长连接,服务器无需开放任何入站端口 |
|
|
26
27
|
|
|
27
28
|
## 一、飞书后台配置(约 3 分钟)
|
|
28
29
|
|
|
29
30
|
1. 打开 [飞书开放平台](https://open.feishu.cn/app) → **创建企业自建应用**。
|
|
30
31
|
2. **添加应用能力 → 机器人**。
|
|
31
|
-
3.
|
|
32
|
+
3. **权限管理 → API 权限**:先开这两个**必开** scope:
|
|
32
33
|
- `im:message.p2p_msg:readonly` —— 读取用户发给机器人的单聊消息
|
|
33
34
|
- `im:message:send_as_bot` —— 以应用身份发消息(也用于更新卡片)
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
- `im:message:readonly` —— 获取消息中的资源文件(图片 /
|
|
36
|
+
**接收图片 / 文件需要再加开一个**(不需要该功能可跳过):
|
|
37
|
+
- `im:message:readonly` —— 获取消息中的资源文件(图片 / 文件下载的**必要条件**)
|
|
38
|
+
|
|
39
|
+
> ⚠️ 未开通 `im:message:readonly` 时:图片/文件**不会被下载**,消息照常送达 AI,但只带占位文本("…下载失败:…")。开通后要**重新创建版本并发布**才生效。
|
|
37
40
|
4. **事件与回调 → 事件配置**:订阅方式选**「使用长连接接收事件」**(不要选 Webhook),添加事件 `im.message.receive_v1`。
|
|
38
41
|
5. **事件与回调 → 回调配置**:订阅方式同样选**长连接**,添加回调 `card.action.trigger`(零权限要求)。
|
|
39
|
-
6.
|
|
40
|
-
|
|
42
|
+
6. **机器人菜单(可选,推荐)**:**应用能力 → 机器人 → 机器人自定义菜单** 开启菜单,展示形式选**悬浮菜单**,添加两个菜单项(名称/图标随意,**响应动作=推送事件**):
|
|
43
|
+
- 「➕ 新建会话」→ 事件 Key = `new`
|
|
44
|
+
- 「📋 会话列表」→ 事件 Key = `sessions`
|
|
45
|
+
再到 **事件配置** 添加事件 `application.bot.menu_v6`(零权限要求)。效果:机器人输入框上多两个快捷按钮,点一下等于发 `/new` / `/sessions`。
|
|
46
|
+
7. **版本管理与发布**:可用范围 = **仅本人**,创建版本并**发布**。⚠️ 不发布就是开发态,长连接连不上,机器人不会有任何反应。
|
|
47
|
+
8. 记下 **App ID**(`cli_…`)与 **App Secret**。
|
|
41
48
|
|
|
42
49
|
> **为什么不申请群权限?** 本插件是"一个人的遥控台"。不申请群权限,机器人**物理上收不到群消息**,单人边界由平台 scope 层保证,而不是只靠代码判断。
|
|
43
50
|
|
|
@@ -104,6 +111,22 @@ tail -f ~/.config/opencode/plugins/feishu.log # 应看到「飞书长连接已
|
|
|
104
111
|
|
|
105
112
|
 
|
|
106
113
|
|
|
114
|
+
### 机器人菜单(输入框快捷按钮)
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
按「一、飞书后台配置」第 6 步配好菜单后,机器人聊天窗口的**输入框上方**会常驻两个快捷按钮:
|
|
119
|
+
|
|
120
|
+
| 按钮 | 等价命令 | 效果 |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `new` | `/new` | 弹出建会话表单卡 |
|
|
123
|
+
| `sessions` | `/sessions` | 弹出会话列表卡 |
|
|
124
|
+
|
|
125
|
+
- 菜单项的事件 Key 需配置为 **`new` / `sessions`**(也兼容直接把 `/new`、`/sessions` 填成 Key);
|
|
126
|
+
- 点击事件走长连接(`application.bot.menu_v6`,零权限要求),插件把它合成一条**等价命令消息**处理——白名单、去重、命令矩阵与手输完全一致;未知 Key 静默忽略;
|
|
127
|
+
- 菜单配置修改后需**重新发布版本**(官方说明:发版成功后约 5 分钟内生效);
|
|
128
|
+
- 首次使用菜单前,先给机器人发过至少一条消息(插件据消息记住单聊会话;正常使用必然满足)。
|
|
129
|
+
|
|
107
130
|
### 话题内(干活)
|
|
108
131
|
|
|
109
132
|
一个话题 = 一个会话,发普通文本就是给 AI 下指令。
|
|
@@ -160,6 +183,7 @@ agent 反问时表单会变成飞书卡片,**点按钮或在话题里直接发
|
|
|
160
183
|
| 现象 | 处理 |
|
|
161
184
|
|---|---|
|
|
162
185
|
| 发消息没反应 | ① 应用是否**已发布**、可用范围是否勾了你;② 订阅是否选了**长连接**(不是 Webhook);③ 是否开通 `im:message.p2p_msg:readonly` |
|
|
186
|
+
| 图片 / 文件收不到(只显示占位 / “下载失败”) | 开通 `im:message:readonly`(权限管理 → API 权限,搜“获取消息中的资源文件”)→ **重新创建版本并发布**;具体失败原因看 `feishu.log` 的「附件下载失败」 |
|
|
163
187
|
| 改了 `feishu.json` 不生效 | 确认路径,然后 `opencode reload` |
|
|
164
188
|
| 插件完全没被加载 | npm 方式确认包名在 `plugins` 数组;目录方式确认 `plugins/<名>/index.js` 存在 |
|
|
165
189
|
| 审批卡收不到 | 该会话不是从飞书发起的(无映射),插件按设计不接管 |
|
package/dist/index.js
CHANGED
|
@@ -144767,6 +144767,17 @@ function parseIncomingMessage(data) {
|
|
|
144767
144767
|
...str(message.parent_id) ? { parentId: str(message.parent_id) } : {}
|
|
144768
144768
|
};
|
|
144769
144769
|
}
|
|
144770
|
+
function parseBotMenuEvent(data) {
|
|
144771
|
+
if (!isRecord2(data)) return void 0;
|
|
144772
|
+
const header = isRecord2(data.header) ? data.header : {};
|
|
144773
|
+
const event = isRecord2(data.event) ? data.event : data;
|
|
144774
|
+
const operator = isRecord2(event.operator) ? event.operator : {};
|
|
144775
|
+
const operatorId = isRecord2(operator.operator_id) ? operator.operator_id : {};
|
|
144776
|
+
const operatorOpenId = str(operatorId.open_id);
|
|
144777
|
+
const eventKey = str(event.event_key);
|
|
144778
|
+
if (!operatorOpenId || !eventKey) return void 0;
|
|
144779
|
+
return { eventId: str(header.event_id) || str(data.event_id), eventKey, operatorOpenId };
|
|
144780
|
+
}
|
|
144770
144781
|
function parseCardAction(data) {
|
|
144771
144782
|
if (!isRecord2(data)) return void 0;
|
|
144772
144783
|
const context3 = isRecord2(data.context) ? data.context : {};
|
|
@@ -144826,7 +144837,17 @@ function describeCardActionEvent(data) {
|
|
|
144826
144837
|
// src/feishu/gateway.ts
|
|
144827
144838
|
function startGateway(options2) {
|
|
144828
144839
|
const { log: log2 } = options2;
|
|
144829
|
-
const
|
|
144840
|
+
const larkLogger = (tag2) => ({
|
|
144841
|
+
error: (...msg) => log2.error(tag2, { msg: safeArgs(msg) }),
|
|
144842
|
+
warn: (...msg) => log2.warn(tag2, { msg: safeArgs(msg) }),
|
|
144843
|
+
info: (...msg) => log2.debug(tag2, { msg: safeArgs(msg) }),
|
|
144844
|
+
debug: (...msg) => log2.debug(tag2, { msg: safeArgs(msg) }),
|
|
144845
|
+
trace: (...msg) => log2.debug(tag2, { msg: safeArgs(msg) })
|
|
144846
|
+
});
|
|
144847
|
+
const dispatcher = new EventDispatcher({
|
|
144848
|
+
loggerLevel: options2.logLevel === "debug" ? LoggerLevel.debug : LoggerLevel.info,
|
|
144849
|
+
logger: larkLogger("lark.dispatcher")
|
|
144850
|
+
}).register({
|
|
144830
144851
|
"im.message.receive_v1": (data) => {
|
|
144831
144852
|
try {
|
|
144832
144853
|
const message = parseIncomingMessage(data);
|
|
@@ -144858,6 +144879,27 @@ function startGateway(options2) {
|
|
|
144858
144879
|
log2.error("\u5361\u7247\u56DE\u8C03\u5904\u7406\u5F02\u5E38", { error: errorMessage(err2) });
|
|
144859
144880
|
return { toast: { type: "error", content: "\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" } };
|
|
144860
144881
|
}
|
|
144882
|
+
},
|
|
144883
|
+
"application.bot.menu_v6": (data) => {
|
|
144884
|
+
try {
|
|
144885
|
+
const click = parseBotMenuEvent(data);
|
|
144886
|
+
if (!click) {
|
|
144887
|
+
log2.debug("\u83DC\u5355\u4E8B\u4EF6\u89E3\u6790\u5931\u8D25", {
|
|
144888
|
+
shape: typeof data === "object" && data !== null ? Object.keys(data).slice(0, 20) : typeof data
|
|
144889
|
+
});
|
|
144890
|
+
return;
|
|
144891
|
+
}
|
|
144892
|
+
log2.debug("\u6536\u5230\u673A\u5668\u4EBA\u83DC\u5355\u70B9\u51FB", {
|
|
144893
|
+
eventKey: click.eventKey,
|
|
144894
|
+
operator: maskId(click.operatorOpenId)
|
|
144895
|
+
});
|
|
144896
|
+
if (!options2.onBotMenu) return;
|
|
144897
|
+
void Promise.resolve(options2.onBotMenu(click)).catch((err2) => {
|
|
144898
|
+
log2.error("\u83DC\u5355\u70B9\u51FB\u5904\u7406\u5931\u8D25", { eventKey: click.eventKey, error: errorMessage(err2) });
|
|
144899
|
+
});
|
|
144900
|
+
} catch (err2) {
|
|
144901
|
+
log2.error("\u83DC\u5355 handler \u5F02\u5E38", { error: errorMessage(err2) });
|
|
144902
|
+
}
|
|
144861
144903
|
}
|
|
144862
144904
|
});
|
|
144863
144905
|
const loggerLevel = options2.logLevel === "debug" ? LoggerLevel.debug : LoggerLevel.warn;
|
|
@@ -144866,13 +144908,7 @@ function startGateway(options2) {
|
|
|
144866
144908
|
appSecret: options2.appSecret,
|
|
144867
144909
|
domain: options2.domain === "lark" ? Domain.Lark : Domain.Feishu,
|
|
144868
144910
|
loggerLevel,
|
|
144869
|
-
logger:
|
|
144870
|
-
error: (...msg) => log2.error("lark.ws", { msg: safeArgs(msg) }),
|
|
144871
|
-
warn: (...msg) => log2.warn("lark.ws", { msg: safeArgs(msg) }),
|
|
144872
|
-
info: (...msg) => log2.debug("lark.ws", { msg: safeArgs(msg) }),
|
|
144873
|
-
debug: (...msg) => log2.debug("lark.ws", { msg: safeArgs(msg) }),
|
|
144874
|
-
trace: (...msg) => log2.debug("lark.ws", { msg: safeArgs(msg) })
|
|
144875
|
-
}
|
|
144911
|
+
logger: larkLogger("lark.ws")
|
|
144876
144912
|
});
|
|
144877
144913
|
void wsClient.start({ eventDispatcher: dispatcher }).catch((err2) => {
|
|
144878
144914
|
log2.error("\u98DE\u4E66\u957F\u8FDE\u63A5\u542F\u52A8\u5931\u8D25", { error: errorMessage(err2) });
|
|
@@ -151893,6 +151929,7 @@ async function start(ctx, config, log2, logSink) {
|
|
|
151893
151929
|
log2.debug("\u5FFD\u7565\u975E\u767D\u540D\u5355\u7528\u6237", { sender: maskId(message.senderOpenId) });
|
|
151894
151930
|
return;
|
|
151895
151931
|
}
|
|
151932
|
+
void rememberMenuChat(message.senderOpenId, message.chatId);
|
|
151896
151933
|
if (!message.text) return;
|
|
151897
151934
|
if (!await dedup.claim(message.messageId)) {
|
|
151898
151935
|
log2.debug("\u5FFD\u7565\u91CD\u590D\u6D88\u606F", { messageId: message.messageId });
|
|
@@ -151955,6 +151992,66 @@ async function start(ctx, config, log2, logSink) {
|
|
|
151955
151992
|
log2.info("\u8BDD\u9898\u65B0\u5EFA opencode \u4F1A\u8BDD", { sessionID: created.id, threadId: message.threadId, chatId: message.chatId });
|
|
151956
151993
|
await runInSession(message, created.id, message.messageId);
|
|
151957
151994
|
}
|
|
151995
|
+
const MENU_COMMANDS = {
|
|
151996
|
+
new: { cmd: "/new", label: "\u65B0\u5EFA\u4F1A\u8BDD" },
|
|
151997
|
+
sessions: { cmd: "/sessions", label: "\u4F1A\u8BDD\u5217\u8868" },
|
|
151998
|
+
// 容忍把命令原文直接当 event_key 配置。
|
|
151999
|
+
"/new": { cmd: "/new", label: "\u65B0\u5EFA\u4F1A\u8BDD" },
|
|
152000
|
+
"/sessions": { cmd: "/sessions", label: "\u4F1A\u8BDD\u5217\u8868" }
|
|
152001
|
+
};
|
|
152002
|
+
const menuChatCache = /* @__PURE__ */ new Map();
|
|
152003
|
+
const menuChatKey = (openId) => `feishu:v2:menu-chat:${openId}`;
|
|
152004
|
+
async function rememberMenuChat(openId, chatId) {
|
|
152005
|
+
if (!openId || !chatId || menuChatCache.get(openId) === chatId) return;
|
|
152006
|
+
menuChatCache.set(openId, chatId);
|
|
152007
|
+
try {
|
|
152008
|
+
await ctx.storage.set(menuChatKey(openId), chatId);
|
|
152009
|
+
} catch (err2) {
|
|
152010
|
+
log2.debug("\u83DC\u5355 chatId \u6301\u4E45\u5316\u5931\u8D25", { error: errorMessage(err2) });
|
|
152011
|
+
}
|
|
152012
|
+
}
|
|
152013
|
+
async function resolveMenuChat(openId) {
|
|
152014
|
+
const cached3 = menuChatCache.get(openId);
|
|
152015
|
+
if (cached3) return cached3;
|
|
152016
|
+
try {
|
|
152017
|
+
const stored = await ctx.storage.get(menuChatKey(openId));
|
|
152018
|
+
if (typeof stored === "string" && stored) {
|
|
152019
|
+
menuChatCache.set(openId, stored);
|
|
152020
|
+
return stored;
|
|
152021
|
+
}
|
|
152022
|
+
} catch {
|
|
152023
|
+
}
|
|
152024
|
+
return void 0;
|
|
152025
|
+
}
|
|
152026
|
+
async function handleBotMenu(click) {
|
|
152027
|
+
if (!await owner.admit(click.operatorOpenId)) {
|
|
152028
|
+
log2.debug("\u5FFD\u7565\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u83DC\u5355\u70B9\u51FB", { operator: maskId(click.operatorOpenId) });
|
|
152029
|
+
return;
|
|
152030
|
+
}
|
|
152031
|
+
const entry = MENU_COMMANDS[click.eventKey.trim()];
|
|
152032
|
+
if (!entry) {
|
|
152033
|
+
log2.debug("\u672A\u77E5\u83DC\u5355 event_key\uFF0C\u5FFD\u7565", { eventKey: click.eventKey });
|
|
152034
|
+
return;
|
|
152035
|
+
}
|
|
152036
|
+
const chatId = await resolveMenuChat(click.operatorOpenId);
|
|
152037
|
+
if (!chatId) {
|
|
152038
|
+
log2.warn("\u83DC\u5355\u70B9\u51FB\u7F3A\u5C11\u5355\u804A\u4E0A\u4E0B\u6587\uFF1A\u8BF7\u5148\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u6761\u6D88\u606F\u540E\u518D\u4F7F\u7528\u83DC\u5355", {
|
|
152039
|
+
eventKey: click.eventKey
|
|
152040
|
+
});
|
|
152041
|
+
return;
|
|
152042
|
+
}
|
|
152043
|
+
const synthetic = {
|
|
152044
|
+
eventId: click.eventId,
|
|
152045
|
+
messageId: `menu_${click.eventId || Date.now()}`,
|
|
152046
|
+
chatId,
|
|
152047
|
+
chatType: "p2p",
|
|
152048
|
+
messageType: "text",
|
|
152049
|
+
text: entry.cmd,
|
|
152050
|
+
senderOpenId: click.operatorOpenId
|
|
152051
|
+
};
|
|
152052
|
+
log2.info("\u673A\u5668\u4EBA\u83DC\u5355\u70B9\u51FB \u2192 \u6267\u884C\u547D\u4EE4", { eventKey: click.eventKey, cmd: entry.cmd, label: entry.label });
|
|
152053
|
+
await handleMessage(synthetic);
|
|
152054
|
+
}
|
|
151958
152055
|
async function runInActiveSession(message) {
|
|
151959
152056
|
let active = await sessionMap.getActive(message.chatId);
|
|
151960
152057
|
if (!active) {
|
|
@@ -152023,6 +152120,7 @@ ${downloadedAttachmentPrompt(message.attachment, outcome)}`;
|
|
|
152023
152120
|
log: log2,
|
|
152024
152121
|
logLevel: config.logLevel,
|
|
152025
152122
|
onMessage: (message) => handleMessage(message),
|
|
152123
|
+
onBotMenu: (click) => handleBotMenu(click),
|
|
152026
152124
|
onCardAction: (action) => routeCardAction(action, {
|
|
152027
152125
|
log: log2,
|
|
152028
152126
|
handleForm: (a) => formRelay.handleCardAction(a),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-feishu-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Feishu/Lark plugin for OpenCode V2 — single-user topics as sessions, in-card permission approvals, streaming cards. Minimal permissions, no public endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|