@yvhitxcel/opencode-remote 0.16.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -1
- package/dist/autonomous/decisions.js +73 -0
- package/dist/autonomous/index.js +141 -0
- package/dist/cli.js +121 -19
- package/dist/core/adapter.js +12 -0
- package/dist/core/agent-registry.js +77 -0
- package/dist/core/crypto.js +80 -0
- package/dist/core/git-push.js +143 -0
- package/dist/core/handler.js +293 -0
- package/dist/core/log.js +92 -0
- package/dist/core/lru.js +98 -0
- package/dist/core/notifications.js +2 -2
- package/dist/core/qiniu.js +2 -2
- package/dist/core/retry.js +46 -0
- package/dist/core/router.js +62 -296
- package/dist/core/state.js +190 -0
- package/dist/core/stats.js +115 -0
- package/dist/feishu/adapter.js +0 -1
- package/dist/feishu/bot.js +4 -4
- package/dist/feishu/commands.js +28 -397
- package/dist/feishu/handler.js +9 -369
- package/dist/opencode/client.js +172 -168
- package/dist/patch_spawn.js +1 -0
- package/dist/plugins/agents/claude-code/index.js +59 -47
- package/dist/plugins/agents/codex/index.js +32 -6
- package/dist/plugins/agents/copilot/index.js +32 -6
- package/dist/plugins/agents/opencode/index.js +38 -12
- package/dist/telegram/adapter.js +22 -9
- package/dist/telegram/bot.js +1 -6
- package/dist/weixin/adapter.js +37 -15
- package/dist/weixin/api.js +47 -19
- package/dist/weixin/bot.js +172 -83
- package/dist/weixin/commands.js +476 -597
- package/dist/weixin/handler.js +27 -541
- package/dist/weixin/user-adapter-map.js +12 -0
- package/package.json +5 -3
- package/dist/core/session.js +0 -403
package/dist/feishu/handler.js
CHANGED
|
@@ -1,371 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { initOpenCode, createSession, sendMessage, checkConnection, resumeSession, shareSession } from '../opencode/client.js';
|
|
4
|
-
import { isAuthorized, hasOwner } from '../core/auth.js';
|
|
5
|
-
import { detectCommand, EXPERT_SYSTEM_PROMPT } from '../core/router.js';
|
|
6
|
-
import { handleCommand, formatTimeAgo } from './commands.js';
|
|
7
|
-
import { existsSync, readFileSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
1
|
+
import { createHandler } from '../core/handler.js';
|
|
2
|
+
import { handleCommand } from './commands.js';
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const handler = createHandler({
|
|
5
|
+
handleCommand,
|
|
6
|
+
replyTo: (userId, text, fallbackAdapter) => fallbackAdapter.reply(userId, text),
|
|
7
|
+
wrapAdapterForShared: (adapter) => adapter,
|
|
8
|
+
});
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (text.startsWith('/z')) {
|
|
17
|
-
const arg = text.slice(2).trim();
|
|
18
|
-
if (arg === 'off' || arg === 'reset' || arg === '关闭') {
|
|
19
|
-
await adapter.reply(ctx.threadId, '⏹️ 自定义 prompt 已清除');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (arg) {
|
|
23
|
-
expertPrompt = arg;
|
|
24
|
-
} else {
|
|
25
|
-
expertPrompt = EXPERT_SYSTEM_PROMPT;
|
|
26
|
-
}
|
|
27
|
-
await forwardToOpenCode(adapter, ctx, text, openCodeSessions, session, expertPrompt);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (expertTriggers.some(t => text.trim().toLowerCase().includes(t))) {
|
|
32
|
-
expertPrompt = EXPERT_SYSTEM_PROMPT;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const parsed = detectCommand(text);
|
|
36
|
-
if (parsed) {
|
|
37
|
-
await handleCommand(adapter, ctx, parsed.name, parsed.arg, openCodeSessions);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const trimmed = text.trim();
|
|
41
|
-
if (session._deleteSessionList) {
|
|
42
|
-
if (/^\d+$/.test(trimmed)) {
|
|
43
|
-
const num = parseInt(trimmed, 10);
|
|
44
|
-
const list = session._deleteSessionList;
|
|
45
|
-
if (num >= 1 && num <= list.length) {
|
|
46
|
-
const target = list[num - 1];
|
|
47
|
-
try {
|
|
48
|
-
const opencode = await initOpenCode();
|
|
49
|
-
if (!opencode) { await adapter.reply(ctx.threadId, '❌ 无法删除会话'); return; }
|
|
50
|
-
await opencode.client.session.delete({ path: { id: target.id } });
|
|
51
|
-
if (session.opencodeSessionId === target.id) {
|
|
52
|
-
openCodeSessions.delete(ctx.threadId);
|
|
53
|
-
session.opencodeSessionId = undefined;
|
|
54
|
-
}
|
|
55
|
-
const result = await opencode.client.session.list();
|
|
56
|
-
if (!result.error && result.data && result.data.length > 0) {
|
|
57
|
-
const sorted = result.data.sort((a, b) => (b.time?.updated || 0) - (a.time?.updated || 0));
|
|
58
|
-
session._deleteSessionList = sorted;
|
|
59
|
-
let msg = `🗑️ 已删除: ${target.title || '无标题'}\n\n📂 选择要删除的会话(回复编号):\n\n`;
|
|
60
|
-
sorted.slice(0, 10).forEach((s, i) => {
|
|
61
|
-
const n = i + 1;
|
|
62
|
-
msg += `${n}. ${s.title || '无标题'} (${s.updated_at ? formatTimeAgo(s.updated_at * 1000) : ''})\n`;
|
|
63
|
-
});
|
|
64
|
-
if (sorted.length > 10) msg += `\n... 共 ${sorted.length} 个会话`;
|
|
65
|
-
msg += '\n\n回复编号删除';
|
|
66
|
-
for (const m of splitMessage(msg)) await adapter.reply(ctx.threadId, m);
|
|
67
|
-
} else {
|
|
68
|
-
session._deleteSessionList = null;
|
|
69
|
-
await adapter.reply(ctx.threadId, `🗑️ 已删除: ${target.title || '无标题'}\n📭 没有更多会话了`);
|
|
70
|
-
}
|
|
71
|
-
} catch (e) {
|
|
72
|
-
await adapter.reply(ctx.threadId, `❌ 删除失败: ${e.message}`);
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
await adapter.reply(ctx.threadId, `❌ 无效编号,请输入 1-${session._deleteSessionList.length}`);
|
|
76
|
-
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
session._deleteSessionList = null;
|
|
80
|
-
}
|
|
81
|
-
if (session._switchSessionList) {
|
|
82
|
-
if (/^\d+$/.test(trimmed)) {
|
|
83
|
-
const num = parseInt(trimmed, 10);
|
|
84
|
-
const list = session._switchSessionList;
|
|
85
|
-
if (num >= 1 && num <= list.length) {
|
|
86
|
-
const target = list[num - 1];
|
|
87
|
-
if (session._showSessionState) {
|
|
88
|
-
session._showSessionState = null;
|
|
89
|
-
session._pendingSwitchSession = target;
|
|
90
|
-
const targetDir = target.directory || process.cwd();
|
|
91
|
-
let stateMsg = `📋 会话: ${target.title || '无标题'}\n`;
|
|
92
|
-
stateMsg += `ID: ${target.id.slice(0, 8)}...\n`;
|
|
93
|
-
if (target.directory) stateMsg += `📁 目录: ${target.directory}\n`;
|
|
94
|
-
stateMsg += '\n';
|
|
95
|
-
try {
|
|
96
|
-
const memoryPath = join(targetDir, 'MEMORY.md');
|
|
97
|
-
if (existsSync(memoryPath)) {
|
|
98
|
-
const content = readFileSync(memoryPath, 'utf-8');
|
|
99
|
-
const summaryMatch = content.match(/## 最近会话摘要\n([\s\S]*?)(?=##|$)/);
|
|
100
|
-
if (summaryMatch) {
|
|
101
|
-
const lines = summaryMatch[1].trim().split('\n').filter(l => l.trim());
|
|
102
|
-
const recent = lines.slice(-3);
|
|
103
|
-
if (recent.length > 0) {
|
|
104
|
-
stateMsg += `📝 最近会话:\n${recent.map(l => ` ${l.trim()}`).join('\n')}\n\n`;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} catch { console.debug('[session-switch] 无法读取MEMORY.md'); }
|
|
109
|
-
stateMsg += `💡 回复 "确认" 切换到此会话,或回复其他内容取消`;
|
|
110
|
-
await adapter.reply(ctx.threadId, stateMsg);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const resumed = await resumeSession(target.id);
|
|
115
|
-
if (resumed) {
|
|
116
|
-
openCodeSessions.set(ctx.threadId, resumed);
|
|
117
|
-
session.opencodeSessionId = resumed.sessionId;
|
|
118
|
-
session.taskStartTime = null;
|
|
119
|
-
session.currentTool = null;
|
|
120
|
-
session.modifiedFiles = null;
|
|
121
|
-
if (target.directory) {
|
|
122
|
-
session.projectDir = target.directory;
|
|
123
|
-
globalThis.__autoProjectDir = target.directory;
|
|
124
|
-
}
|
|
125
|
-
await adapter.reply(ctx.threadId, `✅ 已切换至: ${target.title || '无标题'}\nID: ${resumed.sessionId.slice(0, 8)}...`);
|
|
126
|
-
} else {
|
|
127
|
-
await adapter.reply(ctx.threadId, '❌ 切换失败');
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
await adapter.reply(ctx.threadId, `❌ 切换失败: ${e.message}`);
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
await adapter.reply(ctx.threadId, `❌ 无效编号,请输入 1-${session._switchSessionList.length}`);
|
|
134
|
-
}
|
|
135
|
-
session._switchSessionList = null;
|
|
136
|
-
session._showSessionState = null;
|
|
137
|
-
session._pendingSwitchSession = null;
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
session._switchSessionList = null;
|
|
141
|
-
session._showSessionState = null;
|
|
142
|
-
session._pendingSwitchSession = null;
|
|
143
|
-
}
|
|
144
|
-
if (session._pendingSwitchSession) {
|
|
145
|
-
const confirm = trimmed.toLowerCase();
|
|
146
|
-
const target = session._pendingSwitchSession;
|
|
147
|
-
session._pendingSwitchSession = null;
|
|
148
|
-
if (confirm === '确认' || confirm === 'confirm' || confirm === 'y' || confirm === '1') {
|
|
149
|
-
try {
|
|
150
|
-
const resumed = await resumeSession(target.id);
|
|
151
|
-
if (resumed) {
|
|
152
|
-
openCodeSessions.set(ctx.threadId, resumed);
|
|
153
|
-
session.opencodeSessionId = resumed.sessionId;
|
|
154
|
-
session.taskStartTime = null;
|
|
155
|
-
session.currentTool = null;
|
|
156
|
-
session.modifiedFiles = null;
|
|
157
|
-
if (target.directory) {
|
|
158
|
-
session.projectDir = target.directory;
|
|
159
|
-
globalThis.__autoProjectDir = target.directory;
|
|
160
|
-
}
|
|
161
|
-
await adapter.reply(ctx.threadId, `✅ 已切换至: ${target.title || '无标题'}\nID: ${resumed.sessionId.slice(0, 8)}...`);
|
|
162
|
-
} else {
|
|
163
|
-
await adapter.reply(ctx.threadId, '❌ 切换失败');
|
|
164
|
-
}
|
|
165
|
-
} catch (e) {
|
|
166
|
-
await adapter.reply(ctx.threadId, `❌ 切换失败: ${e.message}`);
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
await adapter.reply(ctx.threadId, '已取消切换');
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (session._analyzeMode) {
|
|
174
|
-
const confirm = trimmed.toLowerCase();
|
|
175
|
-
if (confirm === '执行' || confirm === 'execute' || confirm === '开始' || confirm === 'go') {
|
|
176
|
-
session._analyzeMode = false;
|
|
177
|
-
const task = session._analyzeTask || text;
|
|
178
|
-
session._analyzeTask = null;
|
|
179
|
-
await adapter.reply(ctx.threadId, `🔧 开始执行: ${task}\n\nAI 将实施最小改动,完成后列出变更点和验证步骤。`);
|
|
180
|
-
const execPrompt = `现在开始施工队模式。任务:${task}\n\n请严格执行以下步骤:\n\n1. 实施最小改动:只修改必要的代码,不要重构其他地方\n2. 保持风格一致:遵循项目现有的代码风格\n3. 列出变更点:修改完成后,列出所有改动的文件和代码\n4. 验证步骤:说明需要运行哪些测试或检查来验证修改\n5. 列出未验证的部分:说明还有哪些边界情况需要人工检查\n\n注意:\n- 只做最小改动\n- 不要顺手重构\n- 不要修改不相关的文件`;
|
|
181
|
-
await forwardToOpenCode(adapter, ctx, execPrompt, openCodeSessions, session);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
session._analyzeMode = false;
|
|
185
|
-
session._analyzeTask = null;
|
|
186
|
-
await adapter.reply(ctx.threadId, '已取消分析模式');
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (!isAuthorized('feishu', ctx.userId)) {
|
|
190
|
-
if (!hasOwner('feishu')) {
|
|
191
|
-
await adapter.reply(ctx.threadId, `🔐 **需要认证**
|
|
192
|
-
|
|
193
|
-
此 bot 尚未绑定。
|
|
194
|
-
|
|
195
|
-
请发送 /start 进行首次认证。`);
|
|
196
|
-
} else {
|
|
197
|
-
await adapter.reply(ctx.threadId, `🚫 **拒绝访问**
|
|
198
|
-
|
|
199
|
-
你无权使用此 bot。`);
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const connected = await checkConnection();
|
|
204
|
-
if (!connected) {
|
|
205
|
-
await adapter.reply(ctx.threadId, `❌ OpenCode 离线
|
|
206
|
-
|
|
207
|
-
无法连接 OpenCode 服务。
|
|
208
|
-
|
|
209
|
-
🔄 /retry — 重试连接`);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
await forwardToOpenCode(adapter, ctx, text, openCodeSessions, session);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session, expertPrompt) {
|
|
216
|
-
await adapter.sendTypingIndicator(ctx.threadId);
|
|
217
|
-
let openCodeSession = openCodeSessions.get(ctx.threadId);
|
|
218
|
-
if (!openCodeSession) {
|
|
219
|
-
if (session.opencodeSessionId) {
|
|
220
|
-
openCodeSession = await resumeSession(session.opencodeSessionId);
|
|
221
|
-
}
|
|
222
|
-
if (!openCodeSession && globalThis.__latestOpenCodeSession?.id) {
|
|
223
|
-
openCodeSession = await resumeSession(globalThis.__latestOpenCodeSession.id);
|
|
224
|
-
}
|
|
225
|
-
if (!openCodeSession) {
|
|
226
|
-
openCodeSession = await createSession(ctx.threadId, `Feishu ${ctx.threadId}`);
|
|
227
|
-
if (!openCodeSession) {
|
|
228
|
-
await adapter.reply(ctx.threadId, '❌ 无法创建 OpenCode 会话');
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
openCodeSessions.set(ctx.threadId, openCodeSession);
|
|
233
|
-
session.opencodeSessionId = openCodeSession.sessionId;
|
|
234
|
-
}
|
|
235
|
-
if (session.modelOverride) {
|
|
236
|
-
openCodeSession.model = session.modelOverride;
|
|
237
|
-
}
|
|
238
|
-
session.taskStartTime = Date.now();
|
|
239
|
-
session.currentTool = null;
|
|
240
|
-
const projectDir = session.projectDir || globalThis.__autoProjectDir;
|
|
241
|
-
let scopedText = text;
|
|
242
|
-
if (session._contextScope) {
|
|
243
|
-
scopedText = `[上下文范围: ${session._contextScope}]\n\n${text}`;
|
|
244
|
-
}
|
|
245
|
-
if (projectDir) {
|
|
246
|
-
if (!scopedText.includes('项目目录')) {
|
|
247
|
-
scopedText = `[当前项目目录: ${projectDir}]\n\n${scopedText}`;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (expertPrompt) {
|
|
252
|
-
scopedText = `${expertPrompt}\n\n${scopedText}`;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
console.log(`📤 Forwarding to OpenCode: ${text.substring(0, 80)}...`);
|
|
256
|
-
try {
|
|
257
|
-
let lastToolNotified = '';
|
|
258
|
-
let hasToolActivity = false;
|
|
259
|
-
let toolCallCount = 0;
|
|
260
|
-
let response = await sendMessage(openCodeSession, scopedText, {
|
|
261
|
-
idleThreshold: expertPrompt ? 30 : 10,
|
|
262
|
-
onEvent: (event) => {
|
|
263
|
-
if (event.type === 'tool.call') {
|
|
264
|
-
const props = event.properties || {};
|
|
265
|
-
const toolName = props.name || props.tool_name || 'unknown';
|
|
266
|
-
const input = props.input || {};
|
|
267
|
-
toolCallCount++;
|
|
268
|
-
let toolDesc = `🔧 执行工具: ${toolName}`;
|
|
269
|
-
if (input.path) {
|
|
270
|
-
toolDesc += `\n📁 ${input.path}`;
|
|
271
|
-
}
|
|
272
|
-
if (input.command) {
|
|
273
|
-
toolDesc += `\n💻 ${input.command}`;
|
|
274
|
-
}
|
|
275
|
-
adapter.reply(ctx.threadId, toolDesc).catch(() => {});
|
|
276
|
-
if (input.path) {
|
|
277
|
-
if (!session.modifiedFiles) session.modifiedFiles = new Set();
|
|
278
|
-
session.modifiedFiles.add(input.path);
|
|
279
|
-
}
|
|
280
|
-
console.log(`[feishu-tool] Executing: ${toolName}`);
|
|
281
|
-
}
|
|
282
|
-
if (event.type && !event.type.includes('delta')) {
|
|
283
|
-
console.log(`📡 Feishu Event: ${event.type}`);
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
onTextDelta: () => {},
|
|
287
|
-
onStatusChange: (status) => {
|
|
288
|
-
if (status.hasToolActivity) {
|
|
289
|
-
hasToolActivity = true;
|
|
290
|
-
}
|
|
291
|
-
if (status.type === 'retry') {
|
|
292
|
-
adapter.updateMessage(ctx.threadId, '', `⏳ 重试中 (${status.attempt})...`).catch(() => {});
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
}, ctx.threadId);
|
|
296
|
-
session.taskStartTime = null;
|
|
297
|
-
session.currentTool = null;
|
|
298
|
-
if (session.modifiedFiles instanceof Set) {
|
|
299
|
-
session.modifiedFiles = Array.from(session.modifiedFiles);
|
|
300
|
-
}
|
|
301
|
-
if (!response || typeof response !== 'string') {
|
|
302
|
-
await adapter.reply(ctx.threadId, '...');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const trimmedResponse = response.trim();
|
|
306
|
-
if (!trimmedResponse) {
|
|
307
|
-
await adapter.reply(ctx.threadId, '...');
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (trimmedResponse.startsWith('⏰') || trimmedResponse.startsWith('❌')) {
|
|
311
|
-
console.error('[feishu-forward] Error response:', trimmedResponse);
|
|
312
|
-
await adapter.reply(ctx.threadId, trimmedResponse);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (trimmedResponse.endsWith(':') || trimmedResponse.endsWith('...')) {
|
|
316
|
-
console.log('[feishu-forward] 检测到不完整响应,等待补充...');
|
|
317
|
-
await new Promise(r => setTimeout(r, 5000));
|
|
318
|
-
try {
|
|
319
|
-
const msgsResult = await openCodeSession.client.session.messages({
|
|
320
|
-
path: { id: openCodeSession.sessionId },
|
|
321
|
-
query: { limit: 5 }
|
|
322
|
-
});
|
|
323
|
-
if (!msgsResult.error && msgsResult.data && msgsResult.data.length > 0) {
|
|
324
|
-
for (let i = msgsResult.data.length - 1; i >= 0; i--) {
|
|
325
|
-
const msg = msgsResult.data[i];
|
|
326
|
-
if (msg.info?.role === 'assistant' && msg.parts) {
|
|
327
|
-
const textParts = msg.parts.filter(p => p.type === 'text' && p.text).map(p => p.text);
|
|
328
|
-
if (textParts.length > 0) {
|
|
329
|
-
const newResponse = textParts.join('\n').trim();
|
|
330
|
-
if (newResponse && newResponse !== trimmedResponse) {
|
|
331
|
-
response = newResponse;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
} catch (e) {
|
|
339
|
-
console.log('[feishu-forward] 无法检查额外响应');
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
const responseMsgs = splitMessage(response);
|
|
343
|
-
for (const m of responseMsgs) {
|
|
344
|
-
const trimmed = m.trim();
|
|
345
|
-
if (!trimmed) continue;
|
|
346
|
-
try {
|
|
347
|
-
await adapter.reply(ctx.threadId, m);
|
|
348
|
-
} catch (replyErr) {
|
|
349
|
-
console.error('[feishu-forward] 回复失败:', replyErr.message);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
const filesCount = (session.modifiedFiles?.length || session.modifiedFiles?.size || 0);
|
|
353
|
-
if (filesCount > 0) {
|
|
354
|
-
try {
|
|
355
|
-
const shareUrl = await shareSession(openCodeSession);
|
|
356
|
-
if (shareUrl) {
|
|
357
|
-
await adapter.reply(ctx.threadId, `🔗 ${shareUrl}`);
|
|
358
|
-
}
|
|
359
|
-
} catch (e) {
|
|
360
|
-
console.error('[feishu-forward] 分享会话失败:', e.message);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} catch (error) {
|
|
364
|
-
session.taskStartTime = null;
|
|
365
|
-
session.currentTool = null;
|
|
366
|
-
console.error('❌ Feishu 错误:', error);
|
|
367
|
-
await adapter.reply(ctx.threadId, `❌ 错误: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export { handleMessage, forwardToOpenCode };
|
|
10
|
+
export const handleMessage = handler.handleMessage;
|
|
11
|
+
export const forwardToOpenCode = handler.forwardToOpenCode;
|