@wenbin_wb/dsh-bridge 2.2.5 → 2.2.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.
@@ -326,9 +326,11 @@ export class ConversationBridge {
326
326
  source: { kind: 'user' },
327
327
  }))
328
328
  }
329
- await this.sendText(`${this.mark.turn} 已创建会话 ${handle.agent.session.id}${cwdOverride ? `(工作区: ${cwdOverride})` : ''}${prompt ? '' : '(发消息即可开始)'}`)
329
+ const wsDetail = cwdOverride ? `\n- **工作区**:\`${cwdOverride}\`` : ''
330
+ const hint = prompt ? '' : '\n\n> 💡 发送任意消息即可直接与 Agent 对话。'
331
+ await this.sendText(`✓ **已创建新会话**\n- **会话 ID**:\`${fmtSessionId(handle.agent.session.id)}\`${wsDetail}${hint}`)
330
332
  } catch (error) {
331
- await this.sendText(`${this.mark.err} 创建会话失败: ${error instanceof Error ? error.message : String(error)}`)
333
+ await this.sendText(`❌ **创建会话失败**:${error instanceof Error ? error.message : String(error)}`)
332
334
  }
333
335
  }
334
336
 
@@ -510,11 +512,11 @@ export class ConversationBridge {
510
512
  this.logger?.warn?.(`[dsh-bridge ${this.platform.id}] failed to re-attach agent: ${reason}`)
511
513
  // 清除失效的 activeSessionId,避免用户误以为还在旧会话中
512
514
  this.activeSessionId = null
513
- await this.sendText(`${this.mark.err} 恢复会话失败: ${reason}。发送 /new <提示词> 新建一个会话。`)
515
+ await this.sendText(`❌ **恢复会话失败**:${reason}\n\n> 发送 \`/new <提示词>\` 可新建一个会话。`)
514
516
  }
515
517
  }
516
518
  if (!agent) {
517
- await this.sendText(`${this.mark.idle} 没有活动会话。发送 /new <提示词> 开始一个新会话,或 /sessions 查看已有会话。`)
519
+ await this.sendText(`> 💤 **当前没有活动会话**\n> 发送 \`/new <提示词>\` 开始新会话,或发送 \`/sessions\` 查看已有会话。`)
518
520
  return 'routed'
519
521
  }
520
522
 
@@ -603,11 +605,11 @@ export class ConversationBridge {
603
605
  if (this.peerId) this.sendTyping(2).catch(() => {})
604
606
  const reason = event.data.reason
605
607
  if (reason.kind === 'error') {
606
- void this.sendText(`${this.mark.err} 处理出错: ${summarizeError(reason.error)}`)
608
+ void this.sendText(`❌ **处理出错**:${summarizeError(reason.error)}`)
607
609
  } else if (reason.kind === 'aborted') {
608
- void this.sendText(`${this.mark.stop} 已停止`)
610
+ void this.sendText(`⏹ **任务已停止**`)
609
611
  } else if (reason.kind === 'max-tokens') {
610
- void this.sendText(`${this.mark.warn} 达到输出上限,本轮已截断`)
612
+ void this.sendText(`⚠️ **达到模型单轮输出上限,本轮内容已截断**`)
611
613
  }
612
614
  return
613
615
  }
@@ -630,12 +632,16 @@ export class ConversationBridge {
630
632
 
631
633
  const number = this.nextApprovalNumber()
632
634
  const timeoutSec = this.config.approvalTimeoutSec
635
+ const timeoutMin = Math.max(1, Math.round(timeoutSec / 60))
633
636
  const prompt = [
634
- `${this.mark.ask} #${number} 需要你的确认`,
635
- `工具: ${req.toolName}`,
636
- ...(req.reason ? [`原因: ${req.reason}`] : []),
637
- `回复 /yes 同意,/no 拒绝(仅一条待确认时也可回复 1/2)`,
638
- `${Math.max(1, Math.round(timeoutSec / 60))} 分钟内未回复将自动拒绝。`,
637
+ `### ⚠️ 操作权限确认 (#${number})`,
638
+ '',
639
+ `- **调用工具**:\`${req.toolName}\``,
640
+ ...(req.reason ? [`- **申请原因**:${req.reason}`] : []),
641
+ `- **等待超时**:${timeoutMin} 分钟内未回复将自动拒绝`,
642
+ '',
643
+ `> 回复 \`/yes\`(或 \`1\`)批准执行`,
644
+ `> 回复 \`/no\`(或 \`2\`)拒绝执行`,
639
645
  ].join('\n')
640
646
 
641
647
  void this.sendText(prompt)
@@ -653,7 +659,7 @@ export class ConversationBridge {
653
659
 
654
660
  // 仅在非超时路径发送确认消息(超时时 resolve 已经发生在 timer 回调)
655
661
  if (!timeoutFired) {
656
- const label = outcome === 'allowed-once' ? `${this.mark.ok} 已同意` : outcome === 'rejected' ? `${this.mark.err} 已拒绝` : `[${outcome}]`
662
+ const label = outcome === 'allowed-once' ? '✓ **已批准执行**' : outcome === 'rejected' ? '❌ **已拒绝执行**' : `**[${outcome}]**`
657
663
  void this.sendText(`${label}(#${number})`)
658
664
  }
659
665
  return outcome
@@ -676,10 +682,10 @@ function sessionLabel(session) {
676
682
  .map((block) => block.text ?? '')
677
683
  .join(' ')
678
684
  .trim()
679
- if (text) return text.length > 24 ? `${text.slice(0, 24)}…` : text
685
+ if (text) return text.length > 28 ? `${text.slice(0, 28)}…` : text
680
686
  }
681
687
  }
682
- return '(空会话)'
688
+ return '新会话 (待输入)'
683
689
  }
684
690
 
685
691
  // 校验工作区路径(防止路径遍历攻击)
@@ -689,13 +695,11 @@ async function validateWorkspacePath(node, sel) {
689
695
  const allowedPaths = workspaces.map(w => normalize(resolve(w.path)))
690
696
 
691
697
  if (!allowedPaths.includes(normalized)) {
692
- const wsDisplay = allowedPaths.slice(0, 5).join('\n ')
693
- const more = allowedPaths.length > 5 ? `\n ... ${allowedPaths.length} 个工作区` : ''
698
+ const wsDisplay = allowedPaths.slice(0, 5).map((p, i) => `- \`[${i + 1}]\` \`${p}\``).join('\n')
699
+ const more = allowedPaths.length > 5 ? `\n- *…等共 ${allowedPaths.length} 个工作区*` : ''
694
700
  return {
695
701
  valid: false,
696
- error: `${node.mark.err} 路径不在已注册工作区内: ${normalized}\n\n` +
697
- `可用工作区:\n ${wsDisplay}${more}\n\n` +
698
- `提示:使用 /workspaces 查看完整列表`
702
+ error: `❌ **路径不在已注册工作区列表中**:\`${normalized}\`\n\n**可用工作区:**\n${wsDisplay}${more}\n\n> 提示:发送 \`/workspaces\` 查看完整列表`
699
703
  }
700
704
  }
701
705
 
@@ -705,7 +709,7 @@ async function validateWorkspacePath(node, sel) {
705
709
  if (!ok) {
706
710
  return {
707
711
  valid: false,
708
- error: `${node.mark.err} 工作区目录不存在: ${normalized}`
712
+ error: `❌ **工作区目录不存在**:\`${normalized}\``
709
713
  }
710
714
  }
711
715
 
@@ -778,6 +782,13 @@ async function listWorkspaces(node) {
778
782
  }
779
783
  }
780
784
 
785
+ function getWorkspaceBasename(cwd) {
786
+ if (!cwd || cwd === '(未指定)') return '(未指定)'
787
+ const norm = normalize(cwd).replace(/[\\/]+$/, '')
788
+ const parts = norm.split(/[\\/]/)
789
+ return parts[parts.length - 1] || cwd
790
+ }
791
+
781
792
  async function routeCommand(node, text) {
782
793
  const trimmed = text.trim()
783
794
  if (!trimmed.startsWith('/')) return false
@@ -800,25 +811,27 @@ async function routeCommand(node, text) {
800
811
  const index = Number(rest[0])
801
812
  const sessions = sessionsInDisplayOrder(await listSessions(node))
802
813
  if (!Number.isInteger(index) || index < 1 || index > sessions.length) {
803
- await node.sendText(`${node.mark.err} 无效编号。可用: 1–${sessions.length}(/sessions 查看列表)`)
814
+ await node.sendText(`❌ **无效会话编号**:\`${rest[0] ?? ''}\`\n\n> 可用编号范围:\`1 – ${sessions.length}\`(发送 \`/sessions\` 查看会话列表)`)
804
815
  return true
805
816
  }
806
817
  const session = sessions[index - 1]
807
818
  node.setActiveSessionById(session.id)
808
- await node.sendText(`${node.mark.ok} 已切换到会话 #${index}(${session.id})`)
819
+ const title = session.title || (session.events ? sessionLabel(session) : '')
820
+ const titleLine = title ? `\n- **标题**:${title}` : ''
821
+ await node.sendText(`✓ **已切换到会话 #${index}**${titleLine}\n- **会话 ID**:\`${fmtSessionId(session.id)}\``)
809
822
  return true
810
823
  }
811
824
  case 'workspaces': {
812
825
  const workspaces = await listWorkspaces(node)
813
826
  if (workspaces.length === 0) {
814
- await node.sendText(`${node.mark.list} 没有可用的工作区。使用 /new <提示词> @<路径> 指定一个目录。`)
827
+ await node.sendText(`### 🗂️ 可用工作区\n\n> ℹ️ 当前没有已注册的工作区。可使用 \`/new <提示词> @<路径>\` 指定项目目录。`)
815
828
  return true
816
829
  }
817
830
  const lines = workspaces.map((w, i) => {
818
- const name = w.title && w.title !== w.path ? `**${w.title}** · \`${w.path}\`` : `\`${w.path}\``
819
- return `${i + 1}. ${name}`
831
+ const titleStr = w.title && w.title !== w.path ? `**${w.title}**` : `**${getWorkspaceBasename(w.path)}**`
832
+ return `- **[${i + 1}]** ${titleStr}\n \`${w.path}\``
820
833
  })
821
- await node.sendText(`${node.mark.list}\n**可用工作区**(/new <提示词> @N 选择)\n\n${lines.join('\n')}`)
834
+ await node.sendText(`### 🗂️ 可用工作区 (共 ${workspaces.length} 个)\n> 新建会话:发送 \`/new <提示词> @序号\`\n\n${lines.join('\n')}`)
822
835
  return true
823
836
  }
824
837
  case 'new': {
@@ -835,7 +848,10 @@ async function routeCommand(node, text) {
835
848
  const idx = Number(sel)
836
849
  const ws = workspaces[idx - 1]
837
850
  if (ws) cwd = ws.path
838
- else { await node.sendText(`${node.mark.err} 无效工作区编号 ${sel}。用 /workspaces 查看。`); return true }
851
+ else {
852
+ await node.sendText(`❌ **无效工作区编号**:\`${sel}\`\n\n> 请发送 \`/workspaces\` 查看可用工作区列表与编号。`)
853
+ return true
854
+ }
839
855
  } else {
840
856
  // 直接指定路径时,规范化并校验(必须完全匹配已注册工作区)
841
857
  const validation = await validateWorkspacePath(node, sel)
@@ -852,60 +868,71 @@ async function routeCommand(node, text) {
852
868
  case 'stop': {
853
869
  const agent = node.activeAgent()
854
870
  if (!agent) {
855
- await node.sendText(`${node.mark.err} 没有活动的 agent`)
871
+ await node.sendText(`ℹ️ **当前没有正在运行的 Agent 任务**`)
856
872
  } else {
857
873
  agent.cancel({ kind: 'user' })
858
- await node.sendText(`${node.mark.stop} 已请求停止`)
874
+ await node.sendText(`⏹ **已请求停止当前任务**`)
859
875
  }
860
876
  return true
861
877
  }
862
878
  case 'end': {
863
- // 结束当前会话:停止 agent 并清除活动会话(进入"无活动会话"状态)
879
+ // 结束当前会话:停止 agent 并清除活动会话(进入"没有活动会话"状态)
864
880
  const agent = node.activeAgent()
865
881
  if (agent) agent.cancel({ kind: 'user' })
866
882
  node.activeSessionId = null
867
883
  await node.onActiveSessionChange?.(null)
868
- // 文本含"没有活动会话"→ 有按钮权限时附带快捷按钮;无按钮时文字指引也能操作
869
- await node.sendText(`${node.mark.ok} 已结束当前会话(没有活动会话)。\n\n发送以下任一命令开始:\n/new <提示词> — 新建会话\n/sessions — 查看已有会话\n/help — 命令帮助`)
884
+ await node.sendText(`✓ **已结束当前会话**(没有活动会话)。\n\n> **后续操作**:\n> - \`/new <提示词>\` — 新建会话并开始\n> - \`/sessions\` — 查看历史会话列表\n> - \`/help\` — 查看常用指令帮助`)
870
885
  return true
871
886
  }
872
887
  case 'status': {
873
888
  const agent = node.activeAgent()
874
889
  const session = node.activeSession()
875
890
  if (!session) {
876
- await node.sendText(`${node.mark.idle} 没有活动会话。发送 /new <提示词> 开始,或 /sessions 查看已有会话。`)
891
+ await node.sendText(`### 📊 Agent 状态看板\n\n> ℹ️ **当前没有活动会话**。\n> 发送 \`/new <提示词>\` 开始新任务,或发送 \`/sessions\` 查看已有会话。`)
877
892
  return true
878
893
  }
879
- const status = agent?.status ?? 'idle'
894
+ const statusMap = {
895
+ idle: '空闲 (idle)',
896
+ running: '运行中 (running)',
897
+ paused: '已暂停 (paused)',
898
+ error: '异常 (error)',
899
+ }
900
+ const status = statusMap[agent?.status] || (agent?.status ?? '空闲 (idle)')
880
901
  const lastTurn = [...(session.events ?? [])].reverse().find((e) => e.type === 'turn/end')
881
902
  const reason = lastTurn ? describeTurnEnd(lastTurn.data.reason) : '尚未运行'
882
- await node.sendText(`${node.mark.status}\n会话: ${session.id}\nagent: ${status}\n事件: ${session.seq} 条\n最近: ${reason}`)
903
+ const title = session.title || (session.events ? sessionLabel(session) : '')
904
+ const titleLine = title ? `\n- **会话标题**:${title}` : ''
905
+ const cwdLine = session.header?.cwd ? `\n- **工作区**:\`${session.header.cwd}\`` : ''
906
+
907
+ await node.sendText(`### 📊 Agent 状态看板\n\n- **会话 ID**:\`${session.id}\`${titleLine}${cwdLine}\n- **Agent 状态**:${status}\n- **累计事件**:${session.seq ?? 0} 条\n- **最近执行**:${reason}`)
883
908
  return true
884
909
  }
885
910
  case 'start': // 别名:首次扫码自动开始一个会话
886
911
  await node.createSession('')
887
912
  return true
888
913
  default:
889
- await node.sendText(`${node.mark.err} 未知命令 /${command}\n${helpText()}`)
914
+ await node.sendText(`❌ **未知指令**:\`/${command}\`\n\n${helpText()}`)
890
915
  return true
891
916
  }
892
917
  }
893
918
 
894
919
  function describeTurnEnd(reason) {
895
920
  switch (reason.kind) {
896
- case 'completed': return '[完成]'
897
- case 'error': return '[出错]'
898
- case 'aborted': return '[已停止]'
899
- case 'blocked': return '[已阻塞]'
900
- case 'max-tokens': return '[输出截断]'
901
- case 'interrupted': return '[中断]'
902
- default: return reason.kind
921
+ case 'completed': return '✓ 已完成'
922
+ case 'error': return '出错'
923
+ case 'aborted': return '已停止'
924
+ case 'blocked': return '⚠️ 已阻塞'
925
+ case 'max-tokens': return '⚠️ 输出截断'
926
+ case 'interrupted': return '⚡ 已中断'
927
+ default: return `[${reason.kind}]`
903
928
  }
904
929
  }
905
930
 
906
931
  async function renderSessions(node) {
907
932
  const all = await listSessions(node)
908
- if (all.length === 0) return `${node.mark.list} 没有会话。发送 /new <提示词> 开始。`
933
+ if (all.length === 0) {
934
+ return `### 📋 会话列表\n\n> ℹ️ 暂无历史会话。发送 \`/new <提示词>\` 开始新会话。`
935
+ }
909
936
  // 按工作区(真实 cwd)分组;无 cwd 的归入 '(未指定)'
910
937
  const groups = new Map()
911
938
  for (const s of all) {
@@ -914,23 +941,34 @@ async function renderSessions(node) {
914
941
  groups.get(key).push(s)
915
942
  }
916
943
  const sortedGroups = [...groups.entries()].sort((a, b) => String(a[0]).localeCompare(String(b[0])))
917
- const parts = [`**会话列表** · 共 ${all.length} 个(/use N 切换)`, '']
944
+ const parts = [
945
+ `### 📋 会话列表 (共 ${all.length} 个)`,
946
+ `> 切换会话:发送 \`/use 编号\` 或 \`/resume 编号\``,
947
+ '',
948
+ ]
918
949
  let idx = 0
919
950
  for (const [cwd, sessions] of sortedGroups) {
920
- parts.push(`## ${cwd}`)
951
+ const groupName = cwd === '(未指定)' ? '📁 未指定工作区' : `📁 **${getWorkspaceBasename(cwd)}**`
952
+ const pathDetail = cwd === '(未指定)' ? '' : ` \`${cwd}\``
953
+ parts.push(`${groupName}${pathDetail}`)
921
954
  for (const session of sessions.slice(0, 20)) {
922
955
  idx += 1
923
- const active = session.id === node.activeSessionId ? ' **← 当前**' : ''
956
+ const isActive = session.id === node.activeSessionId
957
+ const activeTag = isActive ? ' `[当前]`' : ''
924
958
  const title = session.title || (session.events ? sessionLabel(session) : '')
925
959
  const label = title || fmtSessionId(session.id)
926
960
  const when = session.createdAt ? fmtTime(session.createdAt) : ''
927
- parts.push(`${idx}. \`${label}\``)
928
- parts.push(` ${session.id} · ${when}${active}`)
961
+ const timeStr = when ? ` · ${when}` : ''
962
+ parts.push(`- **#${idx}** ${label}${activeTag}`)
963
+ parts.push(` \`${fmtSessionId(session.id)}\`${timeStr}`)
964
+ }
965
+ if (sessions.length > 20) {
966
+ parts.push(` *…该工作区共 ${sessions.length} 个会话*`)
929
967
  }
930
- if (sessions.length > 20) parts.push(` …该工作区共 ${sessions.length} 个`)
968
+ parts.push('')
931
969
  }
932
- if (all.length > 50) parts.push('', `…共 ${all.length} 个会话,仅显示前若干`)
933
- return `${node.mark.list}\n${parts.join('\n')}`
970
+ if (all.length > 50) parts.push(`*…共 ${all.length} 个会话,仅显示前 50 个*`)
971
+ return parts.join('\n').trim()
934
972
  }
935
973
 
936
974
  // 与 renderSessions 完全一致的显示顺序:按工作区字母序分组、组内保持 listSessions 顺序。
@@ -946,35 +984,47 @@ function sessionsInDisplayOrder(all) {
946
984
  return sortedGroups.flatMap(([, sessions]) => sessions)
947
985
  }
948
986
 
949
- // 时间戳 → 可读时间
987
+ // 时间戳 → 简洁可读时间 (MM-DD HH:mm 或 YYYY-MM-DD HH:mm)
950
988
  function fmtTime(ms) {
951
989
  try {
952
990
  const d = new Date(ms)
991
+ if (isNaN(d.getTime())) return ''
953
992
  const p = (n) => String(n).padStart(2, '0')
954
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
993
+ const now = new Date()
994
+ const isSameYear = d.getFullYear() === now.getFullYear()
995
+ const datePart = isSameYear ? `${p(d.getMonth() + 1)}-${p(d.getDate())}` : `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
996
+ return `${datePart} ${p(d.getHours())}:${p(d.getMinutes())}`
955
997
  } catch { return '' }
956
998
  }
957
999
 
958
- // 持久化会话没有加载 events,用时间戳作为可读标识
1000
+ // 提取精简会话短 ID(去除冗余 session- 前缀,保留 8 位短标识)
959
1001
  function fmtSessionId(id) {
960
- const m = /^session-([0-9a-f]{8})/.exec(id ?? '')
961
- return m ? `session-${m[1]}…` : (id ?? '')
1002
+ if (!id) return ''
1003
+ const clean = String(id).replace(/^session-/, '')
1004
+ return clean.length > 8 ? clean.slice(0, 8) : clean
962
1005
  }
963
1006
 
964
1007
  function helpText() {
965
1008
  return [
966
- `${BRIDGE_MARK.welcome} 命令`,
967
- '/sessions(或 /list)— 列出会话(按工作区分组)',
968
- '/use N(或 /resume N)— 切换到会话 N',
969
- '/workspaces列出可用工作区',
970
- '/new <提示词>新建会话并开始(当前工作区)',
971
- '/new <提示词> @路径 在指定目录新建会话',
972
- '/new <提示词> @N用编号选择工作区(/workspaces)',
973
- '/stop停止当前任务',
974
- '/end — 结束当前会话(回到无活动会话状态)',
975
- '/status查看状态',
976
- '/yes /no 或 1/2 — 回应权限请求',
977
- '/help — 本帮助',
1009
+ '### 🤖 常用指令帮助',
1010
+ '',
1011
+ '**💬 会话控制**',
1012
+ '- `/sessions` (或 `/list`) 查看所有会话列表',
1013
+ '- `/use <编号>` (或 `/resume <编号>`) 切换到指定编号会话',
1014
+ '- `/new <提示词>`在当前工作区创建新会话并开始',
1015
+ '- `/new <提示词> @序号`在指定工作区创建新会话(/workspaces)',
1016
+ '- `/new <提示词> @路径` 在指定目录路径创建新会话',
1017
+ '- `/end` — 结束当前会话(回到无活动会话状态)',
1018
+ '- `/stop` 中断停止当前正在执行的任务',
1019
+ '',
1020
+ '**📁 环境与状态**',
1021
+ '- `/workspaces` — 查看已注册的工作区列表',
1022
+ '- `/status` — 查看 Agent 运行状态与事件摘要',
1023
+ '- `/help` — 查看此帮助菜单',
1024
+ '',
1025
+ '**🔐 权限确认**',
1026
+ '- `/yes` (或 `1`) — 批准当前工具执行请求',
1027
+ '- `/no` (或 `2`) — 拒绝当前工具执行请求',
978
1028
  ].join('\n')
979
1029
  }
980
1030
 
@@ -108,7 +108,7 @@ export class WechatConversationNode extends ConversationBridge {
108
108
  if (debugInfo.length > 0) {
109
109
  errMsg += `\n调试信息:\n${debugInfo.join('\n')}`
110
110
  }
111
- await this.sendText(`${this.mark.err} ${errMsg}`)
111
+ await this.sendText(`❌ **${errMsg}**`)
112
112
  return
113
113
  }
114
114
  this.logger?.info?.(`[dsh-bridge wechat] ignore empty message from ${sender}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 Bot(多工作区/会话持久化/媒体/审批),无需自己搭公网服务器。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -16,7 +16,7 @@
16
16
  "client",
17
17
  "cordis.patch.yml",
18
18
  "README.md",
19
- "README.zh-CN.md",
19
+ "README.en.md",
20
20
  "LICENSE",
21
21
  "docs/banner.jpg",
22
22
  "docs/custom-tunnel.md",