@fengming-gh/oc-wechat-bridge 1.0.6 → 1.0.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/README.md +102 -32
- package/package.json +3 -7
- package/src/index.ts +25 -36
package/README.md
CHANGED
|
@@ -1,32 +1,102 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
# oc-wechat-bridge
|
|
2
|
+
|
|
3
|
+
微信 ↔ OpenCode 双向桥接插件。在微信中直接与 OC AI 对话,接收实时进度、切换会话。集成跨会话转发与自动续命。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **双向消息**:微信↔OC 消息实时互通,AI 回复自动转发到微信
|
|
8
|
+
- **流式输出**:AI 思考、工具调用、回复文字实时推送微信,不等完整回复
|
|
9
|
+
- **多项目目录**:自动发现同级带 `.opencode/` 的项目,会话按目录分组显示
|
|
10
|
+
- **会话管理**:全局编号 `/switch` 跨目录切换、`/status` 查看全貌
|
|
11
|
+
|
|
12
|
+
- **自动恢复**:AI 出错或上下文压缩后自动重试,微信端无感(集成了 [oc-auto-continue](https://github.com/Fengming-GH/oc-auto-continue))
|
|
13
|
+
- **跨会话转发**:AI 可通过 `!` 指令将消息转发到其他会话(集成了 [oc-forward](https://github.com/Fengming-GH/oc-forward))
|
|
14
|
+
- **消息不阻塞**:`promptAsync` 注射消息后立即返回,AI 处理期间仍可接收新消息
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
### 通过 npm 安装
|
|
19
|
+
|
|
20
|
+
在项目根目录的 `opencode.json` 中添加:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"plugin": ["@fengming-gh/oc-wechat-bridge"]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
重启 OC 后自动加载。无需手动 `npm install`。
|
|
29
|
+
|
|
30
|
+
### 复制文件安装
|
|
31
|
+
|
|
32
|
+
将 `src/index.ts` 复制到 OC 项目的 `.opencode/plugins/` 目录下:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cp src/index.ts 你的项目/.opencode/plugins/wechat-bridge.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
重启 OC,首次启动弹出二维码,微信扫码登录。无需 `npm install`,插件零外部运行时依赖。
|
|
39
|
+
|
|
40
|
+
## 指令
|
|
41
|
+
|
|
42
|
+
所有指令通过微信发送给 Bot,以 `/` 开头。中英文别名均可识别。
|
|
43
|
+
|
|
44
|
+
| 指令 | 别名 | 功能 |
|
|
45
|
+
|------|------|------|
|
|
46
|
+
| `/stop` | `/停止` | 中断 AI 任务 |
|
|
47
|
+
| `/status` | `/状态` / `/会话` | 查看所有目录和会话 |
|
|
48
|
+
| `/switch N` | `/切换 N` | 切换到指定会话并绑定 |
|
|
49
|
+
| `/new [N]` | `/新建 [N]` | 创建新会话 |
|
|
50
|
+
| `/unbind` | `/解绑` | 解绑当前会话 |
|
|
51
|
+
| `/rename <标题>` | `/改名 <标题>` | 修改会话标题 |
|
|
52
|
+
| `/mode` | `/模式` | 查看当前会话模式(只读) |
|
|
53
|
+
| `/help` | `/帮助` | 显示指令列表 |
|
|
54
|
+
|
|
55
|
+
**无空格参数:** `/switch3` 与 `/switch 3` 等价。
|
|
56
|
+
|
|
57
|
+
## AI 工具 / 跨会话指令
|
|
58
|
+
|
|
59
|
+
AI 对话中支持以下操作:
|
|
60
|
+
|
|
61
|
+
| 输入 | 说明 |
|
|
62
|
+
|------|------|
|
|
63
|
+
| `!会话` 或 `!sessions` | AI 调用 `list_sessions` 列出所有会话 |
|
|
64
|
+
| `!<前缀> <消息>` 或 `!<前缀> <消息>` | AI 调用 `forward_to_session` 转发消息到目标会话 |
|
|
65
|
+
|
|
66
|
+
`!`(全角)和 `!`(半角)都支持。
|
|
67
|
+
|
|
68
|
+
## 进度与格式
|
|
69
|
+
|
|
70
|
+
AI 处理时微信实时收到:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
思考中... ← 推理进度
|
|
74
|
+
read ← 工具调用
|
|
75
|
+
edit
|
|
76
|
+
好的…… ← AI 回复文字
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
会话列表按目录分组,带空行分隔:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
📁 项目A — 3 个会话
|
|
83
|
+
1. 📱WeChat插件 [当前会话]
|
|
84
|
+
2. 微信测试
|
|
85
|
+
3. 改造计划审查
|
|
86
|
+
|
|
87
|
+
📁 项目B — 1 个会话
|
|
88
|
+
4. 编译 4.1 版本
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 技术栈
|
|
92
|
+
|
|
93
|
+
- 单文件 TypeScript(~740 行)
|
|
94
|
+
- 零外部运行时依赖(仅 Node 内置模块)
|
|
95
|
+
- 基于微信官方 iLink Bot API 开发,非逆向/非破解
|
|
96
|
+
- 原生 Web Fetch API
|
|
97
|
+
- AES-128-ECB 加密传输(CDN 媒体文件)
|
|
98
|
+
- 事件驱动架构(`message.part.updated` / `session.idle` 等)
|
|
99
|
+
|
|
100
|
+
## 许可
|
|
101
|
+
|
|
102
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fengming-gh/oc-wechat-bridge",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "将微信消息桥接到 OpenCode
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "将微信消息桥接到 OpenCode,支持双向对话、会话管理、跨会话转发",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -20,10 +20,6 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
-
"peerDependencies": {
|
|
24
|
-
"@opencode-ai/plugin": ">=1.0.0",
|
|
25
|
-
"@opencode-ai/sdk": ">=1.0.0"
|
|
26
|
-
},
|
|
27
23
|
"repository": {
|
|
28
24
|
"type": "git",
|
|
29
25
|
"url": "git+https://github.com/Fengming-GH/oc-wechat-bridge.git"
|
|
@@ -31,5 +27,5 @@
|
|
|
31
27
|
"bugs": {
|
|
32
28
|
"url": "https://github.com/Fengming-GH/oc-wechat-bridge/issues"
|
|
33
29
|
},
|
|
34
|
-
"homepage":
|
|
30
|
+
"homepage": "https://github.com/Fengming-GH/oc-wechat-bridge#readme"
|
|
35
31
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ============================================================
|
|
2
2
|
// oc-wechat-bridge: 将微信消息桥接到 OpenCode
|
|
3
|
+
// 本插件用于 OpenCode WIN平台 桌面端,实现打通微信和 OpenCode 会话。最多允许一个微信在同一时刻绑定一个会话,允许微信切换会话。
|
|
3
4
|
// ============================================================
|
|
4
5
|
|
|
5
6
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
@@ -56,6 +57,7 @@ const sidTitle = new Map<string, string>()
|
|
|
56
57
|
const wechatSid = new Map<string, string>()
|
|
57
58
|
const _pendingFirstContact = new Set<string>()
|
|
58
59
|
let _projectDirs: string[] = []
|
|
60
|
+
let _worktree = ""
|
|
59
61
|
const _modeCache = new Map<string, string>()
|
|
60
62
|
|
|
61
63
|
const _thinkingSent = new Set<string>()
|
|
@@ -68,7 +70,7 @@ const _compacted = new Set<string>()
|
|
|
68
70
|
|
|
69
71
|
function enqueueSend(sid: string, fn: () => Promise<void>) {
|
|
70
72
|
const prev = _fwdQueue.get(sid) ?? Promise.resolve()
|
|
71
|
-
_fwdQueue.set(sid, prev.then(() => fn()
|
|
73
|
+
_fwdQueue.set(sid, prev.then(() => fn()).catch(() => {}))
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
// ============================================================
|
|
@@ -141,7 +143,7 @@ function buildCdnUploadUrl(uploadParam: string, filekey: string): string {
|
|
|
141
143
|
function initSessionCache(client: any) {
|
|
142
144
|
setTimeout(async () => {
|
|
143
145
|
try {
|
|
144
|
-
const { flat } = await
|
|
146
|
+
const { flat } = await _listSessionsByDirs(client, _projectDirs)
|
|
145
147
|
for (const s of flat) sidTitle.set(s.id, s.title)
|
|
146
148
|
log("INIT", `cached ${sidTitle.size} sessions`)
|
|
147
149
|
} catch (err) {
|
|
@@ -230,18 +232,6 @@ async function getOrCreateSession(client: any, wechatId: string, _worktree: stri
|
|
|
230
232
|
return sid
|
|
231
233
|
}
|
|
232
234
|
} catch (err) { log("SESSION_CREATE_FAIL", `${err}`) }
|
|
233
|
-
try {
|
|
234
|
-
const resp: any = await client.session.list()
|
|
235
|
-
const all: Session[] = Array.isArray(resp) ? resp : resp.data ?? []
|
|
236
|
-
const first = all.find((s: Session) => !s.parentID)
|
|
237
|
-
if (first) {
|
|
238
|
-
wechatSid.set(wechatId, first.id)
|
|
239
|
-
sidTitle.set(first.id, first.title)
|
|
240
|
-
await updateSessionIcon(client, first.id, "normal")
|
|
241
|
-
saveState()
|
|
242
|
-
return first.id
|
|
243
|
-
}
|
|
244
|
-
} catch { /* best effort */ }
|
|
245
235
|
throw new Error("No available session")
|
|
246
236
|
}
|
|
247
237
|
|
|
@@ -311,7 +301,7 @@ function loadCredentials(): WechatCredentials | null {
|
|
|
311
301
|
function saveCredentials(cred: WechatCredentials) {
|
|
312
302
|
ensureDataDir()
|
|
313
303
|
const tmp = CREDENTIALS_FILE + ".tmp"
|
|
314
|
-
try { writeFileSync(tmp, JSON.stringify(cred, null, 2), "utf-8"); renameSync(tmp, CREDENTIALS_FILE) } catch (e) { log("CRED_WRITE_ERR", `${e}`) }
|
|
304
|
+
try { writeFileSync(tmp, JSON.stringify(cred, null, 2), "utf-8"); renameSync(tmp, CREDENTIALS_FILE) } catch (e) { log("CRED_WRITE_ERR", `${e}`) } finally { try { rmSync(tmp) } catch { } }
|
|
315
305
|
}
|
|
316
306
|
|
|
317
307
|
async function validateCredentials(cred: WechatCredentials): Promise<string | null> {
|
|
@@ -338,7 +328,7 @@ function saveState() {
|
|
|
338
328
|
for (const [wx, sid] of wechatSid) { if (wx.endsWith("@im.wechat")) sm[wx] = sid }
|
|
339
329
|
const state: any = { syncBuf: syncBuffer, contextTokens: ct }
|
|
340
330
|
if (Object.keys(sm).length) state.sessionMap = sm
|
|
341
|
-
try { writeFileSync(tmp, JSON.stringify(state), "utf-8"); renameSync(tmp, STATE_FILE) } catch (e) { log("STATE_WRITE_ERR", `${e}`) }
|
|
331
|
+
try { writeFileSync(tmp, JSON.stringify(state), "utf-8"); renameSync(tmp, STATE_FILE) } catch (e) { log("STATE_WRITE_ERR", `${e}`) } finally { try { rmSync(tmp) } catch { } }
|
|
342
332
|
}
|
|
343
333
|
function loadState() {
|
|
344
334
|
try {
|
|
@@ -478,7 +468,7 @@ async function processInboundMessage(raw: any, account: WechatCredentials, clien
|
|
|
478
468
|
recentMessageKeys.add(msgKey); recentMessageOrder.push(msgKey)
|
|
479
469
|
while (recentMessageOrder.length > RECENT_KEYS_MAX) recentMessageKeys.delete(recentMessageOrder.shift()!)
|
|
480
470
|
const senderId = raw.from_user_id ?? "unknown"
|
|
481
|
-
if (raw.context_token) { contextTokens.set(senderId, raw.context_token); saveState() }
|
|
471
|
+
if (raw.context_token && contextTokens.get(senderId) !== raw.context_token) { contextTokens.set(senderId, raw.context_token); saveState() }
|
|
482
472
|
const downloadedPaths: string[] = []
|
|
483
473
|
for (const att of attachments) {
|
|
484
474
|
try { const enc = await downloadFromCdn(att.media); const pt = decryptInboundMediaPayload(enc, att.aesKey); downloadedPaths.push(saveAttachment(att.fileName || `wechat-${att.kind}`, pt)) }
|
|
@@ -529,11 +519,11 @@ function saveAttachment(fileName: string, data: Buffer): string {
|
|
|
529
519
|
function resolveDir(dirIdx: number | null, worktree: string): string { return (!dirIdx || dirIdx < 1 || dirIdx > _projectDirs.length) ? worktree : _projectDirs[dirIdx - 1] }
|
|
530
520
|
function getNick(dir: string): string { const n = basename(dir); return n || dir }
|
|
531
521
|
|
|
532
|
-
async function
|
|
522
|
+
async function _listSessionsByDirs(client: any, dirs: string[]): Promise<{ flat: Session[]; dirMap: Map<string, number> }> {
|
|
533
523
|
const flat: Session[] = []; const dm = new Map<string, number>()
|
|
534
|
-
for (let di = 0; di <
|
|
524
|
+
for (let di = 0; di < dirs.length; di++) {
|
|
535
525
|
try {
|
|
536
|
-
const resp: any = await client.session.list({ query: { directory:
|
|
526
|
+
const resp: any = await client.session.list({ query: { directory: dirs[di] } })
|
|
537
527
|
const all: Session[] = Array.isArray(resp) ? resp : resp.data ?? []
|
|
538
528
|
for (const s of all) { if (!s.parentID) { flat.push(s); dm.set(s.id, di) } }
|
|
539
529
|
} catch { /* skip */ }
|
|
@@ -541,6 +531,11 @@ async function listAllSessions(client: any): Promise<{ flat: Session[]; dirMap:
|
|
|
541
531
|
return { flat, dirMap: dm }
|
|
542
532
|
}
|
|
543
533
|
|
|
534
|
+
async function listAllSessions(client: any): Promise<{ flat: Session[]; dirMap: Map<string, number> }> {
|
|
535
|
+
_projectDirs = findProjectDirs(_worktree)
|
|
536
|
+
return _listSessionsByDirs(client, _projectDirs)
|
|
537
|
+
}
|
|
538
|
+
|
|
544
539
|
function formatDirSessions(flat: Session[], dm: Map<string, number>, cur: string | undefined): string[] {
|
|
545
540
|
const lines: string[] = []; let idx = 0
|
|
546
541
|
for (let di = 0; di < _projectDirs.length; di++) {
|
|
@@ -552,9 +547,9 @@ function formatDirSessions(flat: Session[], dm: Map<string, number>, cur: string
|
|
|
552
547
|
return lines
|
|
553
548
|
}
|
|
554
549
|
|
|
555
|
-
async function formatSessionGuide(client: any, cur: string | undefined): Promise<string> {
|
|
550
|
+
async function formatSessionGuide(client: any, cur: string | undefined, prefetched?: { flat: Session[]; dirMap: Map<string, number> }): Promise<string> {
|
|
556
551
|
try {
|
|
557
|
-
const { flat, dirMap: dm } = await listAllSessions(client)
|
|
552
|
+
const { flat, dirMap: dm } = prefetched ?? await listAllSessions(client)
|
|
558
553
|
const sl = formatDirSessions(flat, dm, cur)
|
|
559
554
|
return (sl.length ? sl.join("\n") : "(无会话)") + "\n回复 /switch <编号> 切换"
|
|
560
555
|
} catch { return "获取会话列表失败" }
|
|
@@ -576,13 +571,14 @@ async function handleCommand(cmd: string, senderId: string, account: WechatCrede
|
|
|
576
571
|
if (pv && pv !== m.id) { const pt = flat.find(s => s.id === pv)?.title; await stripSessionIcon(client, pv, pt) }
|
|
577
572
|
await updateSessionIcon(client, m.id, "normal"); await wx(`已切换到: ${m.title}`) } catch { await wx("切换失败") }; break }
|
|
578
573
|
case "unbind": case "解绑": { const old = wechatSid.get(senderId)
|
|
574
|
+
let fetched: { flat: Session[]; dirMap: Map<string, number> } | undefined
|
|
579
575
|
let oldTitle = sidTitle.get(old)
|
|
580
576
|
if (old) {
|
|
581
|
-
try {
|
|
582
|
-
if (oldTitle && ICON_PREFIXES.some(p => oldTitle.startsWith(p))) { await stripSessionIcon(client, old, oldTitle) }
|
|
577
|
+
try { fetched = await listAllSessions(client); const found = fetched.flat.find(s => s.id === old); if (found) oldTitle = found.title } catch { /* */ }
|
|
583
578
|
wechatSid.delete(senderId); saveState()
|
|
579
|
+
if (oldTitle && ICON_PREFIXES.some(p => oldTitle.startsWith(p))) { await stripSessionIcon(client, old, oldTitle) }
|
|
584
580
|
}
|
|
585
|
-
await wx(`WeChat 桥接\n${await formatSessionGuide(client, undefined)}`); break }
|
|
581
|
+
await wx(`WeChat 桥接\n${await formatSessionGuide(client, undefined, fetched)}`); break }
|
|
586
582
|
case "rename": case "改名": { const nn = args.join(" ").trim(); if (!nn) { await wx("请指定标题"); break }; const sid = wechatSid.get(senderId); if (!sid) { await wx("未绑定"); break }
|
|
587
583
|
try { await client.session.update({ path: { id: sid }, body: { title: nn } }); sidTitle.set(sid, nn); await updateSessionIcon(client, sid, "normal"); await wx(`已改名: ${t(sid)}`) } catch { await wx("改名失败") }; break }
|
|
588
584
|
case "mode": case "模式": { const sid = wechatSid.get(senderId); if (!sid) { await wx("未绑定"); break }
|
|
@@ -670,7 +666,7 @@ export const WechatBridgePlugin: Plugin = async ({ client, worktree }) => {
|
|
|
670
666
|
try { await client.app.log({ body: { service: "wechat-bridge", level: "info", message: "plugin loaded" } }) } catch { /* */ }
|
|
671
667
|
migrateOldDataDir()
|
|
672
668
|
migrateOldStateFiles(worktree)
|
|
673
|
-
_projectDirs = findProjectDirs(worktree)
|
|
669
|
+
_projectDirs = findProjectDirs(worktree); _worktree = worktree
|
|
674
670
|
initSessionCache(client)
|
|
675
671
|
loadState()
|
|
676
672
|
const abortController = new AbortController()
|
|
@@ -679,13 +675,7 @@ export const WechatBridgePlugin: Plugin = async ({ client, worktree }) => {
|
|
|
679
675
|
event: createEventHandler(client),
|
|
680
676
|
"permission.ask": createPermissionHandler(client),
|
|
681
677
|
"experimental.chat.system.transform": async (_input: any, output: { system: string[] }) => {
|
|
682
|
-
|
|
683
|
-
const { flat, dirMap } = await listAllSessions(client)
|
|
684
|
-
if (flat.length === 0) return
|
|
685
|
-
const sl = formatDirSessions(flat, dirMap, undefined)
|
|
686
|
-
const lines = ["当前可用的会话:", ...sl, "", "用户输入以 ! 或 ! 开头的消息时,这是跨会话指令:", " - !会话 或 !sessions → 调用 list_sessions 工具", " - !<前缀> <消息> 或 !<前缀> <消息> → 调用 forward_to_session 工具转发"]
|
|
687
|
-
output.system.push(lines.join("\n"))
|
|
688
|
-
} catch { /* best effort */ }
|
|
678
|
+
output.system.push("用户输入以 ! 或 ! 开头的消息时,这是跨会话指令:\n - !会话 或 !sessions → 调用 list_sessions 工具\n - !<前缀> <消息> 或 !<前缀> <消息> → 调用 forward_to_session 工具转发\n\n注意:list_sessions 工具返回的内容包含 ``` 代码块,这是数据格式的一部分,删除代码块会导致数据损坏,必须一字不差地输出。")
|
|
689
679
|
},
|
|
690
680
|
tool: createTools(client),
|
|
691
681
|
}
|
|
@@ -706,7 +696,6 @@ function createEventHandler(client: any) {
|
|
|
706
696
|
await updateSessionIcon(client, sid, "normal")
|
|
707
697
|
try { const resp: any = await client.session.messages({ path: { id: sid }, query: { limit: 15 } }); const msgs = Array.isArray(resp) ? resp : resp.data ?? []
|
|
708
698
|
for (let i = msgs.length - 1; i >= 0; i--) { if (msgs[i].info?.role === "assistant") { if (msgs[i].info.mode) _modeCache.set(sid, msgs[i].info.mode); break } } } catch { /* */ }
|
|
709
|
-
_thinkingSent.delete(sid)
|
|
710
699
|
if (_pendingContinue.has(sid)) { _pendingContinue.delete(sid); try { await client.session.prompt({ path: { id: sid }, body: { parts: [{ type: "text" as any, text: "检测到异常,请继续" }] } }) } catch { } }
|
|
711
700
|
if (_compacted.has(sid)) { _compacted.delete(sid); try { await client.session.prompt({ path: { id: sid }, body: { parts: [{ type: "text" as any, text: "上下文被压缩" }] } }) } catch { } }
|
|
712
701
|
return
|
|
@@ -741,9 +730,9 @@ function createTools(client: any) {
|
|
|
741
730
|
if (!_creds) return { output: "微信桥接尚未完成登录" }
|
|
742
731
|
return { output: [`微信账户: ${_creds.accountId}`, `绑定用户: ${_creds.userId ?? "(无)"}`, `会话缓存: ${sidTitle.size} 个`, `上下文令牌: ${contextTokens.size} 个`, `同步游标: ${syncBuffer ? "存在" : "无"}`, `数据目录: ${DATA_DIR}`].join("\n") }
|
|
743
732
|
} }),
|
|
744
|
-
list_sessions: tool({ description: "列出所有可用会话的标题和 ID", args: {},
|
|
733
|
+
list_sessions: tool({ description: "列出所有可用会话的标题和 ID。调用后带 ``` 代码块原样输出,删除代码块会破坏数据格式,必须保留。", args: {},
|
|
745
734
|
execute: async (_args: any, ctx: any) => {
|
|
746
|
-
try { const { flat, dirMap } = await listAllSessions(client); if (flat.length === 0) return { output: "暂无会话" };
|
|
735
|
+
try { const { flat, dirMap } = await listAllSessions(client); if (flat.length === 0) return { output: "暂无会话" }; const body = formatDirSessions(flat, dirMap, ctx?.sessionID).join("\n"); return { output: "```\n" + body + "\n```" } } catch { return { output: "获取会话列表失败" } }
|
|
747
736
|
} }),
|
|
748
737
|
forward_to_session: tool({ description: "转发消息到标题前缀匹配的会话。用户说「转发」时使用此工具", args: { prefix: tool.schema.string().describe("目标会话标题前缀"), message: tool.schema.string().describe("要转发的消息内容") },
|
|
749
738
|
execute: async (args: any, ctx: any) => {
|