@wenbin_wb/dsh-bridge 2.2.7 → 2.2.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.
@@ -634,14 +634,16 @@ export class ConversationBridge {
634
634
  const timeoutSec = this.config.approvalTimeoutSec
635
635
  const timeoutMin = Math.max(1, Math.round(timeoutSec / 60))
636
636
  const prompt = [
637
- `### ⚠️ 操作权限确认 (#${number})`,
637
+ `## ⚠️ 操作权限确认 (#${number})`,
638
638
  '',
639
- `- **调用工具**:\`${req.toolName}\``,
640
- ...(req.reason ? [`- **申请原因**:${req.reason}`] : []),
641
- `- **等待超时**:${timeoutMin} 分钟内未回复将自动拒绝`,
639
+ '| 项目 | 详情 |',
640
+ '| :--- | :--- |',
641
+ `| **调用工具** | \`${req.toolName}\` |`,
642
+ ...(req.reason ? [`| **申请原因** | ${String(req.reason).replace(/\|/g, '|')} |`] : []),
643
+ `| **等待超时** | ${timeoutMin} 分钟 (超时自动拒绝) |`,
642
644
  '',
643
- `> 回复 \`/yes\`(或 \`1\`)批准执行`,
644
- `> 回复 \`/no\`(或 \`2\`)拒绝执行`,
645
+ `> 回复 \`/yes\` (或 \`1\`) 批准执行`,
646
+ `> 回复 \`/no\` (或 \`2\`) 拒绝执行`,
645
647
  ].join('\n')
646
648
 
647
649
  void this.sendText(prompt)
@@ -824,14 +826,22 @@ async function routeCommand(node, text) {
824
826
  case 'workspaces': {
825
827
  const workspaces = await listWorkspaces(node)
826
828
  if (workspaces.length === 0) {
827
- await node.sendText(`### 🗂️ 可用工作区\n\n> ℹ️ 当前没有已注册的工作区。可使用 \`/new <提示词> @<路径>\` 指定项目目录。`)
829
+ await node.sendText(`## 🗂️ 可用工作区\n\n> 当前没有已注册的工作区。可使用 \`/new <提示词> @<路径>\` 指定项目目录。`)
828
830
  return true
829
831
  }
830
- const lines = workspaces.map((w, i) => {
831
- const titleStr = w.title && w.title !== w.path ? `**${w.title}**` : `**${getWorkspaceBasename(w.path)}**`
832
- return `- **[${i + 1}]** ${titleStr}\n \`${w.path}\``
832
+ const rows = workspaces.map((w, i) => {
833
+ const titleStr = w.title && w.title !== w.path ? w.title : getWorkspaceBasename(w.path)
834
+ const safeTitle = titleStr.replace(/\|/g, '|')
835
+ return `| **@${i + 1}** | ${safeTitle} | \`${w.path}\` |`
833
836
  })
834
- await node.sendText(`### 🗂️ 可用工作区 (共 ${workspaces.length} 个)\n> 新建会话:发送 \`/new <提示词> @序号\`\n\n${lines.join('\n')}`)
837
+ await node.sendText([
838
+ `## 🗂️ 可用工作区 (共 ${workspaces.length} 个)`,
839
+ `> 新建会话:发送 \`/new <提示词> @序号\` 或 \`/new <提示词> @路径\``,
840
+ '',
841
+ '| 序号 | 工作区名称 | 目录路径 |',
842
+ '| :--- | :--- | :--- |',
843
+ ...rows,
844
+ ].join('\n'))
835
845
  return true
836
846
  }
837
847
  case 'new': {
@@ -888,7 +898,7 @@ async function routeCommand(node, text) {
888
898
  const agent = node.activeAgent()
889
899
  const session = node.activeSession()
890
900
  if (!session) {
891
- await node.sendText(`### 📊 Agent 状态看板\n\n> ℹ️ **当前没有活动会话**。\n> 发送 \`/new <提示词>\` 开始新任务,或发送 \`/sessions\` 查看已有会话。`)
901
+ await node.sendText(`## 📊 Agent 状态看板\n\n> 当前没有活动会话。\n> 发送 \`/new <提示词>\` 开始新任务,或发送 \`/sessions\` 查看已有会话。`)
892
902
  return true
893
903
  }
894
904
  const statusMap = {
@@ -901,10 +911,23 @@ async function routeCommand(node, text) {
901
911
  const lastTurn = [...(session.events ?? [])].reverse().find((e) => e.type === 'turn/end')
902
912
  const reason = lastTurn ? describeTurnEnd(lastTurn.data.reason) : '尚未运行'
903
913
  const title = session.title || (session.events ? sessionLabel(session) : '')
904
- const titleLine = title ? `\n- **会话标题**:${title}` : ''
905
- const cwdLine = session.header?.cwd ? `\n- **工作区**:\`${session.header.cwd}\`` : ''
914
+ const shortId = fmtSessionId(session.id)
915
+ const cwd = session.header?.cwd || node.config?.cwd || ''
916
+
917
+ const content = [
918
+ `## 📊 Agent 状态看板`,
919
+ '',
920
+ '| 属性 | 当前状态 / 参数 |',
921
+ '| :--- | :--- |',
922
+ `| **会话 ID** | \`${shortId}\` |`,
923
+ ...(title ? [`| **会话标题** | ${title.replace(/\|/g, '|')} |`] : []),
924
+ ...(cwd ? [`| **工作区** | \`${cwd}\` |`] : []),
925
+ `| **Agent 状态** | ${status} |`,
926
+ `| **累计事件** | ${session.seq ?? 0} 条 |`,
927
+ `| **最近执行** | ${reason} |`,
928
+ ].join('\n')
906
929
 
907
- await node.sendText(`### 📊 Agent 状态看板\n\n- **会话 ID**:\`${session.id}\`${titleLine}${cwdLine}\n- **Agent 状态**:${status}\n- **累计事件**:${session.seq ?? 0} 条\n- **最近执行**:${reason}`)
930
+ await node.sendText(content)
908
931
  return true
909
932
  }
910
933
  case 'start': // 别名:首次扫码自动开始一个会话
@@ -931,7 +954,7 @@ function describeTurnEnd(reason) {
931
954
  async function renderSessions(node) {
932
955
  const all = await listSessions(node)
933
956
  if (all.length === 0) {
934
- return `### 📋 会话列表\n\n> ℹ️ 暂无历史会话。发送 \`/new <提示词>\` 开始新会话。`
957
+ return `## 📋 会话列表\n\n> 暂无历史会话。发送 \`/new <提示词>\` 开始新会话。`
935
958
  }
936
959
  // 按工作区(真实 cwd)分组;无 cwd 的归入 '(未指定)'
937
960
  const groups = new Map()
@@ -942,28 +965,28 @@ async function renderSessions(node) {
942
965
  }
943
966
  const sortedGroups = [...groups.entries()].sort((a, b) => String(a[0]).localeCompare(String(b[0])))
944
967
  const parts = [
945
- `### 📋 会话列表 (共 ${all.length} 个)`,
968
+ `## 📋 会话列表 (共 ${all.length} 个)`,
946
969
  `> 切换会话:发送 \`/use 编号\` 或 \`/resume 编号\``,
947
970
  '',
948
971
  ]
949
972
  let idx = 0
950
973
  for (const [cwd, sessions] of sortedGroups) {
951
974
  const groupName = cwd === '(未指定)' ? '📁 未指定工作区' : `📁 **${getWorkspaceBasename(cwd)}**`
952
- const pathDetail = cwd === '(未指定)' ? '' : ` \`${cwd}\``
953
- parts.push(`${groupName}${pathDetail}`)
975
+ parts.push(groupName)
976
+ parts.push('')
977
+ parts.push('| 序号 | 会话标题 / 摘要 | 时间 | 状态 |')
978
+ parts.push('| :--- | :--- | :--- | :--- |')
954
979
  for (const session of sessions.slice(0, 20)) {
955
980
  idx += 1
956
981
  const isActive = session.id === node.activeSessionId
957
- const activeTag = isActive ? ' `[当前]`' : ''
958
- const title = session.title || (session.events ? sessionLabel(session) : '')
959
- const label = title || fmtSessionId(session.id)
960
- const when = session.createdAt ? fmtTime(session.createdAt) : ''
961
- const timeStr = when ? ` · ${when}` : ''
962
- parts.push(`- **#${idx}** ${label}${activeTag}`)
963
- parts.push(` \`${fmtSessionId(session.id)}\`${timeStr}`)
982
+ const statusTag = isActive ? '`[当前]`' : '-'
983
+ const rawTitle = session.title || (session.events ? sessionLabel(session) : '')
984
+ const safeTitle = (rawTitle || '新会话 (待输入)').replace(/\|/g, '|').replace(/\r?\n/g, ' ')
985
+ const when = session.createdAt ? fmtTime(session.createdAt) : '-'
986
+ parts.push(`| **#${idx}** | ${safeTitle} | ${when} | ${statusTag} |`)
964
987
  }
965
988
  if (sessions.length > 20) {
966
- parts.push(` *…该工作区共 ${sessions.length} 个会话*`)
989
+ parts.push(`*…该工作区共 ${sessions.length} 个会话,仅显示前 20 个*`)
967
990
  }
968
991
  parts.push('')
969
992
  }
@@ -1006,25 +1029,30 @@ function fmtSessionId(id) {
1006
1029
 
1007
1030
  function helpText() {
1008
1031
  return [
1009
- '### 🤖 常用指令帮助',
1032
+ '## 🤖 常用指令帮助',
1010
1033
  '',
1011
- '**💬 会话控制**',
1012
- '- `/sessions` (或 `/list`) 查看所有会话列表',
1013
- '- `/use <编号>` (或 `/resume <编号>`) — 切换到指定编号会话',
1014
- '- `/new <提示词>` 在当前工作区创建新会话并开始',
1015
- '- `/new <提示词> @序号` 在指定工作区创建新会话(/workspaces)',
1016
- '- `/new <提示词> @路径` 在指定目录路径创建新会话',
1017
- '- `/end` 结束当前会话(回到无活动会话状态)',
1018
- '- `/stop` 中断停止当前正在执行的任务',
1034
+ '### 💬 会话控制',
1035
+ '| 指令 | 说明 | 示例 |',
1036
+ '| :--- | :--- | :--- |',
1037
+ '| `/sessions` | 查看所有会话表格列表 | `/sessions` 或 `/list` |',
1038
+ '| `/use <编号>` | 切换到指定编号会话 | `/use 1` 或 `/resume 1` |',
1039
+ '| `/new <提示词>` | 在当前工作区新建会话 | `/new 帮我写个脚本` |',
1040
+ '| `/new <词> @N` | 在指定工作区新建会话 | `/new 帮我写个脚本 @1` |',
1041
+ '| `/stop` | 中断停止当前正在执行的任务 | `/stop` |',
1042
+ '| `/end` | 结束当前会话(回到空闲) | `/end` |',
1019
1043
  '',
1020
- '**📁 环境与状态**',
1021
- '- `/workspaces` 查看已注册的工作区列表',
1022
- '- `/status` 查看 Agent 运行状态与事件摘要',
1023
- '- `/help` 查看此帮助菜单',
1044
+ '### 📁 环境与状态',
1045
+ '| 指令 | 说明 |',
1046
+ '| :--- | :--- |',
1047
+ '| `/workspaces` | 查看可用工作区表格列表 |',
1048
+ '| `/status` | 查看 Agent 运行状态看板 |',
1049
+ '| `/help` | 查看此帮助菜单 |',
1024
1050
  '',
1025
- '**🔐 权限确认**',
1026
- '- `/yes` (或 `1`) 批准当前工具执行请求',
1027
- '- `/no` (或 `2`) 拒绝当前工具执行请求',
1051
+ '### 🔐 权限确认',
1052
+ '| 指令 | 快捷数字 | 说明 |',
1053
+ '| :--- | :--- | :--- |',
1054
+ '| `/yes` | `1` | 批准当前工具执行请求 |',
1055
+ '| `/no` | `2` | 拒绝当前工具执行请求 |',
1028
1056
  ].join('\n')
1029
1057
  }
1030
1058
 
package/lib/qq/node.js CHANGED
@@ -17,7 +17,8 @@ const STREAM_CHUNK_SIZE = 400
17
17
 
18
18
  /**
19
19
  * 将文本转成 QQ 安全 Markdown:
20
- * - 表格(| a | b | / |---|)降级为纯文本(QQ Markdown 不支持表格,会截断/报错)
20
+ * - 标题降级:QQ 仅标准支持 # ##,超出(###、####)降级为 ## 或加粗
21
+ * - 列表/表格前空行:QQ 规定列表前若为普通文本必须有空行隔开,自动补齐空行
21
22
  * - markdown 图片语法 ![alt](url) 转为链接 [alt](url)(避免图片转存失败导致整条失败)
22
23
  * - 代码块内容原样保留
23
24
  */
@@ -26,28 +27,35 @@ function sanitizeQQMarkdown(text) {
26
27
  const lines = String(text).split('\n')
27
28
  const out = []
28
29
  let inFence = false
29
- let inTable = false
30
- const isSep = (l) => /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(l) && l.includes('-')
31
- const isTableRow = (l) => /^\s*\|.*\|\s*$/.test(l)
30
+ const isListOrTable = (l) => /^\s*(-|\*|\d+\.|\/|\|)\s+/.test(l) || /^\s*\|.*\|\s*$/.test(l)
31
+ const isHeading = (l) => /^\s*#{1,6}\s+/.test(l)
32
+
32
33
  for (let i = 0; i < lines.length; i++) {
33
- const line = lines[i]
34
+ let line = lines[i]
34
35
  if (/^\s*```/.test(line)) {
35
36
  inFence = !inFence
36
37
  out.push(line)
37
38
  continue
38
39
  }
39
40
  if (inFence) { out.push(line); continue }
40
- const nextIsSep = i + 1 < lines.length && isSep(lines[i + 1])
41
- if (isTableRow(line) && (inTable || nextIsSep)) {
42
- inTable = true
43
- if (!isSep(line)) {
44
- // 去掉首尾 |,内部 | 转空格
45
- out.push(line.replace(/^\s*\|\s*/, '').replace(/\s*\|\s*$/, '').replace(/\s*\|\s*/g, ' '))
41
+
42
+ // 标题级别规范化:QQ 标准支持 # ##,超出(###+)统一转为 ##
43
+ if (isHeading(line)) {
44
+ line = line.replace(/^\s*#{3,}\s+/, '## ')
45
+ }
46
+
47
+ // 图片语法转为链接
48
+ line = line.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '[$1]($2)')
49
+
50
+ // 列表/表格前空行保障:若前一行是非空普通文本且不是标题/列表/引用/表格,则插入空行保证 QQ 正确解析
51
+ if (isListOrTable(line) && out.length > 0) {
52
+ const prev = out[out.length - 1]
53
+ if (prev && prev.trim() && !isListOrTable(prev) && !isHeading(prev) && !prev.startsWith('>')) {
54
+ out.push('')
46
55
  }
47
- continue
48
56
  }
49
- inTable = false
50
- out.push(line.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '[$1]($2)'))
57
+
58
+ out.push(line)
51
59
  }
52
60
  return out.join('\n')
53
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "2.2.7",
3
+ "version": "2.2.9",
4
4
  "description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 Bot(多工作区/会话持久化/媒体/审批),无需自己搭公网服务器。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",