dsh-plugin-weapp-gateway 0.8.10 → 0.8.12

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 CHANGED
@@ -4,6 +4,16 @@
4
4
 
5
5
  # dsh-plugin-weapp-gateway
6
6
 
7
+ ### v0.8.12 · 显示插件升级失败详情
8
+
9
+ - 安装器返回结构化错误时提取可读信息,不再显示 `[object Object]`;错误序列化会省略凭据、令牌和堆栈字段。
10
+
11
+ ### v0.8.11 · 小程序 ClawBot 私聊接线控制
12
+
13
+ - 在已配对的小程序控制通道中增加受限的 ClawBot 控制 RPC,复用 `dsh-wechat-remote` 自身的访问授权、状态冲突检查和本地凭据存储。
14
+ - 支持读取状态、绑定流程、选择普通 DSH 会话、启停与移除本地绑定;微信凭据不传回小程序。
15
+ - 新增 `wechat-clawbot-control-v1` 能力标记,客户端可对旧版网关明确降级。
16
+
7
17
  ### v0.8.9 · 插件在线更新
8
18
 
9
19
  - 打开「设置 → 移动设备」时自动检查 npm 最新版本,并可在面板中一键安装更新。
package/lib/client.js CHANGED
@@ -578,7 +578,13 @@ window.__ModuleLoader__.load({
578
578
  setUpdateInfo({ ...result, currentVersion: result.latestVersion, updateAvailable: false })
579
579
  setUpdateNotice(`已安装 v${result.latestVersion},需要重启 DSH 后生效。`)
580
580
  } else if (application === 'failed' || application === 'cancelled' || application === 'refused') {
581
- setUpdateNotice(`安装未完成:${application}${result.installation?.error ? `(${result.installation.error})` : ''}`)
581
+ const installError = result.installation?.error
582
+ const detail = result.installation?.errorMessage
583
+ || (typeof installError === 'string' ? installError : '')
584
+ || (typeof installError?.message === 'string' ? installError.message : '')
585
+ || (typeof installError?.code === 'string' ? installError.code : '')
586
+ || '请在 DSH 插件管理中查看安装详情。'
587
+ setUpdateNotice(`安装未完成:${application}(${detail})`)
582
588
  } else {
583
589
  setUpdateInfo({ ...result, currentVersion: result.latestVersion, updateAvailable: false })
584
590
  setUpdateNotice(`已安装 v${result.latestVersion}。如界面尚未更新,请重启 DSH。`)
@@ -325,6 +325,12 @@ export function createDshHostAdapter(typertGateway) {
325
325
  permissionPresets: {
326
326
  catalog: (_payload = {}, signal) => invoke('permissionPresets', 'catalog', {}, signal),
327
327
  },
328
+ // This endpoint intentionally forwards only the ClawBot control DTO. It
329
+ // stays behind the paired gateway and the ClawBot service's own access
330
+ // checks; it never exposes the channel credentials to the client.
331
+ clawbot: {
332
+ control: (request, signal) => invoke('wechatClawbot', 'control', request, signal),
333
+ },
328
334
  schedule: {
329
335
  async catalog(_payload = {}, signal) {
330
336
  return { items: requireArray(await invoke('schedule', 'catalog', {}, signal), 'schedule/catalog') }
package/lib/index.mjs CHANGED
@@ -1369,8 +1369,93 @@ function parseAgentPreset(msg, requestType, required = false) {
1369
1369
  return { value: msg.agentPreset.trim() }
1370
1370
  }
1371
1371
 
1372
+ const CLAWBOT_ACTIONS = new Set([
1373
+ 'status', 'sessions', 'route', 'enabled', 'login.start', 'login.poll', 'login.cancel', 'disconnect',
1374
+ ])
1375
+
1376
+ function validClawbotId(value) {
1377
+ return typeof value === 'string' && value.trim().length > 0 && value.length <= 256
1378
+ && !/[\u0000-\u001f]/.test(value)
1379
+ }
1380
+
1381
+ async function handleClawbotControl(api, msg) {
1382
+ const type = 'clawbot-control'
1383
+ const requestId = typeof msg.requestId === 'string' && msg.requestId.trim().length > 0
1384
+ && msg.requestId.length <= 128 ? msg.requestId.trim() : null
1385
+ const error = (code, message) => ({ kind: 'error', code, message, requestType: type, ...(requestId ? { requestId } : {}) })
1386
+ if (!requestId) return error('bad-request', 'ClawBot control requires a requestId')
1387
+ if (!CLAWBOT_ACTIONS.has(msg.action)) return error('bad-request', 'Unsupported ClawBot action')
1388
+ if (typeof api.clawbot?.control !== 'function') {
1389
+ return error('clawbot-unavailable', 'The gateway host adapter does not provide ClawBot control')
1390
+ }
1391
+
1392
+ const request = { action: msg.action }
1393
+ if (msg.expectedRevision !== undefined) {
1394
+ if (!Number.isSafeInteger(msg.expectedRevision) || msg.expectedRevision < 0) {
1395
+ return error('bad-request', 'expectedRevision must be a non-negative integer')
1396
+ }
1397
+ request.expectedRevision = msg.expectedRevision
1398
+ }
1399
+ if (msg.sessionId !== undefined) {
1400
+ if (!validClawbotId(msg.sessionId)) return error('bad-request', 'sessionId is invalid')
1401
+ request.sessionId = msg.sessionId.trim()
1402
+ }
1403
+ if (msg.loginId !== undefined) {
1404
+ if (!validClawbotId(msg.loginId)) return error('bad-request', 'loginId is invalid')
1405
+ request.loginId = msg.loginId.trim()
1406
+ }
1407
+ if (msg.enabled !== undefined) {
1408
+ if (typeof msg.enabled !== 'boolean') return error('bad-request', 'enabled must be a boolean')
1409
+ request.enabled = msg.enabled
1410
+ }
1411
+ if (msg.verifyCode !== undefined) {
1412
+ if (typeof msg.verifyCode !== 'string' || msg.verifyCode.length > 128 || /[\u0000-\u001f]/.test(msg.verifyCode)) {
1413
+ return error('bad-request', 'verifyCode is invalid')
1414
+ }
1415
+ request.verifyCode = msg.verifyCode
1416
+ }
1417
+ if (msg.abandonPending !== undefined) {
1418
+ if (typeof msg.abandonPending !== 'boolean') return error('bad-request', 'abandonPending must be a boolean')
1419
+ request.abandonPending = msg.abandonPending
1420
+ }
1421
+ if (['route', 'enabled'].includes(request.action) && !Number.isSafeInteger(request.expectedRevision)) {
1422
+ return error('bad-request', 'This ClawBot action requires expectedRevision')
1423
+ }
1424
+ if (request.action === 'route' && !request.sessionId) return error('bad-request', 'route requires sessionId')
1425
+ if (request.action === 'enabled' && typeof request.enabled !== 'boolean') return error('bad-request', 'enabled requires a boolean')
1426
+ if (request.action === 'login.poll' && !request.loginId) return error('bad-request', 'login.poll requires loginId')
1427
+ if (request.action === 'login.cancel' && !request.loginId) return error('bad-request', 'login.cancel requires loginId')
1428
+ if (request.action === 'disconnect' && !Number.isSafeInteger(request.expectedRevision)) {
1429
+ return error('bad-request', 'disconnect requires expectedRevision')
1430
+ }
1431
+
1432
+ try {
1433
+ const result = await api.clawbot.control(request)
1434
+ if (!result || typeof result !== 'object' || Array.isArray(result)) {
1435
+ return error('clawbot-unavailable', 'ClawBot returned an invalid response')
1436
+ }
1437
+ if (result.ok !== true) {
1438
+ const failure = result.error
1439
+ const code = typeof failure?.code === 'string' && /^[a-z0-9./_-]{1,80}$/i.test(failure.code)
1440
+ ? failure.code : 'clawbot-operation-failed'
1441
+ const message = typeof failure?.message === 'string' && failure.message.length <= 500
1442
+ ? failure.message : 'ClawBot operation failed; refresh status before retrying.'
1443
+ return error(code, message)
1444
+ }
1445
+ if (!result.value || typeof result.value !== 'object' || Array.isArray(result.value)) {
1446
+ return error('clawbot-unavailable', 'ClawBot returned an invalid response')
1447
+ }
1448
+ return { kind: type, requestId, value: result.value }
1449
+ } catch (cause) {
1450
+ // Do not expose arbitrary host/plugin exception details over the mobile API.
1451
+ log(`ClawBot control unavailable: ${cause?.code || 'unknown error'}`)
1452
+ return error('clawbot-unavailable', 'ClawBot is unavailable. Update dsh-wechat-remote and check its component status.')
1453
+ }
1454
+ }
1455
+
1372
1456
  // Dispatch one mobile query frame; returns the wire frame to send back.
1373
1457
  async function handleQuery(api, host, agentDefaultModel, msg, pluginContext) {
1458
+ if (msg.type === 'clawbot-control') return handleClawbotControl(api, msg)
1374
1459
  if (WORKSPACE_CHANGES_TYPES.has(msg.type)) return handleWorkspaceChanges(api, msg)
1375
1460
  if (PLUGIN_REQUEST_TYPES.has(msg.type)) return handlePluginQuery(api, msg, pluginContext)
1376
1461
  if (msg.type === 'session-agent-preset' || msg.type === 'select-agent-preset') {
@@ -3127,6 +3212,7 @@ const plugin = {
3127
3212
  msg.type === 'command-options' || msg.type === 'command-select' || msg.type === 'select-model' ||
3128
3213
  msg.type === 'permission-options' || msg.type === 'permission' || msg.type === 'context-usage' ||
3129
3214
  msg.type === 'agent-presets' || msg.type === 'session-agent-preset' || msg.type === 'select-agent-preset' ||
3215
+ msg.type === 'clawbot-control' ||
3130
3216
  msg.type === 'defaults' || msg.type === 'set-default' ||
3131
3217
  msg.type === 'session-stats' || msg.type === 'default-model' ||
3132
3218
  msg.type === 'save-default-model' || msg.type === 'fork' || msg.type === 'session-cancel' || msg.type === 'queue-update' ||
@@ -3136,10 +3222,12 @@ const plugin = {
3136
3222
  msg.type === 'schedule-catalog' || msg.type === 'schedule-list' || msg.type === 'schedule-history' ||
3137
3223
  msg.type === 'schedule-update' || msg.type === 'schedule-delete' ||
3138
3224
  WORKSPACE_CHANGES_TYPES.has(msg.type) || PLUGIN_REQUEST_TYPES.has(msg.type)) {
3139
- if ((WORKSPACE_CHANGES_TYPES.has(msg.type) || PLUGIN_REQUEST_TYPES.has(msg.type)) && !ws.deviceId) {
3225
+ if ((WORKSPACE_CHANGES_TYPES.has(msg.type) || PLUGIN_REQUEST_TYPES.has(msg.type) || msg.type === 'clawbot-control') && !ws.deviceId) {
3140
3226
  sendFrame({ kind: 'error', code: 'authentication-required', requestType: msg.type,
3141
3227
  ...(typeof msg.requestId === 'string' ? { requestId: msg.requestId } : {}),
3142
- message: 'Plugin management requires a paired device' })
3228
+ message: msg.type === 'clawbot-control'
3229
+ ? 'ClawBot control requires a paired device'
3230
+ : 'Plugin management requires a paired device' })
3143
3231
  return
3144
3232
  }
3145
3233
  if (msg.type === 'plugin-install' && pluginInstallOwners.has(msg.requestId)) {
@@ -3193,6 +3281,7 @@ const plugin = {
3193
3281
  'images',
3194
3282
  'session-create',
3195
3283
  'session-agent-preset',
3284
+ 'wechat-clawbot-control-v1',
3196
3285
  'commands',
3197
3286
  'tasks',
3198
3287
  'goals',
@@ -1,6 +1,37 @@
1
1
  export const PLUGIN_PACKAGE_NAME = 'dsh-plugin-weapp-gateway'
2
2
  const REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PLUGIN_PACKAGE_NAME}/latest`
3
3
 
4
+ function errorField(value) {
5
+ return typeof value === 'string' && value.trim() ? value.trim() : ''
6
+ }
7
+
8
+ export function formatPluginInstallError(error, depth = 0) {
9
+ if (typeof error === 'string') return error.trim().slice(0, 1000)
10
+ if (!error || typeof error !== 'object' || depth > 4) return ''
11
+
12
+ const code = errorField(error.code)
13
+ const message = errorField(error.message)
14
+ || errorField(error.errorMessage)
15
+ || errorField(error.detail)
16
+ || errorField(error.reason)
17
+ || errorField(error.description)
18
+ if (message) return `${code && code !== message ? `${code}: ` : ''}${message}`.slice(0, 1000)
19
+
20
+ for (const nested of [error.error, error.cause, error.details]) {
21
+ const detail = formatPluginInstallError(nested, depth + 1)
22
+ if (detail) return `${code && code !== detail ? `${code}: ` : ''}${detail}`.slice(0, 1000)
23
+ }
24
+
25
+ try {
26
+ const serialized = JSON.stringify(error, (key, value) => {
27
+ if (/stack|authorization|cookie|password|secret|token|credential|api.?key/i.test(key)) return undefined
28
+ return value
29
+ })
30
+ if (serialized && serialized !== '{}') return serialized.slice(0, 1000)
31
+ } catch { /* use a generic message for cyclic or non-serializable errors */ }
32
+ return code || 'The plugin manager did not provide an error message'
33
+ }
34
+
4
35
  function parseVersion(value) {
5
36
  if (typeof value !== 'string') return null
6
37
  const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value)
@@ -85,5 +116,12 @@ export async function installLatestPlugin({ api, currentVersion, fetchImpl = glo
85
116
  `${PLUGIN_PACKAGE_NAME}@${update.latestVersion}`,
86
117
  { requestId },
87
118
  )
88
- return { ...update, installed: true, installation }
119
+ const errorMessage = formatPluginInstallError(installation?.error)
120
+ return {
121
+ ...update,
122
+ installed: true,
123
+ installation: errorMessage && installation && typeof installation === 'object'
124
+ ? { ...installation, errorMessage }
125
+ : installation,
126
+ }
89
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-weapp-gateway",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "description": "Authenticated mobile gateway for dsh 0.1.7-rc.2 with Session V4 history, live permissions, inbox-based queue sync, plugin management (DSH 0.2.0 hosts), LAN and Cloudflare Tunnel access with public endpoint health checks, device pairing, per-session Agent preset selection, relay access for the WeChat mini-program, and the official speech-input bridge (speech-input-v1)",
5
5
  "main": "lib/index.mjs",
6
6
  "files": [