lark-remote 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.en.md +5 -1
- package/README.md +10 -2
- package/dist/bridge/approval-coordinator.d.ts +131 -0
- package/dist/bridge/approval-coordinator.js +409 -0
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +138 -5
- package/dist/bridge/index.js +569 -65
- package/dist/bridge/queue-manager.d.ts +11 -1
- package/dist/bridge/queue-manager.js +20 -11
- package/dist/card/approval-render.d.ts +16 -0
- package/dist/card/approval-render.js +363 -0
- package/dist/card/bash-card-session.d.ts +2 -24
- package/dist/card/bash-card-session.js +6 -73
- package/dist/card/bash-renderer.js +2 -3
- package/dist/card/card-budget.js +29 -75
- package/dist/card/card-session.d.ts +48 -0
- package/dist/card/card-session.js +86 -0
- package/dist/card/card-shared.d.ts +13 -10
- package/dist/card/card-shared.js +27 -24
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.js +140 -202
- package/dist/card/run-state.d.ts +38 -3
- package/dist/card/run-state.js +221 -11
- package/dist/card/text-truncate.d.ts +13 -12
- package/dist/card/text-truncate.js +13 -63
- package/dist/common/fs.d.ts +8 -0
- package/dist/common/fs.js +17 -0
- package/dist/common/guards.d.ts +0 -20
- package/dist/common/guards.js +0 -43
- package/dist/common/sleep.d.ts +2 -0
- package/dist/common/sleep.js +5 -0
- package/dist/common/truncate.d.ts +39 -0
- package/dist/common/truncate.js +92 -0
- package/dist/config/codex-config.d.ts +1 -1
- package/dist/config/codex-config.js +1 -1
- package/dist/config/dir.js +1 -1
- package/dist/config/index.d.ts +116 -9
- package/dist/config/index.js +122 -29
- package/dist/connector/index.d.ts +74 -2
- package/dist/connector/index.js +119 -1
- package/dist/index.js +139 -99
- package/dist/instance-lock.js +4 -12
- package/dist/order/alias-resolve.d.ts +13 -0
- package/dist/order/alias-resolve.js +22 -0
- package/dist/order/alias-store.d.ts +38 -0
- package/dist/order/alias-store.js +106 -0
- package/dist/persistence/atomic-write.d.ts +8 -2
- package/dist/persistence/atomic-write.js +23 -7
- package/dist/restart.js +2 -1
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +25 -1
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +33 -5
- package/dist/router/config/common/model-patch.d.ts +22 -0
- package/dist/router/config/common/model-patch.js +26 -0
- package/dist/router/config/common/render.js +9 -4
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +25 -1
- package/dist/router/config/kimi.d.ts +4 -1
- package/dist/router/config/kimi.js +27 -0
- package/dist/router/config/opencode.js +18 -5
- package/dist/router/config/pi.js +4 -5
- package/dist/router/config/types.d.ts +12 -3
- package/dist/router/index.d.ts +77 -17
- package/dist/router/index.js +455 -549
- package/dist/router/utils.d.ts +4 -0
- package/dist/router/utils.js +9 -2
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +31 -0
- package/dist/runner/claude/runner.d.ts +47 -10
- package/dist/runner/claude/runner.js +117 -46
- package/dist/runner/claude/session.d.ts +166 -0
- package/dist/runner/claude/session.js +749 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +519 -0
- package/dist/runner/codex/app-server/protocol-types.js +56 -0
- package/dist/runner/codex/app-server/runner.d.ts +104 -0
- package/dist/runner/codex/app-server/runner.js +377 -0
- package/dist/runner/codex/app-server/translator.d.ts +89 -0
- package/dist/runner/codex/app-server/translator.js +716 -0
- package/dist/runner/codex/index.d.ts +1 -1
- package/dist/runner/codex/index.js +1 -1
- package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
- package/dist/runner/common/acp/protocol-helpers.js +21 -0
- package/dist/runner/common/acp/protocol-types.d.ts +238 -0
- package/dist/runner/common/acp/protocol-types.js +75 -0
- package/dist/runner/common/connection-based-runner.d.ts +111 -0
- package/dist/runner/common/connection-based-runner.js +261 -0
- package/dist/runner/common/jsonrpc/client.d.ts +89 -0
- package/dist/runner/common/jsonrpc/client.js +243 -0
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
- package/dist/runner/common/jsonrpc/transport.js +230 -0
- package/dist/runner/common/jsonrpc/types.d.ts +32 -0
- package/dist/runner/common/jsonrpc/types.js +9 -0
- package/dist/runner/common/process-stopper.d.ts +2 -1
- package/dist/runner/common/process-stopper.js +1 -0
- package/dist/runner/common/runner-utils.d.ts +6 -13
- package/dist/runner/common/runner-utils.js +6 -47
- package/dist/runner/common/spawning-runner.d.ts +24 -70
- package/dist/runner/common/spawning-runner.js +98 -152
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +5 -5
- package/dist/runner/index.js +4 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +448 -0
- package/dist/runner/kimi/acp/translator.d.ts +124 -0
- package/dist/runner/kimi/acp/translator.js +336 -0
- package/dist/runner/kimi/index.d.ts +1 -1
- package/dist/runner/kimi/index.js +1 -1
- package/dist/runner/opencode/acp/index.d.ts +1 -0
- package/dist/runner/opencode/acp/index.js +2 -0
- package/dist/runner/opencode/acp/runner.d.ts +121 -0
- package/dist/runner/opencode/acp/runner.js +429 -0
- package/dist/runner/opencode/acp/translator.d.ts +119 -0
- package/dist/runner/opencode/acp/translator.js +345 -0
- package/dist/runner/opencode/index.d.ts +1 -1
- package/dist/runner/opencode/index.js +1 -1
- package/dist/runner/pi/index.d.ts +1 -1
- package/dist/runner/pi/index.js +1 -1
- package/dist/runner/pi/rpc/client.d.ts +52 -0
- package/dist/runner/pi/rpc/client.js +114 -0
- package/dist/runner/pi/rpc/index.d.ts +4 -0
- package/dist/runner/pi/rpc/index.js +4 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
- package/dist/runner/pi/rpc/protocol-types.js +21 -0
- package/dist/runner/pi/rpc/runner.d.ts +80 -0
- package/dist/runner/pi/rpc/runner.js +199 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/registry.d.ts +7 -8
- package/dist/runner/registry.js +3 -4
- package/dist/runner/resolve-agent-choices.js +18 -59
- package/dist/runner/sync-agent-choices.js +16 -67
- package/dist/runner/types.d.ts +153 -2
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +6 -13
- package/dist/session/codex/rollout-reader.d.ts +2 -2
- package/dist/session/codex/rollout-reader.js +57 -17
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +2 -2
- package/dist/session/common/content-blocks.d.ts +7 -0
- package/dist/session/common/content-blocks.js +13 -9
- package/dist/session/common/jsonl.d.ts +8 -25
- package/dist/session/common/jsonl.js +126 -261
- package/dist/session/common/pagination.d.ts +28 -0
- package/dist/session/common/pagination.js +33 -0
- package/dist/session/kimi/sessions.d.ts +34 -1
- package/dist/session/kimi/sessions.js +208 -98
- package/dist/session/opencode/sessions.d.ts +17 -3
- package/dist/session/opencode/sessions.js +29 -29
- package/dist/session/pi/sessions.d.ts +5 -1
- package/dist/session/pi/sessions.js +12 -16
- package/dist/session/session-store.d.ts +11 -1
- package/dist/session/session-store.js +75 -178
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/install.d.ts +20 -2
- package/dist/update/install.js +51 -7
- package/package.json +1 -1
- package/dist/runner/codex/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- package/dist/runner/codex/jsonl.d.ts +0 -37
- package/dist/runner/codex/jsonl.js +0 -221
- package/dist/runner/codex/runner.d.ts +0 -63
- package/dist/runner/codex/runner.js +0 -124
- package/dist/runner/common/exec-translator.d.ts +0 -73
- package/dist/runner/common/exec-translator.js +0 -105
- package/dist/runner/kimi/jsonl.d.ts +0 -28
- package/dist/runner/kimi/jsonl.js +0 -143
- package/dist/runner/kimi/runner.d.ts +0 -49
- package/dist/runner/kimi/runner.js +0 -105
- package/dist/runner/opencode/argv.d.ts +0 -32
- package/dist/runner/opencode/argv.js +0 -35
- package/dist/runner/opencode/jsonl.d.ts +0 -68
- package/dist/runner/opencode/jsonl.js +0 -295
- package/dist/runner/opencode/runner.d.ts +0 -61
- package/dist/runner/opencode/runner.js +0 -93
- package/dist/runner/pi/jsonl.d.ts +0 -128
- package/dist/runner/pi/jsonl.js +0 -249
- package/dist/runner/pi/runner.d.ts +0 -36
- package/dist/runner/pi/runner.js +0 -76
- package/dist/session/common/catch-up.d.ts +0 -12
- package/dist/session/common/catch-up.js +0 -31
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CardActionEvent, type CardActionResponse, type CardStreamProducer } from '@larksuite/channel';
|
|
1
|
+
import { type CardActionEvent, type CardActionResponse, type CardStreamProducer, type LarkChannel } from '@larksuite/channel';
|
|
2
2
|
import type { AppConfig } from '../config/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* dedup 缓存 TTL(毫秒)。控制飞书事件去重窗口,全局作用于 message + cardAction。
|
|
@@ -26,6 +26,54 @@ interface FeishuMessage {
|
|
|
26
26
|
content: string;
|
|
27
27
|
}
|
|
28
28
|
type MessageHandler = (msg: FeishuMessage) => void;
|
|
29
|
+
/**
|
|
30
|
+
* 入站媒体(image/file 消息的 resource)下载结果,交给 bridge 落盘。
|
|
31
|
+
* media 为通过大小限制、可落盘的项;failures 为下载失败或超限的项
|
|
32
|
+
* (bridge 负责把这些失败提示回用户)。
|
|
33
|
+
*/
|
|
34
|
+
export interface InboundMediaItem {
|
|
35
|
+
/** 资源类型:image 消息按 MIME 生成 image_<HHmmss>_<n>.<ext>,file 保留文件名。 */
|
|
36
|
+
type: 'image' | 'file';
|
|
37
|
+
/** 原始文件名(file 消息有;image 消息无,由 bridge 按 MIME 生成)。 */
|
|
38
|
+
fileName?: string;
|
|
39
|
+
/** 服务端 content-type(参数已剥离;可能缺失,bridge 有兜底)。 */
|
|
40
|
+
mimeType?: string;
|
|
41
|
+
/**
|
|
42
|
+
* 下载内容所在临时文件(流式落盘,避免大文件全量进堆内存)。
|
|
43
|
+
* 由 bridge 移动到最终位置;任何未移动路径都必须清理该文件。
|
|
44
|
+
*/
|
|
45
|
+
tempPath: string;
|
|
46
|
+
}
|
|
47
|
+
export interface InboundMediaFailure {
|
|
48
|
+
fileName?: string;
|
|
49
|
+
reason: string;
|
|
50
|
+
}
|
|
51
|
+
export interface InboundMediaPayload {
|
|
52
|
+
userId: string;
|
|
53
|
+
chatId: string;
|
|
54
|
+
messageId: string;
|
|
55
|
+
/** 引用上下文:不同 replyTo 强制拆批。 */
|
|
56
|
+
replyToMessageId?: string;
|
|
57
|
+
media: InboundMediaItem[];
|
|
58
|
+
failures: InboundMediaFailure[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 媒体消息到达的轻量描述(未下载):供上层先做 owner/配置闸门,
|
|
62
|
+
* 通过后再调 downloadInboundMedia —— 下载发生在认证之后(P1 review 修复)。
|
|
63
|
+
*/
|
|
64
|
+
export interface InboundMediaMessage {
|
|
65
|
+
userId: string;
|
|
66
|
+
chatId: string;
|
|
67
|
+
messageId: string;
|
|
68
|
+
/** 引用上下文:不同 replyTo 强制拆批。 */
|
|
69
|
+
replyToMessageId?: string;
|
|
70
|
+
resources: Array<{
|
|
71
|
+
type: 'image' | 'file';
|
|
72
|
+
fileKey: string;
|
|
73
|
+
fileName?: string;
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
type InboundMediaDetectedHandler = (msg: InboundMediaMessage) => void | Promise<void>;
|
|
29
77
|
/**
|
|
30
78
|
* CardAction handler may return a CardActionResponse (e.g. `{ toast: {...} }`)
|
|
31
79
|
* to give the clicking user native immediate feedback. The SDK passes the
|
|
@@ -34,9 +82,11 @@ type MessageHandler = (msg: FeishuMessage) => void;
|
|
|
34
82
|
*/
|
|
35
83
|
type CardActionHandler = (action: CardActionEvent) => void | CardActionResponse | Promise<void | CardActionResponse>;
|
|
36
84
|
export declare class FeishuConnector {
|
|
37
|
-
|
|
85
|
+
/** 飞书 channel 实例(public:测试直接访问/注入 mock,替代 as unknown as)。 */
|
|
86
|
+
channel: LarkChannel;
|
|
38
87
|
private onMessage?;
|
|
39
88
|
private onCardAction?;
|
|
89
|
+
private onInboundMediaDetected?;
|
|
40
90
|
private isConnected;
|
|
41
91
|
private appId;
|
|
42
92
|
private appSecret;
|
|
@@ -44,6 +94,23 @@ export declare class FeishuConnector {
|
|
|
44
94
|
get connected(): boolean;
|
|
45
95
|
setMessageHandler(handler: MessageHandler): void;
|
|
46
96
|
setCardActionHandler(handler: CardActionHandler): void;
|
|
97
|
+
setInboundMediaDetectedHandler(handler: InboundMediaDetectedHandler): void;
|
|
98
|
+
/**
|
|
99
|
+
* 上报媒体消息到达(轻量,不含下载内容)。上层认证/配置闸门通过后
|
|
100
|
+
* 再调 downloadInboundMedia。
|
|
101
|
+
*/
|
|
102
|
+
private handleInboundMediaDetected;
|
|
103
|
+
/**
|
|
104
|
+
* 下载消息里的全部资源(流式写临时文件,避免大文件全量进堆内存)。
|
|
105
|
+
* 逐资源 try/catch:单个失败不影响其他资源;超限项不进入 media(不落盘)。
|
|
106
|
+
* 下载失败/超限都随 failures 交给 bridge 提示用户(对齐"发送文件失败"文案风格)。
|
|
107
|
+
* 调用方必须已通过 owner/配置闸门(index.ts);maxFileSizeMb 由调用方从
|
|
108
|
+
* 当前配置传入(避免 connector 持有过期的 config 快照)。
|
|
109
|
+
*/
|
|
110
|
+
downloadInboundMedia(msg: InboundMediaMessage, opts?: {
|
|
111
|
+
maxFileSizeMb?: number;
|
|
112
|
+
downloadTimeoutMs?: number;
|
|
113
|
+
}): Promise<InboundMediaPayload>;
|
|
47
114
|
connect(): Promise<void>;
|
|
48
115
|
disconnect(): Promise<void>;
|
|
49
116
|
reconnect(): Promise<void>;
|
|
@@ -68,6 +135,11 @@ export declare class FeishuConnector {
|
|
|
68
135
|
/** Format error for logging without causing circular serialization */
|
|
69
136
|
private formatError;
|
|
70
137
|
addReaction(messageId: string, emoji: string): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Remove a reaction by emoji (e.g. retract the Typing indicator).
|
|
140
|
+
* The SDK resolves the reaction id internally; missing reaction is a no-op.
|
|
141
|
+
*/
|
|
142
|
+
removeReactionByEmoji(messageId: string, emoji: string): Promise<void>;
|
|
71
143
|
/**
|
|
72
144
|
* Upload a file to Feishu and send it to the specified chat.
|
|
73
145
|
* Uses the im/v1/files upload API.
|
package/dist/connector/index.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { createLarkChannel, } from '@larksuite/channel';
|
|
2
2
|
import { getLogger } from '../logger/index.js';
|
|
3
3
|
import { MAX_FILE_UPLOAD_SIZE } from './file-limits.js';
|
|
4
|
+
import { DEFAULT_INBOUND_MEDIA_MAX_SIZE_MB } from '../config/index.js';
|
|
5
|
+
import { sleep } from '../common/sleep.js';
|
|
4
6
|
import axios from 'axios';
|
|
5
7
|
import fs from 'node:fs';
|
|
8
|
+
import { silentlyUnlink } from '../common/fs.js';
|
|
6
9
|
import https from 'node:https';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
7
13
|
import FormData from 'form-data';
|
|
8
14
|
/**
|
|
9
15
|
* Dedicated https.Agent with keepAlive disabled for sendFile's three axios
|
|
@@ -36,6 +42,20 @@ const noKeepAliveAgent = new https.Agent({ keepAlive: false });
|
|
|
36
42
|
* 详见 design.md §9.8。原地更新逻辑新增按钮时参考该节去重风险分类。
|
|
37
43
|
*/
|
|
38
44
|
export const DEDUP_TTL_MS = 300;
|
|
45
|
+
/** 单资源下载超时(毫秒):SDK 无内置超时,挂起会导致提示丢失 + 残留临时文件。 */
|
|
46
|
+
const RESOURCE_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1000;
|
|
47
|
+
function withTimeout(promise, ms, label) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
50
|
+
promise.then((value) => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
resolve(value);
|
|
53
|
+
}, (err) => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
reject(err);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
39
59
|
/**
|
|
40
60
|
* 判断 send 失败是否值得重试一次(§9.5 限流重试口径,§P1-3 修正)。
|
|
41
61
|
*
|
|
@@ -75,9 +95,11 @@ function tryGetPatchService(channel) {
|
|
|
75
95
|
return c?.im?.v1?.message;
|
|
76
96
|
}
|
|
77
97
|
export class FeishuConnector {
|
|
98
|
+
/** 飞书 channel 实例(public:测试直接访问/注入 mock,替代 as unknown as)。 */
|
|
78
99
|
channel;
|
|
79
100
|
onMessage;
|
|
80
101
|
onCardAction;
|
|
102
|
+
onInboundMediaDetected;
|
|
81
103
|
isConnected = false;
|
|
82
104
|
appId;
|
|
83
105
|
appSecret;
|
|
@@ -125,6 +147,15 @@ export class FeishuConnector {
|
|
|
125
147
|
this.channel.on('message', (msg) => {
|
|
126
148
|
if (msg.chatType !== 'p2p')
|
|
127
149
|
return;
|
|
150
|
+
// 入站图片/文件:不转发文本内容(图片消息的 content 只是占位文案),
|
|
151
|
+
// 而是下载资源后交给 bridge 落盘(runner 零改动)。
|
|
152
|
+
if ((msg.rawContentType === 'image' || msg.rawContentType === 'file') &&
|
|
153
|
+
msg.resources.length > 0) {
|
|
154
|
+
// 只上报"媒体到达",不在此下载:owner/配置闸门在 index.ts 里先执行,
|
|
155
|
+
// 通过后才调用 downloadInboundMedia(避免未认证大文件下载打爆内存)。
|
|
156
|
+
void this.handleInboundMediaDetected(msg);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
128
159
|
this.onMessage?.({
|
|
129
160
|
userId: msg.senderId,
|
|
130
161
|
messageId: msg.messageId,
|
|
@@ -158,6 +189,81 @@ export class FeishuConnector {
|
|
|
158
189
|
setCardActionHandler(handler) {
|
|
159
190
|
this.onCardAction = handler;
|
|
160
191
|
}
|
|
192
|
+
setInboundMediaDetectedHandler(handler) {
|
|
193
|
+
this.onInboundMediaDetected = handler;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 上报媒体消息到达(轻量,不含下载内容)。上层认证/配置闸门通过后
|
|
197
|
+
* 再调 downloadInboundMedia。
|
|
198
|
+
*/
|
|
199
|
+
async handleInboundMediaDetected(msg) {
|
|
200
|
+
if (!this.onInboundMediaDetected) {
|
|
201
|
+
getLogger().warn('[feishu] inbound media received but no detected handler registered');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
await this.onInboundMediaDetected({
|
|
206
|
+
userId: msg.senderId,
|
|
207
|
+
chatId: msg.chatId,
|
|
208
|
+
messageId: msg.messageId,
|
|
209
|
+
replyToMessageId: msg.replyToMessageId,
|
|
210
|
+
resources: msg.resources.map((r) => ({
|
|
211
|
+
type: r.type === 'image' ? 'image' : 'file',
|
|
212
|
+
fileKey: r.fileKey,
|
|
213
|
+
fileName: r.fileName,
|
|
214
|
+
})),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
getLogger().error('[feishu] inbound media detected handler failed:', err.message);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 下载消息里的全部资源(流式写临时文件,避免大文件全量进堆内存)。
|
|
223
|
+
* 逐资源 try/catch:单个失败不影响其他资源;超限项不进入 media(不落盘)。
|
|
224
|
+
* 下载失败/超限都随 failures 交给 bridge 提示用户(对齐"发送文件失败"文案风格)。
|
|
225
|
+
* 调用方必须已通过 owner/配置闸门(index.ts);maxFileSizeMb 由调用方从
|
|
226
|
+
* 当前配置传入(避免 connector 持有过期的 config 快照)。
|
|
227
|
+
*/
|
|
228
|
+
async downloadInboundMedia(msg, opts) {
|
|
229
|
+
const maxBytes = (opts?.maxFileSizeMb ?? DEFAULT_INBOUND_MEDIA_MAX_SIZE_MB) * 1024 * 1024;
|
|
230
|
+
const timeoutMs = opts?.downloadTimeoutMs ?? RESOURCE_DOWNLOAD_TIMEOUT_MS;
|
|
231
|
+
const media = [];
|
|
232
|
+
const failures = [];
|
|
233
|
+
for (const res of msg.resources) {
|
|
234
|
+
const tmpPath = path.join(os.tmpdir(), `lark-remote-inbound-${randomUUID()}`);
|
|
235
|
+
try {
|
|
236
|
+
const { contentType, bytesWritten } = await withTimeout(this.channel.downloadResourceToFile(msg.messageId, res.fileKey, res.type, tmpPath), timeoutMs, `downloadResource fileKey=${res.fileKey}`);
|
|
237
|
+
if (bytesWritten > maxBytes) {
|
|
238
|
+
silentlyUnlink(tmpPath);
|
|
239
|
+
failures.push({
|
|
240
|
+
fileName: res.fileName,
|
|
241
|
+
reason: `超过 ${maxBytes / (1024 * 1024)}MB 大小限制`,
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
media.push({
|
|
246
|
+
type: res.type,
|
|
247
|
+
fileName: res.fileName,
|
|
248
|
+
mimeType: contentType,
|
|
249
|
+
tempPath: tmpPath,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
silentlyUnlink(tmpPath);
|
|
254
|
+
getLogger().warn(`[feishu] downloadResource failed fileKey=${res.fileKey} type=${res.type}:`, err.message);
|
|
255
|
+
failures.push({ fileName: res.fileName, reason: `下载失败: ${err.message}` });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
userId: msg.userId,
|
|
260
|
+
chatId: msg.chatId,
|
|
261
|
+
messageId: msg.messageId,
|
|
262
|
+
replyToMessageId: msg.replyToMessageId,
|
|
263
|
+
media,
|
|
264
|
+
failures,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
161
267
|
async connect() {
|
|
162
268
|
try {
|
|
163
269
|
await this.channel.connect();
|
|
@@ -200,7 +306,7 @@ export class FeishuConnector {
|
|
|
200
306
|
catch (err) {
|
|
201
307
|
if (shouldRetrySendError(err)) {
|
|
202
308
|
getLogger().warn('[feishu] rate limited, retrying in 200ms...');
|
|
203
|
-
await
|
|
309
|
+
await sleep(200);
|
|
204
310
|
try {
|
|
205
311
|
const result = await this.channel.send(chatId, input, { replyTo: opts?.replyTo });
|
|
206
312
|
return result.messageId;
|
|
@@ -264,6 +370,18 @@ export class FeishuConnector {
|
|
|
264
370
|
getLogger().error('[feishu] addReaction failed:', this.formatError(err));
|
|
265
371
|
}
|
|
266
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Remove a reaction by emoji (e.g. retract the Typing indicator).
|
|
375
|
+
* The SDK resolves the reaction id internally; missing reaction is a no-op.
|
|
376
|
+
*/
|
|
377
|
+
async removeReactionByEmoji(messageId, emoji) {
|
|
378
|
+
try {
|
|
379
|
+
await this.channel.removeReactionByEmoji(messageId, emoji);
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
getLogger().error('[feishu] removeReactionByEmoji failed:', this.formatError(err));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
267
385
|
/**
|
|
268
386
|
* Upload a file to Feishu and send it to the specified chat.
|
|
269
387
|
* Uses the im/v1/files upload API.
|