dsh-plugin-weapp-gateway 0.8.9 → 0.8.11

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,12 @@
4
4
 
5
5
  # dsh-plugin-weapp-gateway
6
6
 
7
+ ### v0.8.11 · 小程序 ClawBot 私聊接线控制
8
+
9
+ - 在已配对的小程序控制通道中增加受限的 ClawBot 控制 RPC,复用 `dsh-wechat-remote` 自身的访问授权、状态冲突检查和本地凭据存储。
10
+ - 支持读取状态、绑定流程、选择普通 DSH 会话、启停与移除本地绑定;微信凭据不传回小程序。
11
+ - 新增 `wechat-clawbot-control-v1` 能力标记,客户端可对旧版网关明确降级。
12
+
7
13
  ### v0.8.9 · 插件在线更新
8
14
 
9
15
  - 打开「设置 → 移动设备」时自动检查 npm 最新版本,并可在面板中一键安装更新。
package/bin/setup-ip.mjs CHANGED
File without changes
@@ -1,10 +1,10 @@
1
1
  const TICKET_RE = /^[A-Za-z0-9_-]{43}$/
2
2
  const MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000
3
3
 
4
- function turnKey(event) {
4
+ function eventTurnKey(event) {
5
5
  const value = event?.data?.turn
6
6
  if (typeof value === 'string' || typeof value === 'number') return `${typeof value}:${String(value)}`
7
- return Number.isSafeInteger(event?.seq) ? `seq:${event.seq}` : null
7
+ return null
8
8
  }
9
9
 
10
10
  function completionUrl(baseUrl) {
@@ -102,7 +102,7 @@ export function createCompletionNotifications({ serviceUrl, fetchImpl = globalTh
102
102
  prune()
103
103
  if (event.type === 'turn/start') {
104
104
  const state = sessionState(sessionId, true)
105
- const key = turnKey(event)
105
+ const key = eventTurnKey(event) || (Number.isSafeInteger(event.seq) ? `seq:${event.seq}` : null)
106
106
  if (key === null) return
107
107
  state.currentTurn = key
108
108
  state.currentTurnAt = now()
@@ -117,7 +117,9 @@ export function createCompletionNotifications({ serviceUrl, fetchImpl = globalTh
117
117
  if (event.type !== 'turn/end') return
118
118
  const state = sessionState(sessionId)
119
119
  if (!state) return
120
- const key = turnKey(event) || state.currentTurn
120
+ // Event sequence numbers differ between start and end; if turn metadata is
121
+ // absent on the end event, close the turn already tracked as current.
122
+ const key = eventTurnKey(event) || state.currentTurn
121
123
  const records = key === null ? [] : state.active.get(key) || []
122
124
  if (key !== null) state.active.delete(key)
123
125
  if (state.currentTurn === key) { state.currentTurn = null; state.currentTurnAt = null }
@@ -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',
package/package.json CHANGED
@@ -1,53 +1,51 @@
1
- {
2
- "name": "dsh-plugin-weapp-gateway",
3
- "version": "0.8.9",
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
- "main": "lib/index.mjs",
6
- "files": [
7
- "bin",
8
- "helper",
9
- "lib",
10
- "relay",
11
- "docs",
12
- "cordis.patch.yml",
13
- "PROTOCOL.md"
14
- ],
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/huiikeung/dsh-plugin-weapp-gateway.git"
18
- },
19
- "homepage": "https://github.com/huiikeung/dsh-plugin-weapp-gateway#readme",
20
- "bugs": {
21
- "url": "https://github.com/huiikeung/dsh-plugin-weapp-gateway/issues"
22
- },
23
- "dependencies": {
24
- "@deepseek-ai/schemastery": "^3.18.1",
25
- "ws": "^8.18.0",
26
- "qrcode": "^1.5.4"
27
- },
28
- "exports": {
29
- ".": "./lib/index.mjs",
30
- "./client": "./lib/client.js",
31
- "./package.json": "./package.json"
32
- },
33
- "dsh": {
34
- "bundle": {
35
- "patch": "./cordis.patch.yml"
36
- },
37
- "client": {
38
- "platform": "web",
39
- "inject": [
40
- "@deepseek-ai/dsh-client-runtime",
41
- "@deepseek-ai/dsh-client-ui-layout",
42
- "@deepseek-ai/dsh-client-ui-sidebar"
43
- ]
44
- }
45
- },
46
- "license": "MIT",
47
- "scripts": {
48
- "test": "node test/wire-json.test.mjs && node test/setup-ip.test.mjs && node test/host-adapter.test.mjs && node test/speech-input.test.mjs && node test/speech-official.test.mjs && node test/plugin-management.test.mjs && node test/plugin-updates.test.mjs && node test/session-preset.test.mjs && node test/session-follower.test.mjs && node test/completion-notifications.test.mjs && node --test test/workspace-changes.test.mjs && node test/gateway.test.mjs && node test/auth.test.mjs && node test/lan.test.mjs && node test/cloudflared-binary.test.mjs && node test/cloudflare-tunnel.test.mjs && node --test test/public-endpoint-check.test.mjs && node test/gateway-tools.test.mjs && node test/relay-connector.test.mjs && node test/cloudflare-linux.test.mjs && node test/multi-gateway.test.mjs"
49
- },
50
- "bin": {
51
- "dsh-plugin-weapp-gateway": "bin/setup-ip.mjs"
52
- }
53
- }
1
+ {
2
+ "name": "dsh-plugin-weapp-gateway",
3
+ "version": "0.8.11",
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
+ "main": "lib/index.mjs",
6
+ "files": [
7
+ "bin",
8
+ "helper",
9
+ "lib",
10
+ "relay",
11
+ "docs",
12
+ "cordis.patch.yml",
13
+ "PROTOCOL.md"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/huiikeung/dsh-plugin-weapp-gateway.git"
18
+ },
19
+ "homepage": "https://github.com/huiikeung/dsh-plugin-weapp-gateway#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/huiikeung/dsh-plugin-weapp-gateway/issues"
22
+ },
23
+ "dependencies": {
24
+ "@deepseek-ai/schemastery": "^3.18.1",
25
+ "ws": "^8.18.0",
26
+ "qrcode": "^1.5.4"
27
+ },
28
+ "bin": "bin/setup-ip.mjs",
29
+ "scripts": {
30
+ "test": "node test/wire-json.test.mjs && node test/setup-ip.test.mjs && node test/host-adapter.test.mjs && node test/speech-input.test.mjs && node test/speech-official.test.mjs && node test/plugin-management.test.mjs && node test/plugin-updates.test.mjs && node test/session-preset.test.mjs && node test/session-follower.test.mjs && node test/completion-notifications.test.mjs && node --test test/workspace-changes.test.mjs && node test/gateway.test.mjs && node test/auth.test.mjs && node test/lan.test.mjs && node test/cloudflared-binary.test.mjs && node test/cloudflare-tunnel.test.mjs && node --test test/public-endpoint-check.test.mjs && node test/gateway-tools.test.mjs && node test/relay-connector.test.mjs && node test/cloudflare-linux.test.mjs && node test/multi-gateway.test.mjs"
31
+ },
32
+ "exports": {
33
+ ".": "./lib/index.mjs",
34
+ "./client": "./lib/client.js",
35
+ "./package.json": "./package.json"
36
+ },
37
+ "dsh": {
38
+ "bundle": {
39
+ "patch": "./cordis.patch.yml"
40
+ },
41
+ "client": {
42
+ "platform": "web",
43
+ "inject": [
44
+ "@deepseek-ai/dsh-client-runtime",
45
+ "@deepseek-ai/dsh-client-ui-layout",
46
+ "@deepseek-ai/dsh-client-ui-sidebar"
47
+ ]
48
+ }
49
+ },
50
+ "license": "MIT"
51
+ }