@coclaw/openclaw-coclaw 0.9.0 → 0.9.2
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/file-manager/handler.js +5 -1
- package/src/realtime-bridge.js +42 -27
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})`);
|
|
@@ -720,7 +720,11 @@ export function createFileHandler({ resolveWorkspace, logger, deps = {} }) {
|
|
|
720
720
|
handleFileChannel,
|
|
721
721
|
scheduleTmpCleanup,
|
|
722
722
|
cancelCleanup,
|
|
723
|
-
|
|
723
|
+
listFiles,
|
|
724
|
+
deleteFile,
|
|
725
|
+
mkdirOp,
|
|
726
|
+
createFile,
|
|
727
|
+
// 向后兼容(测试中已使用 __ 前缀)
|
|
724
728
|
__listFiles: listFiles,
|
|
725
729
|
__deleteFile: deleteFile,
|
|
726
730
|
__mkdirOp: mkdirOp,
|
package/src/realtime-bridge.js
CHANGED
|
@@ -98,6 +98,7 @@ export class RealtimeBridge {
|
|
|
98
98
|
this.__serverHbMissCount = 0;
|
|
99
99
|
this.__deviceIdentity = null;
|
|
100
100
|
this.webrtcPeer = null;
|
|
101
|
+
this.__webrtcPeerReady = null;
|
|
101
102
|
this.__fileHandler = null;
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -196,6 +197,35 @@ export class RealtimeBridge {
|
|
|
196
197
|
this.gatewayPendingRequests.clear();
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
/** 懒加载 WebRtcPeer(promise 锁防并发重复创建) */
|
|
201
|
+
async __initWebrtcPeer() {
|
|
202
|
+
const { WebRtcPeer } = await import('./webrtc-peer.js');
|
|
203
|
+
const { createFileHandler } = await import('./file-manager/handler.js');
|
|
204
|
+
/* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
|
|
205
|
+
this.__fileHandler = createFileHandler({
|
|
206
|
+
resolveWorkspace: (agentId) => this.__resolveWorkspace(agentId),
|
|
207
|
+
logger: this.logger,
|
|
208
|
+
});
|
|
209
|
+
this.__fileHandler.scheduleTmpCleanup(() => this.__listAgentWorkspaces());
|
|
210
|
+
/* c8 ignore stop */
|
|
211
|
+
this.webrtcPeer = new WebRtcPeer({
|
|
212
|
+
onSend: (msg) => this.__forwardToServer(msg),
|
|
213
|
+
onRequest: (dcPayload) => {
|
|
214
|
+
void this.__handleGatewayRequestFromServer(dcPayload);
|
|
215
|
+
},
|
|
216
|
+
/* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
|
|
217
|
+
onFileRpc: (payload, sendFn) => {
|
|
218
|
+
this.__fileHandler.handleRpcRequest(payload, sendFn)
|
|
219
|
+
.catch((err) => this.logger.warn?.(`[coclaw/file] rpc error: ${err.message}`));
|
|
220
|
+
},
|
|
221
|
+
onFileChannel: (dc) => {
|
|
222
|
+
this.__fileHandler.handleFileChannel(dc);
|
|
223
|
+
},
|
|
224
|
+
/* c8 ignore stop */
|
|
225
|
+
logger: this.logger,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
199
229
|
/* c8 ignore next 7 -- 防御性检查,serverWs 通常在调用时可用 */
|
|
200
230
|
__forwardToServer(payload) {
|
|
201
231
|
if (!this.serverWs || this.serverWs.readyState !== 1) {
|
|
@@ -681,9 +711,12 @@ export class RealtimeBridge {
|
|
|
681
711
|
if (!this.started || this.reconnectTimer) {
|
|
682
712
|
return;
|
|
683
713
|
}
|
|
684
|
-
this.reconnectTimer = setTimeout(
|
|
714
|
+
this.reconnectTimer = setTimeout(() => {
|
|
685
715
|
this.reconnectTimer = null;
|
|
686
|
-
|
|
716
|
+
this.__connectIfNeeded().catch((err) => {
|
|
717
|
+
/* c8 ignore next -- 防御性兜底,__connectIfNeeded 内部已有完整错误处理 */
|
|
718
|
+
this.logger.warn?.(`[coclaw] reconnect failed: ${err?.message}`);
|
|
719
|
+
});
|
|
687
720
|
}, RECONNECT_MS);
|
|
688
721
|
this.reconnectTimer.unref?.();
|
|
689
722
|
}
|
|
@@ -754,33 +787,13 @@ export class RealtimeBridge {
|
|
|
754
787
|
}
|
|
755
788
|
if (payload?.type?.startsWith('rtc:')) {
|
|
756
789
|
try {
|
|
757
|
-
if (!this.
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
this.__fileHandler = createFileHandler({
|
|
762
|
-
resolveWorkspace: (agentId) => this.__resolveWorkspace(agentId),
|
|
763
|
-
logger: this.logger,
|
|
764
|
-
});
|
|
765
|
-
this.__fileHandler.scheduleTmpCleanup(() => this.__listAgentWorkspaces());
|
|
766
|
-
/* c8 ignore stop */
|
|
767
|
-
this.webrtcPeer = new WebRtcPeer({
|
|
768
|
-
onSend: (msg) => this.__forwardToServer(msg),
|
|
769
|
-
onRequest: (dcPayload) => {
|
|
770
|
-
void this.__handleGatewayRequestFromServer(dcPayload);
|
|
771
|
-
},
|
|
772
|
-
/* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
|
|
773
|
-
onFileRpc: (payload, sendFn) => {
|
|
774
|
-
this.__fileHandler.handleRpcRequest(payload, sendFn)
|
|
775
|
-
.catch((err) => this.logger.warn?.(`[coclaw/file] rpc error: ${err.message}`));
|
|
776
|
-
},
|
|
777
|
-
onFileChannel: (dc) => {
|
|
778
|
-
this.__fileHandler.handleFileChannel(dc);
|
|
779
|
-
},
|
|
780
|
-
/* c8 ignore stop */
|
|
781
|
-
logger: this.logger,
|
|
790
|
+
if (!this.__webrtcPeerReady) {
|
|
791
|
+
this.__webrtcPeerReady = this.__initWebrtcPeer().catch((err) => {
|
|
792
|
+
this.__webrtcPeerReady = null;
|
|
793
|
+
throw err;
|
|
782
794
|
});
|
|
783
795
|
}
|
|
796
|
+
await this.__webrtcPeerReady;
|
|
784
797
|
await this.webrtcPeer.handleSignaling(payload);
|
|
785
798
|
} catch (err) {
|
|
786
799
|
this.logger.warn?.(`[coclaw/rtc] signaling error (or werift not found): ${err?.message}`);
|
|
@@ -816,6 +829,7 @@ export class RealtimeBridge {
|
|
|
816
829
|
/* c8 ignore next 3 -- 防御性兜底,werift close 异常时不可崩溃 gateway */
|
|
817
830
|
catch (e) { this.logger.warn?.(`[coclaw/rtc] closeAll failed: ${e?.message}`); }
|
|
818
831
|
this.webrtcPeer = null;
|
|
832
|
+
this.__webrtcPeerReady = null;
|
|
819
833
|
}
|
|
820
834
|
if (this.__fileHandler) {
|
|
821
835
|
this.__fileHandler.cancelCleanup();
|
|
@@ -882,6 +896,7 @@ export class RealtimeBridge {
|
|
|
882
896
|
if (this.webrtcPeer) {
|
|
883
897
|
await this.webrtcPeer.closeAll().catch(() => {});
|
|
884
898
|
this.webrtcPeer = null;
|
|
899
|
+
this.__webrtcPeerReady = null;
|
|
885
900
|
}
|
|
886
901
|
if (this.__fileHandler) {
|
|
887
902
|
this.__fileHandler.cancelCleanup();
|