@dcrays/dcgchat-test 0.5.0-alpha.2 → 0.5.0-alpha.3

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.
Files changed (42) hide show
  1. package/index.js +292 -0
  2. package/package.json +7 -15
  3. package/schemas/gateway-cron-finished.payload.json +39 -0
  4. package/index.ts +0 -24
  5. package/src/agent.ts +0 -128
  6. package/src/bot.ts +0 -515
  7. package/src/channel.ts +0 -474
  8. package/src/cron.ts +0 -199
  9. package/src/cronToolCall.ts +0 -202
  10. package/src/gateway/cronFinishedPayload.ts +0 -118
  11. package/src/gateway/index.ts +0 -452
  12. package/src/gateway/security.ts +0 -95
  13. package/src/gateway/socket.ts +0 -285
  14. package/src/libs/ali-oss-6.23.0.tgz +0 -0
  15. package/src/libs/axios-1.13.6.tgz +0 -0
  16. package/src/libs/md5-2.3.0.tgz +0 -0
  17. package/src/libs/mime-types-3.0.2.tgz +0 -0
  18. package/src/libs/unzipper-0.12.3.tgz +0 -0
  19. package/src/libs/ws-8.19.0.tgz +0 -0
  20. package/src/monitor.ts +0 -165
  21. package/src/request/api.ts +0 -70
  22. package/src/request/oss.ts +0 -212
  23. package/src/request/request.ts +0 -192
  24. package/src/request/userInfo.ts +0 -93
  25. package/src/session.ts +0 -19
  26. package/src/sessionTermination.ts +0 -168
  27. package/src/skill.ts +0 -146
  28. package/src/tool.ts +0 -403
  29. package/src/tools/messageTool.ts +0 -273
  30. package/src/transport.ts +0 -206
  31. package/src/types.ts +0 -139
  32. package/src/utils/agentErrors.ts +0 -23
  33. package/src/utils/constant.ts +0 -7
  34. package/src/utils/gatewayMsgHanlder.ts +0 -84
  35. package/src/utils/global.ts +0 -161
  36. package/src/utils/log.ts +0 -15
  37. package/src/utils/params.ts +0 -88
  38. package/src/utils/searchFile.ts +0 -228
  39. package/src/utils/workspaceFilePaths.ts +0 -89
  40. package/src/utils/wsMessageHandler.ts +0 -64
  41. package/src/utils/zipExtract.ts +0 -97
  42. package/src/utils/zipPath.ts +0 -24
@@ -1,452 +0,0 @@
1
- // Gateway connection handler - connects to local OpenClaw gateway
2
- import { WebSocket } from 'ws'
3
- import crypto from 'crypto'
4
- import { deriveDeviceIdFromPublicKey, publicKeyRawBase64UrlFromPem, buildDeviceAuthPayloadV3, signDevicePayload } from './security.js'
5
- import { dcgLogger } from '../utils/log.js'
6
- import { getWorkspaceDir } from '../utils/global.js'
7
- import { handleGatewayEventMessage } from '../utils/gatewayMsgHanlder.js'
8
-
9
- export interface GatewayEvent {
10
- type: string
11
- payload?: Record<string, unknown>
12
- seq?: number
13
- }
14
-
15
- export interface GatewayHelloOk {
16
- server: {
17
- connId: string
18
- }
19
- features: {
20
- methods: string[]
21
- events: string[]
22
- }
23
- policy?: {
24
- tickIntervalMs?: number
25
- }
26
- auth?: {
27
- deviceToken?: string
28
- role?: string
29
- scopes?: string[]
30
- }
31
- }
32
-
33
- export interface GatewayResponse {
34
- type: 'res'
35
- id: string
36
- ok: boolean
37
- /** 多数 RPC 成功时的返回值 */
38
- result?: unknown
39
- /** connect 及部分响应使用 payload(见 docs/gateway/protocol.md) */
40
- payload?: unknown
41
- error?: {
42
- code: string
43
- message: string
44
- details?: Record<string, unknown>
45
- }
46
- }
47
-
48
- export interface GatewayEventFrame {
49
- type: 'event'
50
- event: string
51
- payload?: Record<string, unknown>
52
- seq?: number
53
- }
54
-
55
- export interface GatewayHelloOkFrame {
56
- type: 'hello-ok'
57
- server: {
58
- connId: string
59
- }
60
- features: {
61
- methods: string[]
62
- events: string[]
63
- }
64
- policy?: {
65
- tickIntervalMs?: number
66
- }
67
- }
68
-
69
- // Union of all possible gateway messages
70
- export type GatewayMessage = GatewayEventFrame | GatewayResponse | GatewayHelloOkFrame
71
-
72
- export interface GatewayConfig {
73
- url: string
74
- token: string
75
- role: string
76
- scopes: string[]
77
- reconnectInterval?: number
78
- maxReconnectAttempts?: number
79
- }
80
-
81
- /**
82
- * Gateway connection handler
83
- */
84
- export class GatewayConnection {
85
- private ws: WebSocket | null = null
86
- private config: Required<GatewayConfig>
87
- private deviceId: string
88
- private privateKeyPem: string
89
- private publicKeyB64Url: string
90
- private connected: boolean = false
91
- private connId: string | null = null
92
- /** 服务端 connect.challenge 提供的 nonce,须与签名载荷一致 */
93
- private connectChallengeNonce: string | null = null
94
- private connectSent: boolean = false
95
- private pendingRpcById: Map<string, (response: GatewayResponse) => void> = new Map()
96
- private eventHandlers: Set<(event: GatewayEvent) => void> = new Set()
97
-
98
- constructor(config: GatewayConfig) {
99
- this.config = {
100
- url: config.url,
101
- token: config.token,
102
- role: config.role || 'operator',
103
- scopes: config.scopes || ['operator.admin'],
104
- reconnectInterval: config.reconnectInterval || 5000,
105
- maxReconnectAttempts: config.maxReconnectAttempts || 10
106
- }
107
-
108
- const identity = this.loadOrCreateDeviceIdentity()
109
- // 必须与公钥指纹一致(deriveDeviceIdFromPublicKey),不可用随机 UUID
110
- this.deviceId = identity.deviceId
111
- this.privateKeyPem = identity.privateKeyPem
112
- this.publicKeyB64Url = identity.publicKeyB64Url
113
- }
114
-
115
- private loadOrCreateDeviceIdentity() {
116
- const fs = require('fs')
117
- const path = require('path')
118
- const workspaceDir = getWorkspaceDir()
119
- const stateDir = path.join(workspaceDir, '.state')
120
- const deviceFile = path.join(stateDir, 'device.json')
121
-
122
- // Try to load existing identity
123
- if (fs.existsSync(deviceFile)) {
124
- const stored = JSON.parse(fs.readFileSync(deviceFile, 'utf8'))
125
- if (stored.deviceId && stored.publicKeyPem && stored.privateKeyPem) {
126
- const derivedId = deriveDeviceIdFromPublicKey(stored.publicKeyPem)
127
- const deviceId = derivedId !== stored.deviceId ? derivedId : stored.deviceId
128
- if (derivedId !== stored.deviceId) {
129
- try {
130
- fs.writeFileSync(deviceFile, JSON.stringify({ ...stored, deviceId: derivedId }, null, 2))
131
- } catch {
132
- /* keep in-memory fixed id only */
133
- }
134
- }
135
- return {
136
- deviceId,
137
- publicKeyPem: stored.publicKeyPem,
138
- privateKeyPem: stored.privateKeyPem,
139
- publicKeyB64Url: publicKeyRawBase64UrlFromPem(stored.publicKeyPem)
140
- }
141
- }
142
- }
143
-
144
- // Create new identity
145
- const keyPair = crypto.generateKeyPairSync('ed25519')
146
- const publicKeyPem = keyPair.publicKey.export({ type: 'spki', format: 'pem' }).toString()
147
- const privateKeyPem = keyPair.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString()
148
- const deviceId = deriveDeviceIdFromPublicKey(publicKeyPem)
149
- const publicKeyB64Url = publicKeyRawBase64UrlFromPem(publicKeyPem)
150
-
151
- // Ensure directory exists
152
- if (!fs.existsSync(stateDir)) {
153
- fs.mkdirSync(stateDir, { recursive: true })
154
- }
155
-
156
- // Save identity
157
- fs.writeFileSync(
158
- deviceFile,
159
- JSON.stringify(
160
- {
161
- version: 1,
162
- deviceId,
163
- publicKeyPem,
164
- privateKeyPem,
165
- createdAtMs: Date.now()
166
- },
167
- null,
168
- 2
169
- )
170
- )
171
- fs.chmodSync(deviceFile, 0o600)
172
-
173
- return { deviceId, publicKeyPem, privateKeyPem, publicKeyB64Url }
174
- }
175
-
176
- /**
177
- * Connect to the gateway
178
- */
179
- async connect(): Promise<GatewayHelloOk> {
180
- return new Promise((resolve, reject) => {
181
- this.connectChallengeNonce = null
182
- this.connectSent = false
183
- this.ws = new WebSocket(this.config.url)
184
-
185
- let handshakeSettled = false
186
- const finishHandshake = (fn: () => void) => {
187
- if (handshakeSettled) return
188
- handshakeSettled = true
189
- clearTimeout(timeout)
190
- fn()
191
- }
192
-
193
- const timeout = setTimeout(() => {
194
- finishHandshake(() => reject(new Error('Gateway connection timeout')))
195
- }, 15000)
196
-
197
- this.ws.on('open', () => {
198
- dcgLogger('Gateway connection opened(等待 connect.challenge)')
199
- })
200
-
201
- this.ws.on('message', (data, ...args) => {
202
- try {
203
- const msg = JSON.parse(data.toString())
204
- this.handleMessage(
205
- msg,
206
- (hello) => finishHandshake(() => resolve(hello)),
207
- (err) => finishHandshake(() => reject(err)),
208
- timeout
209
- )
210
- } catch (err) {
211
- dcgLogger(`[Gateway] 解析消息失败: ${err}`, 'error')
212
- }
213
- })
214
-
215
- this.ws.on('close', () => {
216
- this.connected = false
217
- finishHandshake(() => reject(new Error('Gateway 在握手完成前关闭了连接')))
218
- })
219
-
220
- this.ws.on('error', (err) => {
221
- dcgLogger(`Gateway 连接错误: ${err}`, 'error')
222
- finishHandshake(() => reject(err))
223
- })
224
- })
225
- }
226
-
227
- /**
228
- * Send initial connect request
229
- */
230
- private sendConnect(): void {
231
- if (this.connectSent) return
232
- const nonce = this.connectChallengeNonce?.trim() ?? ''
233
- if (!nonce) return
234
-
235
- this.connectSent = true
236
- const signedAtMs = Date.now()
237
- const platform = process.platform
238
-
239
- const payload = buildDeviceAuthPayloadV3({
240
- deviceId: this.deviceId,
241
- clientId: 'gateway-client',
242
- clientMode: 'backend',
243
- role: this.config.role,
244
- scopes: this.config.scopes,
245
- signedAtMs,
246
- token: this.config.token,
247
- nonce,
248
- platform,
249
- deviceFamily: ''
250
- })
251
-
252
- const signature = signDevicePayload(this.privateKeyPem, payload)
253
-
254
- this.ws?.send(
255
- JSON.stringify({
256
- type: 'req',
257
- id: '1',
258
- method: 'connect',
259
- params: {
260
- minProtocol: 3,
261
- maxProtocol: 3,
262
- client: {
263
- id: 'gateway-client',
264
- version: '1.0.0',
265
- platform,
266
- mode: 'backend'
267
- },
268
- auth: { token: this.config.token },
269
- role: this.config.role,
270
- scopes: this.config.scopes,
271
- device: {
272
- id: this.deviceId,
273
- publicKey: this.publicKeyB64Url,
274
- signature,
275
- signedAt: signedAtMs,
276
- nonce
277
- }
278
- }
279
- })
280
- )
281
- }
282
-
283
- /**
284
- * Handle incoming messages
285
- */
286
- private mapHelloPayloadToHelloOk(payload: Record<string, unknown>): GatewayHelloOk {
287
- const serverRaw = payload.server as Record<string, unknown> | undefined
288
- const featuresRaw = payload.features as Record<string, unknown> | undefined
289
- return {
290
- server: { connId: typeof serverRaw?.connId === 'string' ? serverRaw.connId : '' },
291
- features: {
292
- methods: Array.isArray(featuresRaw?.methods) ? (featuresRaw.methods as string[]) : [],
293
- events: Array.isArray(featuresRaw?.events) ? (featuresRaw.events as string[]) : []
294
- },
295
- policy: payload.policy as GatewayHelloOk['policy'],
296
- auth: payload.auth as GatewayHelloOk['auth']
297
- }
298
- }
299
-
300
- private handleMessage(
301
- msg: Record<string, any>,
302
- resolveHello: (helloOk: GatewayHelloOk) => void,
303
- rejectHello: (err: Error) => void,
304
- timeout: NodeJS.Timeout
305
- ): void {
306
- const msgType = msg.type as string | undefined
307
-
308
- if (msg.type === 'event' && msg.event === 'connect.challenge') {
309
- const payload = msg.payload as Record<string, unknown> | undefined
310
- const nonce = typeof payload?.nonce === 'string' ? payload.nonce.trim() : ''
311
- if (!nonce) {
312
- rejectHello(new Error('connect.challenge 缺少 nonce'))
313
- return
314
- }
315
- this.connectChallengeNonce = nonce
316
- this.sendConnect()
317
- return
318
- }
319
-
320
- // 协议 v3:握手成功为 type:"res" + payload.type:"hello-ok"(见 openclaw docs/gateway/protocol.md)
321
- if (msg.type === 'res' && !this.connected) {
322
- const ok = msg.ok === true
323
- const inner = msg.payload as Record<string, unknown> | undefined
324
- if (ok && inner && inner.type === 'hello-ok') {
325
- this.connected = true
326
- const serverRaw = inner.server as Record<string, unknown> | undefined
327
- this.connId = typeof serverRaw?.connId === 'string' ? serverRaw.connId : null
328
- clearTimeout(timeout)
329
- dcgLogger(`[Gateway] 已连接 connId=${this.connId ?? '(none)'}`)
330
- resolveHello(this.mapHelloPayloadToHelloOk(inner))
331
- return
332
- }
333
- if (msg.ok === false) {
334
- const errObj = msg.error as Record<string, unknown> | undefined
335
- const message = (typeof errObj?.message === 'string' && errObj.message) || 'Gateway connect 被拒绝'
336
- clearTimeout(timeout)
337
- rejectHello(new Error(message))
338
- return
339
- }
340
- }
341
-
342
- // 旧式或其它实现:顶层 hello-ok
343
- if (msgType === 'hello-ok' || (!msgType && msg.server)) {
344
- this.connected = true
345
- this.connId = ((msg.server as Record<string, unknown>)?.connId as string) || null
346
- clearTimeout(timeout)
347
- dcgLogger(`[Gateway] 已连接 connId=${this.connId ?? '(none)'}`)
348
- resolveHello(msg as unknown as GatewayHelloOk)
349
- return
350
- }
351
- if (msg.type === 'res') {
352
- const handler = this.pendingRpcById.get(msg.id as string)
353
- if (handler) {
354
- this.pendingRpcById.delete(msg.id as string)
355
- handler(msg as unknown as GatewayResponse)
356
- }
357
- return
358
- }
359
-
360
- if (msg.type === 'event') {
361
- void handleGatewayEventMessage(msg)
362
- .then((event) => {
363
- this.eventHandlers.forEach((h) => h(event))
364
- })
365
- .catch((err) => {
366
- dcgLogger(`[Gateway] event 处理异步失败: ${String(err)}`, 'error')
367
- })
368
- }
369
- }
370
-
371
- /**
372
- * Call a gateway method
373
- */
374
- async callMethod<T = unknown>(method: string, params: Record<string, unknown> = {}): Promise<T> {
375
- const id = crypto.randomUUID()
376
-
377
- return new Promise((resolve, reject) => {
378
- if (!this.connected || !this.ws) {
379
- reject(new Error('Not connected to gateway'))
380
- return
381
- }
382
-
383
- const timeout = setTimeout(() => {
384
- this.pendingRpcById.delete(id)
385
- reject(new Error('Method call timeout'))
386
- }, 30000)
387
-
388
- this.pendingRpcById.set(id, (response) => {
389
- clearTimeout(timeout)
390
- if (response.ok) {
391
- const body = response.result !== undefined ? response.result : (response as GatewayResponse).payload
392
- resolve(body as T)
393
- } else {
394
- reject(new Error(response.error?.message || 'Method call failed'))
395
- }
396
- })
397
-
398
- this.ws.send(
399
- JSON.stringify({
400
- type: 'req',
401
- id,
402
- method,
403
- params
404
- })
405
- )
406
- })
407
- }
408
-
409
- /**
410
- * Register event handler
411
- */
412
- onEvent(handler: (event: GatewayEvent) => void): () => void {
413
- this.eventHandlers.add(handler)
414
- return () => this.eventHandlers.delete(handler)
415
- }
416
-
417
- /**
418
- * Close connection
419
- */
420
- close(): void {
421
- this.ws?.close(1000, 'Plugin stopped')
422
- this.connected = false
423
- }
424
-
425
- /**
426
- * Check if connected
427
- */
428
- isConnected(): boolean {
429
- return this.connected
430
- }
431
-
432
- /**
433
- * Get connection ID
434
- */
435
- getConnId(): string | null {
436
- return this.connId
437
- }
438
-
439
- /**
440
- * Get the WebSocket instance (for external use)
441
- */
442
- getWebSocket(): WebSocket | null {
443
- return this.ws
444
- }
445
-
446
- /**
447
- * Ping the gateway
448
- */
449
- ping(): void {
450
- this.ws?.ping()
451
- }
452
- }
@@ -1,95 +0,0 @@
1
- // Security utilities for the tunnel plugin
2
- import crypto from 'crypto'
3
-
4
- // ED25519 SubjectPublicKeyInfo prefix (must match openclaw gateway `ED25519_SPKI_PREFIX`)
5
- const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex')
6
-
7
- /**
8
- * Raw 32-byte Ed25519 public key bytes from PEM (same rules as openclaw `derivePublicKeyRaw`).
9
- */
10
- export function derivePublicKeyRawFromPem(publicKeyPem: string): Buffer {
11
- const spki = crypto.createPublicKey(publicKeyPem).export({ type: 'spki', format: 'der' }) as Buffer
12
- if (spki.length === ED25519_SPKI_PREFIX.length + 32 && spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) {
13
- return spki.subarray(ED25519_SPKI_PREFIX.length)
14
- }
15
- return spki
16
- }
17
-
18
- /**
19
- * Base64url encode (no padding)
20
- */
21
- export function base64UrlEncode(buffer: Buffer): string {
22
- return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
23
- }
24
-
25
- /** Wire-format device public key (base64url of raw 32 bytes), matches gateway expectations. */
26
- export function publicKeyRawBase64UrlFromPem(publicKeyPem: string): string {
27
- return base64UrlEncode(derivePublicKeyRawFromPem(publicKeyPem))
28
- }
29
-
30
- /**
31
- * Derive device ID from ED25519 public key
32
- * Device ID = sha256(rawPublicKey)
33
- */
34
- export function deriveDeviceIdFromPublicKey(publicKeyPem: string): string {
35
- const rawPublicKey = derivePublicKeyRawFromPem(publicKeyPem)
36
- return crypto.createHash('sha256').update(rawPublicKey).digest('hex')
37
- }
38
-
39
- /**
40
- * Sign device payload
41
- */
42
- export function signDevicePayload(privateKeyPem: string, payload: string, encoding: 'base64' | 'base64url' = 'base64url'): string {
43
- const key = crypto.createPrivateKey(privateKeyPem)
44
- const sig = crypto.sign(null, Buffer.from(payload, 'utf8'), key)
45
- return encoding === 'base64url' ? base64UrlEncode(sig) : sig.toString('base64')
46
- }
47
- function normalizeTrimmedMetadata(value: unknown): string {
48
- if (typeof value !== 'string') return ''
49
- const trimmed = value.trim()
50
- return trimmed ? trimmed : ''
51
- }
52
-
53
- function toLowerAscii(input: string): string {
54
- return input.replace(/[A-Z]/g, (char) => String.fromCharCode(char.charCodeAt(0) + 32))
55
- }
56
-
57
- function normalizeDeviceMetadataForAuth(value: unknown): string {
58
- const trimmed = normalizeTrimmedMetadata(value)
59
- if (!trimmed) return ''
60
- return toLowerAscii(trimmed)
61
- }
62
-
63
- /**
64
- * Device authentication payload v3(与 openclaw `buildDeviceAuthPayloadV3` 一致)
65
- */
66
- export function buildDeviceAuthPayloadV3(params: {
67
- deviceId: string
68
- clientId: string
69
- clientMode: string
70
- role: string
71
- scopes: string[]
72
- signedAtMs: number
73
- token: string
74
- nonce: string
75
- platform?: string
76
- deviceFamily?: string
77
- }): string {
78
- const scopes = params.scopes.join(',')
79
- const token = params.token ?? ''
80
- const platform = normalizeDeviceMetadataForAuth(params.platform ?? '')
81
- const deviceFamily = normalizeDeviceMetadataForAuth(params.deviceFamily ?? '')
82
- return [
83
- 'v3',
84
- params.deviceId,
85
- params.clientId,
86
- params.clientMode,
87
- params.role,
88
- scopes,
89
- String(params.signedAtMs),
90
- token,
91
- params.nonce,
92
- platform,
93
- deviceFamily
94
- ].join('|')
95
- }