@coclaw/openclaw-coclaw 0.8.2 → 0.9.1
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/index.js +49 -0
- package/package.json +1 -1
- package/src/auto-upgrade/updater-spawn.js +4 -0
- package/src/config.js +13 -8
- package/src/file-manager/handler.js +179 -28
- package/src/webrtc-peer.js +3 -3
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { ChatHistoryManager } from './src/chat-history-manager/manager.js';
|
|
|
14
14
|
import { generateTitle } from './src/topic-manager/title-gen.js';
|
|
15
15
|
import { AutoUpgradeScheduler } from './src/auto-upgrade/updater.js';
|
|
16
16
|
import { getPackageInfo } from './src/auto-upgrade/updater-check.js';
|
|
17
|
+
import { createFileHandler } from './src/file-manager/handler.js';
|
|
17
18
|
|
|
18
19
|
// 延迟读取 + 缓存:避免模块加载时 package.json 损坏导致插件整体无法注册
|
|
19
20
|
let __pluginVersion = null;
|
|
@@ -472,6 +473,54 @@ const plugin = {
|
|
|
472
473
|
}
|
|
473
474
|
});
|
|
474
475
|
|
|
476
|
+
// --- 文件管理 RPC(WS fallback,RTC 路径由 webrtc-peer 本地拦截) ---
|
|
477
|
+
|
|
478
|
+
const fileHandler = createFileHandler({
|
|
479
|
+
resolveWorkspace: (agentId) => {
|
|
480
|
+
const cfg = api.runtime?.config?.loadConfig();
|
|
481
|
+
const dir = api.runtime?.agent?.resolveAgentWorkspaceDir(cfg, agentId);
|
|
482
|
+
if (!dir) {
|
|
483
|
+
const err = new Error('Cannot resolve workspace: runtime not available');
|
|
484
|
+
err.code = 'AGENT_DENIED';
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
return dir;
|
|
488
|
+
},
|
|
489
|
+
logger,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
api.registerGatewayMethod('coclaw.files.list', async ({ params, respond }) => {
|
|
493
|
+
try {
|
|
494
|
+
respond(true, await fileHandler.listFiles(params ?? {}));
|
|
495
|
+
} catch (err) {
|
|
496
|
+
respondError(respond, err);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
api.registerGatewayMethod('coclaw.files.delete', async ({ params, respond }) => {
|
|
501
|
+
try {
|
|
502
|
+
respond(true, await fileHandler.deleteFile(params ?? {}));
|
|
503
|
+
} catch (err) {
|
|
504
|
+
respondError(respond, err);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
api.registerGatewayMethod('coclaw.files.mkdir', async ({ params, respond }) => {
|
|
509
|
+
try {
|
|
510
|
+
respond(true, await fileHandler.mkdirOp(params ?? {}));
|
|
511
|
+
} catch (err) {
|
|
512
|
+
respondError(respond, err);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
api.registerGatewayMethod('coclaw.files.create', async ({ params, respond }) => {
|
|
517
|
+
try {
|
|
518
|
+
respond(true, await fileHandler.createFile(params ?? {}));
|
|
519
|
+
} catch (err) {
|
|
520
|
+
respondError(respond, err);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
475
524
|
const scheduler = new AutoUpgradeScheduler({ pluginId: api.id, logger });
|
|
476
525
|
api.registerService({
|
|
477
526
|
id: 'coclaw-auto-upgrade',
|
package/package.json
CHANGED
|
@@ -54,6 +54,10 @@ export function spawnUpgradeWorker({ pluginDir, fromVersion, toVersion, pluginId
|
|
|
54
54
|
env,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
// spawn 失败时 Node.js 会异步 emit 'error';若无监听器则变为未捕获异常导致 gateway 崩溃
|
|
58
|
+
child.on('error', (err) => {
|
|
59
|
+
logger?.warn?.(`[spawner] Worker spawn error: ${err.message}`);
|
|
60
|
+
});
|
|
57
61
|
child.unref();
|
|
58
62
|
|
|
59
63
|
logger?.info?.(`[spawner] Worker spawned (pid: ${child.pid})`);
|
package/src/config.js
CHANGED
|
@@ -29,19 +29,24 @@ function toRecord(value) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async function readJson(filePath) {
|
|
32
|
+
let raw;
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
-
if (!String(raw).trim()) {
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
37
|
-
return JSON.parse(raw);
|
|
34
|
+
raw = await fs.readFile(filePath, 'utf8');
|
|
38
35
|
}
|
|
39
36
|
catch (err) {
|
|
40
|
-
if (err?.code === 'ENOENT') {
|
|
41
|
-
|
|
42
|
-
}
|
|
37
|
+
if (err?.code === 'ENOENT') return {};
|
|
38
|
+
/* c8 ignore next 2 */
|
|
43
39
|
throw err;
|
|
44
40
|
}
|
|
41
|
+
if (!String(raw).trim()) return {};
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// 文件损坏,删除后当空文件处理
|
|
47
|
+
await fs.unlink(filePath).catch(() => {});
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
const bindingsMutex = createMutex();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import nodePath from 'node:path';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { randomUUID, randomBytes } from 'node:crypto';
|
|
5
5
|
|
|
6
6
|
// --- 常量 ---
|
|
7
7
|
|
|
@@ -93,6 +93,7 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
93
93
|
const _readdir = deps.readdir ?? fsp.readdir;
|
|
94
94
|
const _unlink = deps.unlink ?? fsp.unlink;
|
|
95
95
|
const _rmdir = deps.rmdir ?? fsp.rmdir;
|
|
96
|
+
const _rm = deps.rm ?? fsp.rm;
|
|
96
97
|
const _stat = deps.stat ?? fsp.stat;
|
|
97
98
|
const _mkdir = deps.mkdir ?? fsp.mkdir;
|
|
98
99
|
const _rename = deps.rename ?? fsp.rename;
|
|
@@ -102,22 +103,28 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
102
103
|
|
|
103
104
|
const pathDeps = { lstat: _lstat, realpath: _realpath };
|
|
104
105
|
|
|
105
|
-
// --- RPC 处理(rpc DC 上的 coclaw.
|
|
106
|
+
// --- RPC 处理(rpc DC 上的 coclaw.files.* 方法) ---
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
|
-
* 处理 rpc DC 上的 coclaw.
|
|
109
|
+
* 处理 rpc DC 上的 coclaw.files.* 请求
|
|
109
110
|
* @param {object} payload - { id, method, params }
|
|
110
111
|
* @param {function} sendFn - (responseObj) => void
|
|
111
112
|
*/
|
|
112
113
|
async function handleRpcRequest(payload, sendFn) {
|
|
113
114
|
const { id, method, params } = payload;
|
|
114
115
|
try {
|
|
115
|
-
if (method === 'coclaw.
|
|
116
|
+
if (method === 'coclaw.files.list') {
|
|
116
117
|
const result = await listFiles(params);
|
|
117
118
|
sendFn({ type: 'res', id, ok: true, payload: result });
|
|
118
|
-
} else if (method === 'coclaw.
|
|
119
|
+
} else if (method === 'coclaw.files.delete') {
|
|
119
120
|
const result = await deleteFile(params);
|
|
120
121
|
sendFn({ type: 'res', id, ok: true, payload: result });
|
|
122
|
+
} else if (method === 'coclaw.files.mkdir') {
|
|
123
|
+
const result = await mkdirOp(params);
|
|
124
|
+
sendFn({ type: 'res', id, ok: true, payload: result });
|
|
125
|
+
} else if (method === 'coclaw.files.create') {
|
|
126
|
+
const result = await createFile(params);
|
|
127
|
+
sendFn({ type: 'res', id, ok: true, payload: result });
|
|
121
128
|
} else {
|
|
122
129
|
sendFn({
|
|
123
130
|
type: 'res', id, ok: false,
|
|
@@ -207,15 +214,19 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
207
214
|
throw e;
|
|
208
215
|
}
|
|
209
216
|
if (stat.isDirectory()) {
|
|
210
|
-
|
|
211
|
-
await
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
if (params?.force) {
|
|
218
|
+
await _rm(resolved, { recursive: true, force: true });
|
|
219
|
+
} else {
|
|
220
|
+
try {
|
|
221
|
+
await _rmdir(resolved);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
if (e.code === 'ENOTEMPTY') {
|
|
224
|
+
const err = new Error(`Directory not empty: ${userPath}`);
|
|
225
|
+
err.code = 'NOT_EMPTY';
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
throw e;
|
|
217
229
|
}
|
|
218
|
-
throw e;
|
|
219
230
|
}
|
|
220
231
|
} else {
|
|
221
232
|
await _unlink(resolved);
|
|
@@ -223,6 +234,79 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
223
234
|
return {};
|
|
224
235
|
}
|
|
225
236
|
|
|
237
|
+
async function mkdirOp(params) {
|
|
238
|
+
const agentId = params?.agentId?.trim?.() || 'main';
|
|
239
|
+
const userPath = params?.path;
|
|
240
|
+
if (!userPath) {
|
|
241
|
+
const err = new Error('path is required');
|
|
242
|
+
err.code = 'PATH_DENIED';
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
const workspaceDir = await resolveWorkspace(agentId);
|
|
246
|
+
const resolved = await validatePath(workspaceDir, userPath, pathDeps);
|
|
247
|
+
await _mkdir(resolved, { recursive: true });
|
|
248
|
+
return {};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function createFile(params) {
|
|
252
|
+
const agentId = params?.agentId?.trim?.() || 'main';
|
|
253
|
+
const userPath = params?.path;
|
|
254
|
+
if (!userPath) {
|
|
255
|
+
const err = new Error('path is required');
|
|
256
|
+
err.code = 'PATH_DENIED';
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
const workspaceDir = await resolveWorkspace(agentId);
|
|
260
|
+
const resolved = await validatePath(workspaceDir, userPath, pathDeps);
|
|
261
|
+
|
|
262
|
+
// 检查文件是否已存在
|
|
263
|
+
try {
|
|
264
|
+
await _lstat(resolved);
|
|
265
|
+
// 没抛异常说明存在
|
|
266
|
+
const err = new Error(`File already exists: ${userPath}`);
|
|
267
|
+
err.code = 'ALREADY_EXISTS';
|
|
268
|
+
throw err;
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e.code === 'ALREADY_EXISTS') throw e;
|
|
271
|
+
if (e.code !== 'ENOENT') throw e;
|
|
272
|
+
// ENOENT — 不存在,继续创建
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 确保父目录存在
|
|
276
|
+
await _mkdir(nodePath.dirname(resolved), { recursive: true });
|
|
277
|
+
await (deps.writeFile ?? fsp.writeFile)(resolved, '');
|
|
278
|
+
return {};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 生成唯一文件名:<name>-<4hex>.<ext>,碰撞时重试
|
|
283
|
+
* @param {string} dir - 目标目录绝对路径
|
|
284
|
+
* @param {string} fileName - 原始文件名
|
|
285
|
+
* @returns {Promise<string>} 唯一文件名(仅文件名,非完整路径)
|
|
286
|
+
*/
|
|
287
|
+
async function generateUniqueName(dir, fileName) {
|
|
288
|
+
const ext = nodePath.extname(fileName);
|
|
289
|
+
const base = nodePath.basename(fileName, ext);
|
|
290
|
+
const maxAttempts = 20;
|
|
291
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
292
|
+
const hex = randomBytes(2).toString('hex');
|
|
293
|
+
const candidate = `${base}-${hex}${ext}`;
|
|
294
|
+
try {
|
|
295
|
+
await _lstat(nodePath.join(dir, candidate));
|
|
296
|
+
// 存在 → 碰撞,重试
|
|
297
|
+
} catch (e) {
|
|
298
|
+
if (e.code === 'ENOENT') return candidate;
|
|
299
|
+
/* c8 ignore next -- lstat 非 ENOENT 属罕见 IO 异常 */
|
|
300
|
+
throw e;
|
|
301
|
+
} /* c8 ignore next */
|
|
302
|
+
}
|
|
303
|
+
/* c8 ignore start -- 20 次均碰撞几乎不可能 */
|
|
304
|
+
const err = new Error(`Cannot generate unique name for: ${fileName}`);
|
|
305
|
+
err.code = 'WRITE_FAILED';
|
|
306
|
+
throw err;
|
|
307
|
+
/* c8 ignore stop */
|
|
308
|
+
}
|
|
309
|
+
|
|
226
310
|
// --- File DataChannel 处理 ---
|
|
227
311
|
|
|
228
312
|
/**
|
|
@@ -262,15 +346,20 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
262
346
|
return;
|
|
263
347
|
}
|
|
264
348
|
|
|
265
|
-
if (req.method === '
|
|
266
|
-
/* c8 ignore next 3 --
|
|
267
|
-
|
|
268
|
-
log.warn?.(`[coclaw/file]
|
|
349
|
+
if (req.method === 'GET') {
|
|
350
|
+
/* c8 ignore next 3 -- handleGet 内部已完整处理异常,此 catch 纯防御 */
|
|
351
|
+
handleGet(dc, req).catch((err) => {
|
|
352
|
+
log.warn?.(`[coclaw/file] GET error: ${err.message}`);
|
|
353
|
+
});
|
|
354
|
+
} else if (req.method === 'PUT') {
|
|
355
|
+
/* c8 ignore next 3 -- handlePut 内部已完整处理异常,此 catch 纯防御 */
|
|
356
|
+
handlePut(dc, req).catch((err) => {
|
|
357
|
+
log.warn?.(`[coclaw/file] PUT error: ${err.message}`);
|
|
269
358
|
});
|
|
270
|
-
} else if (req.method === '
|
|
271
|
-
/* c8 ignore next 3 --
|
|
272
|
-
|
|
273
|
-
log.warn?.(`[coclaw/file]
|
|
359
|
+
} else if (req.method === 'POST') {
|
|
360
|
+
/* c8 ignore next 3 -- handlePost 内部已完整处理异常,此 catch 纯防御 */
|
|
361
|
+
handlePost(dc, req).catch((err) => {
|
|
362
|
+
log.warn?.(`[coclaw/file] POST error: ${err.message}`);
|
|
274
363
|
});
|
|
275
364
|
} else {
|
|
276
365
|
sendError(dc, 'UNKNOWN_METHOD', `Unknown method: ${req.method}`);
|
|
@@ -278,7 +367,7 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
278
367
|
};
|
|
279
368
|
}
|
|
280
369
|
|
|
281
|
-
async function
|
|
370
|
+
async function handleGet(dc, req) {
|
|
282
371
|
let workspaceDir, resolved;
|
|
283
372
|
try {
|
|
284
373
|
const agentId = req.agentId?.trim?.() || 'main'; /* c8 ignore next -- ?./?? fallback */
|
|
@@ -367,7 +456,7 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
367
456
|
});
|
|
368
457
|
}
|
|
369
458
|
|
|
370
|
-
async function
|
|
459
|
+
async function handlePut(dc, req) {
|
|
371
460
|
let workspaceDir, resolved;
|
|
372
461
|
try {
|
|
373
462
|
const agentId = req.agentId?.trim?.() || 'main';
|
|
@@ -377,7 +466,59 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
377
466
|
sendError(dc, err.code ?? 'INTERNAL_ERROR', err.message);
|
|
378
467
|
return;
|
|
379
468
|
}
|
|
469
|
+
await receiveUpload(dc, req, resolved);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function handlePost(dc, req) {
|
|
473
|
+
let workspaceDir, dirResolved;
|
|
474
|
+
try {
|
|
475
|
+
const agentId = req.agentId?.trim?.() || 'main';
|
|
476
|
+
workspaceDir = await resolveWorkspace(agentId);
|
|
477
|
+
dirResolved = await validatePath(workspaceDir, req.path || '.', pathDeps);
|
|
478
|
+
} catch (err) {
|
|
479
|
+
sendError(dc, err.code ?? 'INTERNAL_ERROR', err.message);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
380
482
|
|
|
483
|
+
const fileName = req.fileName;
|
|
484
|
+
if (!fileName || typeof fileName !== 'string') {
|
|
485
|
+
sendError(dc, 'INVALID_INPUT', 'fileName is required for POST');
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 确保集合目录存在
|
|
490
|
+
try {
|
|
491
|
+
await _mkdir(dirResolved, { recursive: true });
|
|
492
|
+
} catch (err) {
|
|
493
|
+
sendError(dc, 'WRITE_FAILED', `Cannot create directory: ${err.message}`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 生成唯一文件名
|
|
498
|
+
let uniqueName;
|
|
499
|
+
try {
|
|
500
|
+
uniqueName = await generateUniqueName(dirResolved, fileName);
|
|
501
|
+
/* c8 ignore start -- generateUniqueName 内部已处理,此为防御 */
|
|
502
|
+
} catch (err) {
|
|
503
|
+
sendError(dc, err.code ?? 'WRITE_FAILED', err.message);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
/* c8 ignore stop */
|
|
507
|
+
|
|
508
|
+
const resolved = nodePath.join(dirResolved, uniqueName);
|
|
509
|
+
// 计算相对于 workspace 的路径,作为响应中的 path
|
|
510
|
+
const relativePath = nodePath.relative(workspaceDir, resolved);
|
|
511
|
+
await receiveUpload(dc, req, resolved, relativePath);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* 共享上传接收逻辑(PUT/POST 复用)
|
|
516
|
+
* @param {object} dc - DataChannel
|
|
517
|
+
* @param {object} req - 请求对象(含 size)
|
|
518
|
+
* @param {string} resolved - 目标文件绝对路径
|
|
519
|
+
* @param {string} [relativePath] - POST 时附带的相对路径(响应中返回)
|
|
520
|
+
*/
|
|
521
|
+
async function receiveUpload(dc, req, resolved, relativePath) {
|
|
381
522
|
const declaredSize = req.size;
|
|
382
523
|
if (!Number.isFinite(declaredSize) || declaredSize < 0) {
|
|
383
524
|
sendError(dc, 'INVALID_INPUT', 'size must be a non-negative number');
|
|
@@ -388,7 +529,7 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
388
529
|
return;
|
|
389
530
|
}
|
|
390
531
|
|
|
391
|
-
//
|
|
532
|
+
// 确保目标目录存在(PUT 场景需要;POST 已在上层创建,幂等无害)
|
|
392
533
|
const targetDir = nodePath.dirname(resolved);
|
|
393
534
|
try {
|
|
394
535
|
await _mkdir(targetDir, { recursive: true });
|
|
@@ -460,8 +601,10 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
460
601
|
try { dc.close(); } catch { /* ignore */ }
|
|
461
602
|
return;
|
|
462
603
|
}
|
|
604
|
+
const result = { ok: true, bytes: receivedBytes };
|
|
605
|
+
if (relativePath) result.path = relativePath;
|
|
463
606
|
try {
|
|
464
|
-
dc.send(JSON.stringify(
|
|
607
|
+
dc.send(JSON.stringify(result));
|
|
465
608
|
/* c8 ignore next */
|
|
466
609
|
} catch { /* ignore */ }
|
|
467
610
|
/* c8 ignore next */
|
|
@@ -577,11 +720,19 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
577
720
|
handleFileChannel,
|
|
578
721
|
scheduleTmpCleanup,
|
|
579
722
|
cancelCleanup,
|
|
580
|
-
|
|
723
|
+
listFiles,
|
|
724
|
+
deleteFile,
|
|
725
|
+
mkdirOp,
|
|
726
|
+
createFile,
|
|
727
|
+
// 向后兼容(测试中已使用 __ 前缀)
|
|
581
728
|
__listFiles: listFiles,
|
|
582
729
|
__deleteFile: deleteFile,
|
|
583
|
-
|
|
584
|
-
|
|
730
|
+
__mkdirOp: mkdirOp,
|
|
731
|
+
__createFile: createFile,
|
|
732
|
+
__handleGet: handleGet,
|
|
733
|
+
__handlePut: handlePut,
|
|
734
|
+
__handlePost: handlePost,
|
|
735
|
+
__generateUniqueName: generateUniqueName,
|
|
585
736
|
__cleanupTmpFilesInDir: cleanupTmpFilesInDir,
|
|
586
737
|
};
|
|
587
738
|
}
|
package/src/webrtc-peer.js
CHANGED
|
@@ -10,7 +10,7 @@ export class WebRtcPeer {
|
|
|
10
10
|
* @param {object} opts
|
|
11
11
|
* @param {function} opts.onSend - 将信令消息交给 RealtimeBridge 发送
|
|
12
12
|
* @param {function} [opts.onRequest] - DataChannel 收到 req 消息时的回调 (payload, connId) => void
|
|
13
|
-
* @param {function} [opts.onFileRpc] - rpc DC 上 coclaw.
|
|
13
|
+
* @param {function} [opts.onFileRpc] - rpc DC 上 coclaw.files.* 请求的回调 (payload, sendFn, connId) => void
|
|
14
14
|
* @param {function} [opts.onFileChannel] - file:<transferId> DataChannel 的回调 (dc, connId) => void
|
|
15
15
|
* @param {object} [opts.logger] - pino 风格 logger
|
|
16
16
|
* @param {function} [opts.PeerConnection] - 可替换的构造函数(测试用)
|
|
@@ -218,8 +218,8 @@ export class WebRtcPeer {
|
|
|
218
218
|
const reassembler = createReassembler((jsonStr) => {
|
|
219
219
|
const payload = JSON.parse(jsonStr);
|
|
220
220
|
if (payload.type === 'req') {
|
|
221
|
-
// coclaw.
|
|
222
|
-
if (payload.method?.startsWith('coclaw.
|
|
221
|
+
// coclaw.files.* 方法本地处理,不转发 gateway
|
|
222
|
+
if (payload.method?.startsWith('coclaw.files.') && this.__onFileRpc) {
|
|
223
223
|
const session = this.__sessions.get(connId);
|
|
224
224
|
const sendFn = (response) => {
|
|
225
225
|
try {
|