@wenbin_wb/dsh-bridge 2.10.7 → 2.10.9
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 +26 -0
- package/README.en.md +27 -0
- package/README.md +27 -0
- package/client/client.js +388 -5
- package/client/index.js +327 -3
- package/client/mobile-styles.js +68 -0
- package/docs/telegram-usage.md +1 -1
- package/lib/auth/dsh-native-cookie.js +148 -0
- package/lib/auth/manager.js +54 -4
- package/lib/bridge-rpc-constants.js +1 -0
- package/lib/bridge-rpc.js +17 -2
- package/lib/compat.js +129 -129
- package/lib/connection-compat.js +115 -0
- package/lib/feishu/index.js +225 -225
- package/lib/feishu/node.js +439 -439
- package/lib/index.js +163 -28
- package/lib/platform/base.js +147 -147
- package/lib/platform/commands.js +221 -221
- package/lib/platform/conversation-bridge.js +821 -821
- package/lib/platform/dsh-storage.js +117 -117
- package/lib/platform/index.js +10 -10
- package/lib/platform/message-split.js +229 -229
- package/lib/platform/session-catalog.js +372 -372
- package/lib/platform/stream-slices.js +21 -21
- package/lib/qq/index.js +312 -312
- package/lib/telegram/index.js +216 -216
- package/lib/telegram/node.js +322 -322
- package/lib/wechat/gateway.js +986 -986
- package/lib/wechat/index.js +244 -244
- package/lib/wechat/media.js +285 -285
- package/lib/wechat/node.js +352 -352
- package/package.json +2 -2
package/lib/platform/commands.js
CHANGED
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
// 斜杠命令解释器(/sessions /use /new /workspaces /status /help …)
|
|
2
|
-
// 自 conversation-bridge.js 拆出。routeCommand(node, text, senderId) 只通过 node 参数
|
|
3
|
-
// 访问会话桥能力,目录/渲染逻辑来自 session-catalog.js。
|
|
4
|
-
import {
|
|
5
|
-
listSessions, listWorkspaces, validateWorkspacePath, renderSessions,
|
|
6
|
-
sessionsInDisplayOrder, describeTurnEnd, helpText, fmtTime, fmtSessionId,
|
|
7
|
-
sessionLabel, getWorkspaceBasename,
|
|
8
|
-
} from './session-catalog.js'
|
|
9
|
-
import { isSafeWorkspacePath } from '../security/path-validator.js'
|
|
10
|
-
import { basename, normalize } from 'node:path'
|
|
11
|
-
|
|
12
|
-
export async function routeCommand(node, text, senderId = null) {
|
|
13
|
-
const trimmed = text.trim()
|
|
14
|
-
|
|
15
|
-
if (trimmed === '/yes' || trimmed === '/no' || /^[12]$/.test(trimmed)) {
|
|
16
|
-
if (node.resolveApproval(trimmed, senderId)) return true
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (!trimmed.startsWith('/')) return false
|
|
20
|
-
|
|
21
|
-
const [command, ...rest] = trimmed.slice(1).split(/\s+/)
|
|
22
|
-
switch (command) {
|
|
23
|
-
case 'help':
|
|
24
|
-
await node.sendText(helpText())
|
|
25
|
-
return true
|
|
26
|
-
case 'sessions':
|
|
27
|
-
case 'list':
|
|
28
|
-
await node.sendText(await renderSessions(node))
|
|
29
|
-
return true
|
|
30
|
-
case 'use':
|
|
31
|
-
case 'resume': {
|
|
32
|
-
const index = Number(rest[0])
|
|
33
|
-
const sessions = sessionsInDisplayOrder(await listSessions(node))
|
|
34
|
-
if (!Number.isInteger(index) || index < 1 || index > sessions.length) {
|
|
35
|
-
await node.sendText(`❌ **无效会话编号**:\`${rest[0] ?? ''}\`\n\n> 可用编号范围:\`1 – ${sessions.length}\`(发送 \`/sessions\` 查看会话列表)`)
|
|
36
|
-
return true
|
|
37
|
-
}
|
|
38
|
-
const session = sessions[index - 1]
|
|
39
|
-
node.setActiveSessionById(session.id)
|
|
40
|
-
const title = session.title || (session.events ? sessionLabel(session) : '')
|
|
41
|
-
const titleLine = title ? `\n- **标题**:${title}` : ''
|
|
42
|
-
await node.sendText(`✓ **已切换到会话 #${index}**${titleLine}\n- **会话 ID**:\`${fmtSessionId(session.id)}\``)
|
|
43
|
-
return true
|
|
44
|
-
}
|
|
45
|
-
case 'rename': {
|
|
46
|
-
if (!node.activeSessionId) {
|
|
47
|
-
await node.sendText(`❌ **当前没有活动会话**\n\n> 请先使用 \`/sessions\` 查看会话列表并通过 \`/use 编号\` 切换到目标会话,或通过 \`/new <提示词>\` 创建新会话。`)
|
|
48
|
-
return true
|
|
49
|
-
}
|
|
50
|
-
const newTitle = rest.join(' ').trim()
|
|
51
|
-
if (!newTitle) {
|
|
52
|
-
await node.sendText(`❌ **缺少新标题参数**\n\n> 用法:\`/rename <新标题>\`\n> 示例:\`/rename 优化登录交互逻辑\``)
|
|
53
|
-
return true
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const session = node.activeSession()
|
|
58
|
-
if (session) {
|
|
59
|
-
session.title = newTitle
|
|
60
|
-
}
|
|
61
|
-
if (node.ctx.sessionPersistence?.update) {
|
|
62
|
-
await node.ctx.sessionPersistence.update(node.activeSessionId, { title: newTitle }).catch(() => {})
|
|
63
|
-
}
|
|
64
|
-
await node.sendText(`✓ **会话重命名成功**\n- **会话 ID**:\`${fmtSessionId(node.activeSessionId)}\`\n- **新标题**:${newTitle}`)
|
|
65
|
-
} catch (err) {
|
|
66
|
-
await node.sendText(`❌ **重命名失败**:${err instanceof Error ? err.message : String(err)}`)
|
|
67
|
-
}
|
|
68
|
-
return true
|
|
69
|
-
}
|
|
70
|
-
case 'workspaces': {
|
|
71
|
-
const workspaces = await listWorkspaces(node)
|
|
72
|
-
if (workspaces.length === 0) {
|
|
73
|
-
await node.sendText(`## 🗂️ 可用工作区\n\n> 当前没有已注册的工作区。可使用 \`/new <提示词> @<路径>\` 指定项目目录。`)
|
|
74
|
-
return true
|
|
75
|
-
}
|
|
76
|
-
const rows = workspaces.map((w, i) => {
|
|
77
|
-
const titleStr = w.title && w.title !== w.path ? w.title : getWorkspaceBasename(w.path)
|
|
78
|
-
const safeTitle = titleStr.replace(/\|/g, '|')
|
|
79
|
-
return `| **@${i + 1}** | ${safeTitle} | \`${w.path}\` |`
|
|
80
|
-
})
|
|
81
|
-
await node.sendText([
|
|
82
|
-
`## 🗂️ 可用工作区 (共 ${workspaces.length} 个)`,
|
|
83
|
-
`> 新建会话:发送 \`/new <提示词> @序号\` 或 \`/new <提示词> @路径\``,
|
|
84
|
-
'',
|
|
85
|
-
'| 序号 | 工作区名称 | 目录路径 |',
|
|
86
|
-
'| :--- | :--- | :--- |',
|
|
87
|
-
...rows,
|
|
88
|
-
].join('\n'))
|
|
89
|
-
return true
|
|
90
|
-
}
|
|
91
|
-
case 'addworkspace': {
|
|
92
|
-
const targetPath = rest.join(' ').trim()
|
|
93
|
-
if (!targetPath) {
|
|
94
|
-
await node.sendText(`❌ **缺少工作区路径**\n\n> 用法:\`/addworkspace <电脑绝对路径>\`\n> 示例:\`/addworkspace D:\\IdeaProjects\\my-app\``)
|
|
95
|
-
return true
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const safetyCheck = await isSafeWorkspacePath(targetPath)
|
|
99
|
-
if (!safetyCheck.valid) {
|
|
100
|
-
await node.sendText(`⚠️ **${safetyCheck.error || '路径安全校验未通过'}**:\`${targetPath}\`\n\n> 出于安全考虑,禁止将系统关键目录或敏感配置文件所在路径登记为工作区。`)
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
const resolved = safetyCheck.path
|
|
104
|
-
const title = basename(resolved) || resolved
|
|
105
|
-
if (node.ctx.workspaceRegistry?.add) {
|
|
106
|
-
await node.ctx.workspaceRegistry.add({ path: resolved, title }).catch(() => {})
|
|
107
|
-
} else if (node.ctx.workspaceRegistry?.register) {
|
|
108
|
-
await node.ctx.workspaceRegistry.register({ path: resolved, title }).catch(() => {})
|
|
109
|
-
}
|
|
110
|
-
const workspaces = await listWorkspaces(node)
|
|
111
|
-
const foundIndex = workspaces.findIndex(w => normalize(w.path) === normalize(resolved))
|
|
112
|
-
const numStr = foundIndex >= 0 ? `@${foundIndex + 1}` : ''
|
|
113
|
-
await node.sendText([
|
|
114
|
-
`✓ **工作区添加成功**!`,
|
|
115
|
-
`- **名称**:${title}`,
|
|
116
|
-
`- **路径**:\`${resolved}\``,
|
|
117
|
-
foundIndex >= 0 ? `- **快捷编号**:\`${numStr}\`` : '',
|
|
118
|
-
'',
|
|
119
|
-
`> 发送 \`/new <提示词> ${numStr || '@' + resolved}\` 即可直接在此工作区创建会话。`,
|
|
120
|
-
].filter(Boolean).join('\n'))
|
|
121
|
-
} catch (err) {
|
|
122
|
-
await node.sendText(`❌ **添加工作区失败**:${err instanceof Error ? err.message : String(err)}`)
|
|
123
|
-
}
|
|
124
|
-
return true
|
|
125
|
-
}
|
|
126
|
-
case 'new': {
|
|
127
|
-
// 解析尾部 @N 或 @路径 作为工作区 cwd
|
|
128
|
-
const args = rest.join(' ').trim()
|
|
129
|
-
let cwd
|
|
130
|
-
let prompt = args
|
|
131
|
-
const atMatch = args.match(/\s+@(\S+)$/)
|
|
132
|
-
if (atMatch) {
|
|
133
|
-
prompt = args.slice(0, atMatch.index).trim()
|
|
134
|
-
const sel = atMatch[1]
|
|
135
|
-
const workspaces = await listWorkspaces(node)
|
|
136
|
-
if (/^\d+$/.test(sel)) {
|
|
137
|
-
const idx = Number(sel)
|
|
138
|
-
const ws = workspaces[idx - 1]
|
|
139
|
-
if (ws) cwd = ws.path
|
|
140
|
-
else {
|
|
141
|
-
await node.sendText(`❌ **无效工作区编号**:\`${sel}\`\n\n> 请发送 \`/workspaces\` 查看可用工作区列表与编号。`)
|
|
142
|
-
return true
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
// 直接指定路径时,规范化并校验(必须完全匹配已注册工作区)
|
|
146
|
-
const validation = await validateWorkspacePath(node, sel)
|
|
147
|
-
if (!validation.valid) {
|
|
148
|
-
await node.sendText(validation.error)
|
|
149
|
-
return true
|
|
150
|
-
}
|
|
151
|
-
cwd = validation.path
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
await node.createSession(prompt, cwd)
|
|
155
|
-
return true
|
|
156
|
-
}
|
|
157
|
-
case 'stop': {
|
|
158
|
-
node.stopAllHeartbeats()
|
|
159
|
-
const agent = node.activeAgent()
|
|
160
|
-
if (!agent) {
|
|
161
|
-
await node.sendText(`ℹ️ **当前没有正在运行的 Agent 任务**`)
|
|
162
|
-
} else {
|
|
163
|
-
agent.cancel({ kind: 'user' })
|
|
164
|
-
await node.sendText(`⏹ **已请求停止当前任务**`)
|
|
165
|
-
}
|
|
166
|
-
return true
|
|
167
|
-
}
|
|
168
|
-
case 'end': {
|
|
169
|
-
node.stopAllHeartbeats()
|
|
170
|
-
// 结束当前会话:停止 agent 并清除活动会话(进入"没有活动会话"状态)
|
|
171
|
-
const agent = node.activeAgent()
|
|
172
|
-
if (agent) agent.cancel({ kind: 'user' })
|
|
173
|
-
node.activeSessionId = null
|
|
174
|
-
await node.onActiveSessionChange?.(null)
|
|
175
|
-
await node.sendText(`✓ **已结束当前会话**(没有活动会话)。\n\n> **后续操作**:\n> - \`/new <提示词>\` — 新建会话并开始\n> - \`/sessions\` — 查看历史会话列表\n> - \`/help\` — 查看常用指令帮助`)
|
|
176
|
-
return true
|
|
177
|
-
}
|
|
178
|
-
case 'status': {
|
|
179
|
-
const agent = node.activeAgent()
|
|
180
|
-
const session = node.activeSession()
|
|
181
|
-
if (!session) {
|
|
182
|
-
await node.sendText(`## 📊 Agent 状态看板\n\n> 当前没有活动会话。\n> 发送 \`/new <提示词>\` 开始新任务,或发送 \`/sessions\` 查看已有会话。`)
|
|
183
|
-
return true
|
|
184
|
-
}
|
|
185
|
-
const statusMap = {
|
|
186
|
-
idle: '空闲 (idle)',
|
|
187
|
-
running: '运行中 (running)',
|
|
188
|
-
paused: '已暂停 (paused)',
|
|
189
|
-
error: '异常 (error)',
|
|
190
|
-
}
|
|
191
|
-
const status = statusMap[agent?.status] || (agent?.status ?? '空闲 (idle)')
|
|
192
|
-
const lastTurn = [...(session.events ?? [])].reverse().find((e) => e.type === 'turn/end')
|
|
193
|
-
const reason = lastTurn ? describeTurnEnd(lastTurn.data.reason) : '尚未运行'
|
|
194
|
-
const title = session.title || (session.events ? sessionLabel(session) : '')
|
|
195
|
-
const shortId = fmtSessionId(session.id)
|
|
196
|
-
const cwd = session.header?.cwd || node.config?.cwd || ''
|
|
197
|
-
|
|
198
|
-
const content = [
|
|
199
|
-
`## 📊 Agent 状态看板`,
|
|
200
|
-
'',
|
|
201
|
-
'| 属性 | 当前状态 / 参数 |',
|
|
202
|
-
'| :--- | :--- |',
|
|
203
|
-
`| **会话 ID** | \`${shortId}\` |`,
|
|
204
|
-
...(title ? [`| **会话标题** | ${title.replace(/\|/g, '|')} |`] : []),
|
|
205
|
-
...(cwd ? [`| **工作区** | \`${cwd}\` |`] : []),
|
|
206
|
-
`| **Agent 状态** | ${status} |`,
|
|
207
|
-
`| **累计事件** | ${session.seq ?? 0} 条 |`,
|
|
208
|
-
`| **最近执行** | ${reason} |`,
|
|
209
|
-
].join('\n')
|
|
210
|
-
|
|
211
|
-
await node.sendText(content)
|
|
212
|
-
return true
|
|
213
|
-
}
|
|
214
|
-
case 'start': // 别名:首次扫码自动开始一个会话
|
|
215
|
-
await node.createSession('')
|
|
216
|
-
return true
|
|
217
|
-
default:
|
|
218
|
-
await node.sendText(`❌ **未知指令**:\`/${command}\`\n\n${helpText()}`)
|
|
219
|
-
return true
|
|
220
|
-
}
|
|
221
|
-
}
|
|
1
|
+
// 斜杠命令解释器(/sessions /use /new /workspaces /status /help …)
|
|
2
|
+
// 自 conversation-bridge.js 拆出。routeCommand(node, text, senderId) 只通过 node 参数
|
|
3
|
+
// 访问会话桥能力,目录/渲染逻辑来自 session-catalog.js。
|
|
4
|
+
import {
|
|
5
|
+
listSessions, listWorkspaces, validateWorkspacePath, renderSessions,
|
|
6
|
+
sessionsInDisplayOrder, describeTurnEnd, helpText, fmtTime, fmtSessionId,
|
|
7
|
+
sessionLabel, getWorkspaceBasename,
|
|
8
|
+
} from './session-catalog.js'
|
|
9
|
+
import { isSafeWorkspacePath } from '../security/path-validator.js'
|
|
10
|
+
import { basename, normalize } from 'node:path'
|
|
11
|
+
|
|
12
|
+
export async function routeCommand(node, text, senderId = null) {
|
|
13
|
+
const trimmed = text.trim()
|
|
14
|
+
|
|
15
|
+
if (trimmed === '/yes' || trimmed === '/no' || /^[12]$/.test(trimmed)) {
|
|
16
|
+
if (node.resolveApproval(trimmed, senderId)) return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!trimmed.startsWith('/')) return false
|
|
20
|
+
|
|
21
|
+
const [command, ...rest] = trimmed.slice(1).split(/\s+/)
|
|
22
|
+
switch (command) {
|
|
23
|
+
case 'help':
|
|
24
|
+
await node.sendText(helpText())
|
|
25
|
+
return true
|
|
26
|
+
case 'sessions':
|
|
27
|
+
case 'list':
|
|
28
|
+
await node.sendText(await renderSessions(node))
|
|
29
|
+
return true
|
|
30
|
+
case 'use':
|
|
31
|
+
case 'resume': {
|
|
32
|
+
const index = Number(rest[0])
|
|
33
|
+
const sessions = sessionsInDisplayOrder(await listSessions(node))
|
|
34
|
+
if (!Number.isInteger(index) || index < 1 || index > sessions.length) {
|
|
35
|
+
await node.sendText(`❌ **无效会话编号**:\`${rest[0] ?? ''}\`\n\n> 可用编号范围:\`1 – ${sessions.length}\`(发送 \`/sessions\` 查看会话列表)`)
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
const session = sessions[index - 1]
|
|
39
|
+
node.setActiveSessionById(session.id)
|
|
40
|
+
const title = session.title || (session.events ? sessionLabel(session) : '')
|
|
41
|
+
const titleLine = title ? `\n- **标题**:${title}` : ''
|
|
42
|
+
await node.sendText(`✓ **已切换到会话 #${index}**${titleLine}\n- **会话 ID**:\`${fmtSessionId(session.id)}\``)
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
case 'rename': {
|
|
46
|
+
if (!node.activeSessionId) {
|
|
47
|
+
await node.sendText(`❌ **当前没有活动会话**\n\n> 请先使用 \`/sessions\` 查看会话列表并通过 \`/use 编号\` 切换到目标会话,或通过 \`/new <提示词>\` 创建新会话。`)
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
const newTitle = rest.join(' ').trim()
|
|
51
|
+
if (!newTitle) {
|
|
52
|
+
await node.sendText(`❌ **缺少新标题参数**\n\n> 用法:\`/rename <新标题>\`\n> 示例:\`/rename 优化登录交互逻辑\``)
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const session = node.activeSession()
|
|
58
|
+
if (session) {
|
|
59
|
+
session.title = newTitle
|
|
60
|
+
}
|
|
61
|
+
if (node.ctx.sessionPersistence?.update) {
|
|
62
|
+
await node.ctx.sessionPersistence.update(node.activeSessionId, { title: newTitle }).catch(() => {})
|
|
63
|
+
}
|
|
64
|
+
await node.sendText(`✓ **会话重命名成功**\n- **会话 ID**:\`${fmtSessionId(node.activeSessionId)}\`\n- **新标题**:${newTitle}`)
|
|
65
|
+
} catch (err) {
|
|
66
|
+
await node.sendText(`❌ **重命名失败**:${err instanceof Error ? err.message : String(err)}`)
|
|
67
|
+
}
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
case 'workspaces': {
|
|
71
|
+
const workspaces = await listWorkspaces(node)
|
|
72
|
+
if (workspaces.length === 0) {
|
|
73
|
+
await node.sendText(`## 🗂️ 可用工作区\n\n> 当前没有已注册的工作区。可使用 \`/new <提示词> @<路径>\` 指定项目目录。`)
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
const rows = workspaces.map((w, i) => {
|
|
77
|
+
const titleStr = w.title && w.title !== w.path ? w.title : getWorkspaceBasename(w.path)
|
|
78
|
+
const safeTitle = titleStr.replace(/\|/g, '|')
|
|
79
|
+
return `| **@${i + 1}** | ${safeTitle} | \`${w.path}\` |`
|
|
80
|
+
})
|
|
81
|
+
await node.sendText([
|
|
82
|
+
`## 🗂️ 可用工作区 (共 ${workspaces.length} 个)`,
|
|
83
|
+
`> 新建会话:发送 \`/new <提示词> @序号\` 或 \`/new <提示词> @路径\``,
|
|
84
|
+
'',
|
|
85
|
+
'| 序号 | 工作区名称 | 目录路径 |',
|
|
86
|
+
'| :--- | :--- | :--- |',
|
|
87
|
+
...rows,
|
|
88
|
+
].join('\n'))
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
case 'addworkspace': {
|
|
92
|
+
const targetPath = rest.join(' ').trim()
|
|
93
|
+
if (!targetPath) {
|
|
94
|
+
await node.sendText(`❌ **缺少工作区路径**\n\n> 用法:\`/addworkspace <电脑绝对路径>\`\n> 示例:\`/addworkspace D:\\IdeaProjects\\my-app\``)
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const safetyCheck = await isSafeWorkspacePath(targetPath)
|
|
99
|
+
if (!safetyCheck.valid) {
|
|
100
|
+
await node.sendText(`⚠️ **${safetyCheck.error || '路径安全校验未通过'}**:\`${targetPath}\`\n\n> 出于安全考虑,禁止将系统关键目录或敏感配置文件所在路径登记为工作区。`)
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
const resolved = safetyCheck.path
|
|
104
|
+
const title = basename(resolved) || resolved
|
|
105
|
+
if (node.ctx.workspaceRegistry?.add) {
|
|
106
|
+
await node.ctx.workspaceRegistry.add({ path: resolved, title }).catch(() => {})
|
|
107
|
+
} else if (node.ctx.workspaceRegistry?.register) {
|
|
108
|
+
await node.ctx.workspaceRegistry.register({ path: resolved, title }).catch(() => {})
|
|
109
|
+
}
|
|
110
|
+
const workspaces = await listWorkspaces(node)
|
|
111
|
+
const foundIndex = workspaces.findIndex(w => normalize(w.path) === normalize(resolved))
|
|
112
|
+
const numStr = foundIndex >= 0 ? `@${foundIndex + 1}` : ''
|
|
113
|
+
await node.sendText([
|
|
114
|
+
`✓ **工作区添加成功**!`,
|
|
115
|
+
`- **名称**:${title}`,
|
|
116
|
+
`- **路径**:\`${resolved}\``,
|
|
117
|
+
foundIndex >= 0 ? `- **快捷编号**:\`${numStr}\`` : '',
|
|
118
|
+
'',
|
|
119
|
+
`> 发送 \`/new <提示词> ${numStr || '@' + resolved}\` 即可直接在此工作区创建会话。`,
|
|
120
|
+
].filter(Boolean).join('\n'))
|
|
121
|
+
} catch (err) {
|
|
122
|
+
await node.sendText(`❌ **添加工作区失败**:${err instanceof Error ? err.message : String(err)}`)
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
case 'new': {
|
|
127
|
+
// 解析尾部 @N 或 @路径 作为工作区 cwd
|
|
128
|
+
const args = rest.join(' ').trim()
|
|
129
|
+
let cwd
|
|
130
|
+
let prompt = args
|
|
131
|
+
const atMatch = args.match(/\s+@(\S+)$/)
|
|
132
|
+
if (atMatch) {
|
|
133
|
+
prompt = args.slice(0, atMatch.index).trim()
|
|
134
|
+
const sel = atMatch[1]
|
|
135
|
+
const workspaces = await listWorkspaces(node)
|
|
136
|
+
if (/^\d+$/.test(sel)) {
|
|
137
|
+
const idx = Number(sel)
|
|
138
|
+
const ws = workspaces[idx - 1]
|
|
139
|
+
if (ws) cwd = ws.path
|
|
140
|
+
else {
|
|
141
|
+
await node.sendText(`❌ **无效工作区编号**:\`${sel}\`\n\n> 请发送 \`/workspaces\` 查看可用工作区列表与编号。`)
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
// 直接指定路径时,规范化并校验(必须完全匹配已注册工作区)
|
|
146
|
+
const validation = await validateWorkspacePath(node, sel)
|
|
147
|
+
if (!validation.valid) {
|
|
148
|
+
await node.sendText(validation.error)
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
cwd = validation.path
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
await node.createSession(prompt, cwd)
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
case 'stop': {
|
|
158
|
+
node.stopAllHeartbeats()
|
|
159
|
+
const agent = node.activeAgent()
|
|
160
|
+
if (!agent) {
|
|
161
|
+
await node.sendText(`ℹ️ **当前没有正在运行的 Agent 任务**`)
|
|
162
|
+
} else {
|
|
163
|
+
agent.cancel({ kind: 'user' })
|
|
164
|
+
await node.sendText(`⏹ **已请求停止当前任务**`)
|
|
165
|
+
}
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
case 'end': {
|
|
169
|
+
node.stopAllHeartbeats()
|
|
170
|
+
// 结束当前会话:停止 agent 并清除活动会话(进入"没有活动会话"状态)
|
|
171
|
+
const agent = node.activeAgent()
|
|
172
|
+
if (agent) agent.cancel({ kind: 'user' })
|
|
173
|
+
node.activeSessionId = null
|
|
174
|
+
await node.onActiveSessionChange?.(null)
|
|
175
|
+
await node.sendText(`✓ **已结束当前会话**(没有活动会话)。\n\n> **后续操作**:\n> - \`/new <提示词>\` — 新建会话并开始\n> - \`/sessions\` — 查看历史会话列表\n> - \`/help\` — 查看常用指令帮助`)
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
case 'status': {
|
|
179
|
+
const agent = node.activeAgent()
|
|
180
|
+
const session = node.activeSession()
|
|
181
|
+
if (!session) {
|
|
182
|
+
await node.sendText(`## 📊 Agent 状态看板\n\n> 当前没有活动会话。\n> 发送 \`/new <提示词>\` 开始新任务,或发送 \`/sessions\` 查看已有会话。`)
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
const statusMap = {
|
|
186
|
+
idle: '空闲 (idle)',
|
|
187
|
+
running: '运行中 (running)',
|
|
188
|
+
paused: '已暂停 (paused)',
|
|
189
|
+
error: '异常 (error)',
|
|
190
|
+
}
|
|
191
|
+
const status = statusMap[agent?.status] || (agent?.status ?? '空闲 (idle)')
|
|
192
|
+
const lastTurn = [...(session.events ?? [])].reverse().find((e) => e.type === 'turn/end')
|
|
193
|
+
const reason = lastTurn ? describeTurnEnd(lastTurn.data.reason) : '尚未运行'
|
|
194
|
+
const title = session.title || (session.events ? sessionLabel(session) : '')
|
|
195
|
+
const shortId = fmtSessionId(session.id)
|
|
196
|
+
const cwd = session.header?.cwd || node.config?.cwd || ''
|
|
197
|
+
|
|
198
|
+
const content = [
|
|
199
|
+
`## 📊 Agent 状态看板`,
|
|
200
|
+
'',
|
|
201
|
+
'| 属性 | 当前状态 / 参数 |',
|
|
202
|
+
'| :--- | :--- |',
|
|
203
|
+
`| **会话 ID** | \`${shortId}\` |`,
|
|
204
|
+
...(title ? [`| **会话标题** | ${title.replace(/\|/g, '|')} |`] : []),
|
|
205
|
+
...(cwd ? [`| **工作区** | \`${cwd}\` |`] : []),
|
|
206
|
+
`| **Agent 状态** | ${status} |`,
|
|
207
|
+
`| **累计事件** | ${session.seq ?? 0} 条 |`,
|
|
208
|
+
`| **最近执行** | ${reason} |`,
|
|
209
|
+
].join('\n')
|
|
210
|
+
|
|
211
|
+
await node.sendText(content)
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
case 'start': // 别名:首次扫码自动开始一个会话
|
|
215
|
+
await node.createSession('')
|
|
216
|
+
return true
|
|
217
|
+
default:
|
|
218
|
+
await node.sendText(`❌ **未知指令**:\`/${command}\`\n\n${helpText()}`)
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
}
|