@coclaw/openclaw-coclaw 0.14.1 → 0.16.0
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 +1 -1
- package/package.json +2 -2
- package/src/auto-upgrade/updater-check.js +1 -1
- package/src/realtime-bridge.js +34 -5
- package/src/webrtc/webrtc-peer.js +71 -2
package/index.js
CHANGED
|
@@ -359,7 +359,7 @@ const plugin = {
|
|
|
359
359
|
await writeName(nameToSave);
|
|
360
360
|
const hostName = getHostName();
|
|
361
361
|
respond(true, { name: nameToSave, hostName });
|
|
362
|
-
//
|
|
362
|
+
// 仅广播本次 patch 涉及的字段;server 端按 patch 语义仅更新 payload 中出现的列
|
|
363
363
|
broadcastPluginEvent('coclaw.info.updated', { name: nameToSave, hostName });
|
|
364
364
|
}
|
|
365
365
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coclaw/openclaw-coclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "OpenClaw CoClaw channel plugin for remote chat",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"node-datachannel": "0.32.2",
|
|
63
|
-
"@coclaw/pion-node": "^0.1.
|
|
63
|
+
"@coclaw/pion-node": "^0.1.3",
|
|
64
64
|
"werift": "^0.19.0",
|
|
65
65
|
"ws": "^8.19.0"
|
|
66
66
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* updater-check.js — 版本检查
|
|
3
3
|
*
|
|
4
4
|
* 通过 `npm view` 查询 registry 最新版本,与本地 package.json 对比。
|
|
5
|
-
* 选择 npm view
|
|
5
|
+
* 选择 npm view 而非自己打 registry HTTP 接口,是因为它自动继承用户完整的
|
|
6
6
|
* npm 环境配置(registry 镜像、proxy、scoped registry、auth token 等),
|
|
7
7
|
* 避免自行解析多层 .npmrc 的复杂性。每小时一次的频率下进程启动开销可忽略。
|
|
8
8
|
*/
|
package/src/realtime-bridge.js
CHANGED
|
@@ -430,17 +430,46 @@ export class RealtimeBridge {
|
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
/**
|
|
434
|
-
async
|
|
433
|
+
/** 推送实例信息(name/hostName/pluginVersion/agentModels)到 server 和已连接的 UI */
|
|
434
|
+
async __pushInstanceInfo() {
|
|
435
435
|
try {
|
|
436
436
|
const settings = await readSettings();
|
|
437
437
|
const name = settings.name ?? null;
|
|
438
438
|
const hostName = getHostName();
|
|
439
|
-
|
|
439
|
+
const pluginVersion = await getPluginVersion();
|
|
440
|
+
const agentModels = await this.__collectAgentModels();
|
|
441
|
+
broadcastPluginEvent('coclaw.info.updated', {
|
|
442
|
+
name,
|
|
443
|
+
hostName,
|
|
444
|
+
pluginVersion,
|
|
445
|
+
agentModels,
|
|
446
|
+
});
|
|
440
447
|
}
|
|
441
448
|
catch (err) {
|
|
442
449
|
/* c8 ignore next 2 -- 防御性兜底 */
|
|
443
|
-
this.logger.warn?.(`[coclaw]
|
|
450
|
+
this.logger.warn?.(`[coclaw] pushInstanceInfo failed: ${String(err?.message ?? err)}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* 采集 agent × 有效主模型列表,用于 coclaw.info.updated 上报
|
|
456
|
+
* @returns {Promise<Array<{id: string, name: string, model: string|null}>|null>} 采集失败返回 null
|
|
457
|
+
*/
|
|
458
|
+
async __collectAgentModels() {
|
|
459
|
+
try {
|
|
460
|
+
const result = await this.__gatewayRpc('agents.list', {}, { timeoutMs: 3000 });
|
|
461
|
+
if (result?.ok !== true) return null;
|
|
462
|
+
const agents = result?.response?.payload?.agents;
|
|
463
|
+
if (!Array.isArray(agents)) return null;
|
|
464
|
+
return agents.map((a) => ({
|
|
465
|
+
id: a?.id,
|
|
466
|
+
name: a?.name ?? a?.id,
|
|
467
|
+
model: a?.model?.primary ?? null,
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
// 防御性兜底:__gatewayRpc 正常会以 { ok:false } 返回,此分支覆盖调用栈意外抛错
|
|
472
|
+
return null;
|
|
444
473
|
}
|
|
445
474
|
}
|
|
446
475
|
|
|
@@ -601,7 +630,7 @@ export class RealtimeBridge {
|
|
|
601
630
|
this.__logDebug(`gateway connect ok <- id=${payload.id}`);
|
|
602
631
|
this.gatewayConnectReqId = null;
|
|
603
632
|
this.__ensureSessionsPromise = this.__ensureAllAgentSessions();
|
|
604
|
-
this.
|
|
633
|
+
this.__pushInstanceInfo();
|
|
605
634
|
}
|
|
606
635
|
else {
|
|
607
636
|
this.gatewayReady = false;
|
|
@@ -110,6 +110,27 @@ export class WebRtcPeer {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* 向指定 connId 的 rpc DC 单播一个 JSON 帧(不走 server 中转)。
|
|
115
|
+
* 若 session/DC 未就绪返回 false,由调用方决定是否重试。
|
|
116
|
+
* @param {string} connId
|
|
117
|
+
* @param {object} payload - 完整的 JSON 帧(通常是 { type: 'event', event, payload })
|
|
118
|
+
* @returns {boolean} true=已入队发送,false=未能发送(session 不存在 / DC 未 open)
|
|
119
|
+
*/
|
|
120
|
+
sendTo(connId, payload) {
|
|
121
|
+
const session = this.__sessions.get(connId);
|
|
122
|
+
if (!session) return false;
|
|
123
|
+
const q = session.rpcSendQueue;
|
|
124
|
+
if (!q || session.rpcChannel?.readyState !== 'open') return false;
|
|
125
|
+
try {
|
|
126
|
+
q.send(JSON.stringify(payload));
|
|
127
|
+
return true;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
this.__logDebug(`[${connId}] sendTo failed: ${err.message}`);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
async __handleOffer(msg) {
|
|
114
135
|
const connId = msg.fromConnId;
|
|
115
136
|
const isIceRestart = !!msg.payload?.iceRestart;
|
|
@@ -307,6 +328,9 @@ export class WebRtcPeer {
|
|
|
307
328
|
if (pair) {
|
|
308
329
|
this.__logNominatedPair(connId, pair);
|
|
309
330
|
}
|
|
331
|
+
// ICE restart 或初次选中都会触发;让出一次 CPU 后再单播 transport 信息。
|
|
332
|
+
// 签名去重保证 pair 不变时不会重复发送。
|
|
333
|
+
queueMicrotask(() => this.__sendPeerTransport(connId));
|
|
310
334
|
};
|
|
311
335
|
}
|
|
312
336
|
|
|
@@ -424,6 +448,12 @@ export class WebRtcPeer {
|
|
|
424
448
|
dc.onopen = () => {
|
|
425
449
|
this.__remoteLog(`dc.open conn=${connId} label=${dc.label}`);
|
|
426
450
|
this.logger.info?.(`${this.__rtcTag} [${connId}] DataChannel "${dc.label}" opened`);
|
|
451
|
+
// rpc DC 建立后,把本端 transport 信息单播给 UI。
|
|
452
|
+
// queueMicrotask 让出一次 CPU:确保 pion 侧 selectedCandidatePair setter 已 assign,
|
|
453
|
+
// 同时避免在 onopen 同步栈里触发可能的重入。
|
|
454
|
+
if (dc.label === 'rpc') {
|
|
455
|
+
queueMicrotask(() => this.__sendPeerTransport(connId));
|
|
456
|
+
}
|
|
427
457
|
};
|
|
428
458
|
dc.onclose = () => {
|
|
429
459
|
this.__remoteLog(`dc.closed conn=${connId} label=${dc.label}`);
|
|
@@ -481,12 +511,51 @@ export class WebRtcPeer {
|
|
|
481
511
|
}
|
|
482
512
|
|
|
483
513
|
__logNominatedPair(connId, pair) {
|
|
484
|
-
const
|
|
485
|
-
const
|
|
514
|
+
const l = pair.local, r = pair.remote;
|
|
515
|
+
const lProto = (l?.protocol ?? '?').toLowerCase();
|
|
516
|
+
const rProto = (r?.protocol ?? '?').toLowerCase();
|
|
517
|
+
const lRelay = l?.relayProtocol ? `(${String(l.relayProtocol).toLowerCase()})` : '';
|
|
518
|
+
const localInfo = `${l?.type ?? '?'}/${lProto}${lRelay} ${l?.address ?? l?.host ?? '?'}:${l?.port ?? '?'}`;
|
|
519
|
+
const remoteInfo = `${r?.type ?? '?'}/${rProto} ${r?.address ?? r?.host ?? '?'}:${r?.port ?? '?'}`;
|
|
486
520
|
this.__remoteLog(`rtc.ice-nominated conn=${connId} local=${localInfo} remote=${remoteInfo}`);
|
|
487
521
|
this.logger.info?.(`${this.__rtcTag} [${connId}] ICE nominated: local=${localInfo} remote=${remoteInfo}`);
|
|
488
522
|
}
|
|
489
523
|
|
|
524
|
+
/**
|
|
525
|
+
* 把当前 session 本端 candidate 的 transport 信息(type/protocol/relayProtocol)
|
|
526
|
+
* 通过 coclaw.rtc.peerTransport 事件单播给对应 UI。已内置签名去重,
|
|
527
|
+
* 同一签名不会重复发送;发送失败(DC 未 open)时回滚签名允许后续重试。
|
|
528
|
+
*
|
|
529
|
+
* @param {string} connId
|
|
530
|
+
*/
|
|
531
|
+
__sendPeerTransport(connId) {
|
|
532
|
+
const session = this.__sessions.get(connId);
|
|
533
|
+
if (!session) return;
|
|
534
|
+
const local = session.pc?.selectedCandidatePair?.local;
|
|
535
|
+
if (!local) return; // nominated pair 尚未产生
|
|
536
|
+
const payload = {
|
|
537
|
+
candidateType: local.type ?? 'unknown',
|
|
538
|
+
protocol: String(local.protocol ?? 'udp').toLowerCase(),
|
|
539
|
+
relayProtocol: local.relayProtocol
|
|
540
|
+
? String(local.relayProtocol).toLowerCase()
|
|
541
|
+
: null,
|
|
542
|
+
};
|
|
543
|
+
const sig = `${payload.candidateType}|${payload.protocol}|${payload.relayProtocol ?? ''}`;
|
|
544
|
+
if (session.__lastPeerTransportSig === sig) return;
|
|
545
|
+
session.__lastPeerTransportSig = sig;
|
|
546
|
+
const ok = this.sendTo(connId, {
|
|
547
|
+
type: 'event',
|
|
548
|
+
event: 'coclaw.rtc.peerTransport',
|
|
549
|
+
payload,
|
|
550
|
+
});
|
|
551
|
+
if (!ok) {
|
|
552
|
+
// DC 尚未 open,回滚签名以便 dc.onopen 再次触发时重发
|
|
553
|
+
session.__lastPeerTransportSig = null;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.__remoteLog(`rtc.peer-transport conn=${connId} type=${payload.candidateType} proto=${payload.protocol} relay=${payload.relayProtocol ?? '-'}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
490
559
|
__remoteLog(msg) {
|
|
491
560
|
remoteLog(this.__impl ? `${msg} rtc=${this.__impl}` : msg);
|
|
492
561
|
}
|