@coclaw/openclaw-coclaw 0.26.5 → 0.26.6
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/package.json
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* 2. OPENCLAW_STATE_DIR 环境变量(worker 子进程,由 spawner 传入)
|
|
8
8
|
* 3. ~/.openclaw(兜底默认值)
|
|
9
9
|
*
|
|
10
|
+
* 注:第 3 档(homedir)实际够不着——gateway 进程内 runtime 必在(full mode 才启
|
|
11
|
+
* scheduler)、worker 进程 spawner 总会传 OPENCLAW_STATE_DIR,故不存在“worker 写错盘”。
|
|
12
|
+
* 曾被对抗式 review 当 bug 捞出,核实为不可达防御,记此免再捞。
|
|
13
|
+
* 同理“runtime 非空但 .state 缺失”的部分注入形态也不可达,勿为对齐 claw-paths.js 改成抛错。
|
|
14
|
+
*
|
|
10
15
|
* 锁策略:state 文件的 read-modify-write 统一走 stateMutex;纯读不加锁。
|
|
11
16
|
* 锁只能护住同进程内并发(gateway 与 worker 跨进程仍各写各的),但 worker /
|
|
12
17
|
* scheduler 各自内部是串行流,进程内互斥已足够。
|
|
@@ -24,8 +29,13 @@ const STATE_FILENAME = 'upgrade-state.json';
|
|
|
24
29
|
const LOG_FILENAME = 'upgrade-log.jsonl';
|
|
25
30
|
const LOG_MAX_LINES = 200;
|
|
26
31
|
const LOG_KEEP_LINES = 100;
|
|
27
|
-
// lastUpgrade.error 截断上限:远端上报行不宜过长;jsonl 保留完整 error
|
|
28
|
-
|
|
32
|
+
// lastUpgrade.error 截断上限:远端上报行不宜过长;jsonl 保留完整 error。
|
|
33
|
+
// 取 1600 是为 ≥ worker.js formatCmdFailure 复合串的理论最大长度(≈1547:
|
|
34
|
+
// prefix + message/stdout/stderr 三段各尾截 500 + 标签与 " | " 分隔符开销),
|
|
35
|
+
// 保证 worker 格式化的失败串绝不被 state.js 二次全局截断而切掉中段真因;
|
|
36
|
+
// 这里不解析 worker 的分段格式(保持两者解耦),只保留一道防御性硬上限——
|
|
37
|
+
// state.js 的 error 还来自 'boom'/'timeout'/verify 等其它短串,需防意外超长串无界落盘。
|
|
38
|
+
const ERROR_MAX_CHARS = 1600;
|
|
29
39
|
|
|
30
40
|
const stateMutex = createMutex();
|
|
31
41
|
const logMutex = createMutex();
|
|
@@ -582,8 +582,12 @@ export class AutoUpgradeScheduler {
|
|
|
582
582
|
);
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
-
// 来源验明 npm 后才上报 available
|
|
586
|
-
|
|
585
|
+
// 来源验明 npm 后才上报 available——否则永不升级的装置每小时刷一条;
|
|
586
|
+
// available 同为稳定态(直到升级落地或新版发布),与兄弟信号同模式按 (原因, toVersion) 去重防刷屏
|
|
587
|
+
this.__gateSignalOnce(
|
|
588
|
+
`available|${result.latestVersion}`,
|
|
589
|
+
`upgrade.available from=${result.currentVersion} to=${result.latestVersion}`,
|
|
590
|
+
);
|
|
587
591
|
this.__logger.info?.(`[auto-upgrade] Update available: ${result.currentVersion} → ${result.latestVersion}`);
|
|
588
592
|
|
|
589
593
|
// installPath 取自权威记录(新鲜);缺失时回退自推包根,
|
|
@@ -32,7 +32,6 @@ async function safeReaddir(dir) {
|
|
|
32
32
|
try { return await fsp.readdir(dir); }
|
|
33
33
|
catch (err) {
|
|
34
34
|
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return [];
|
|
35
|
-
/* c8 ignore next 2 -- 非 ENOENT/ENOTDIR 的 fs 错误按原样上抛 */
|
|
36
35
|
throw err;
|
|
37
36
|
}
|
|
38
37
|
}
|
|
@@ -45,7 +44,6 @@ async function safeAccess(filePath) {
|
|
|
45
44
|
try { await fsp.access(filePath); return true; }
|
|
46
45
|
catch (err) {
|
|
47
46
|
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return false;
|
|
48
|
-
/* c8 ignore next 2 -- 非 ENOENT/ENOTDIR 的 fs 错误按原样上抛 */
|
|
49
47
|
throw err;
|
|
50
48
|
}
|
|
51
49
|
}
|
|
@@ -150,12 +148,10 @@ export function createSessionManager(options = {}) {
|
|
|
150
148
|
const full = nodePath.join(dir, file);
|
|
151
149
|
let stat;
|
|
152
150
|
try { stat = await fsp.stat(full); }
|
|
153
|
-
/* c8 ignore start -- readdir→stat race window:文件被并发删除时跳过 */
|
|
154
151
|
catch (err) {
|
|
155
152
|
if (err.code === 'ENOENT') continue;
|
|
156
153
|
throw err;
|
|
157
154
|
}
|
|
158
|
-
/* c8 ignore stop */
|
|
159
155
|
const row = {
|
|
160
156
|
sessionId: parsed.sessionId,
|
|
161
157
|
sessionKey: sessionKeyById.get(parsed.sessionId) ?? null,
|
|
@@ -224,12 +220,10 @@ export function createSessionManager(options = {}) {
|
|
|
224
220
|
const full = nodePath.join(dir, name);
|
|
225
221
|
let stat;
|
|
226
222
|
try { stat = await fsp.stat(full); }
|
|
227
|
-
/* c8 ignore start -- readdir→stat race window */
|
|
228
223
|
catch (err) {
|
|
229
224
|
if (err.code === 'ENOENT') continue;
|
|
230
225
|
throw err;
|
|
231
226
|
}
|
|
232
|
-
/* c8 ignore stop */
|
|
233
227
|
candidates.push({
|
|
234
228
|
path: full,
|
|
235
229
|
archiveStamp,
|
|
@@ -240,7 +234,6 @@ export function createSessionManager(options = {}) {
|
|
|
240
234
|
if (a.archiveStamp !== b.archiveStamp) {
|
|
241
235
|
return b.archiveStamp.localeCompare(a.archiveStamp);
|
|
242
236
|
}
|
|
243
|
-
/* c8 ignore next -- 同 sessionId 同 archiveStamp 的归档实测 0 case */
|
|
244
237
|
return b.updatedAt - a.updatedAt;
|
|
245
238
|
});
|
|
246
239
|
if (candidates.length > 0) {
|
|
@@ -486,6 +486,7 @@ class FileBackedQueue {
|
|
|
486
486
|
/* c8 ignore next 2 -- rm with force rarely fails */
|
|
487
487
|
this.logger?.warn?.('fbq.handleFsError rm error', rmErr);
|
|
488
488
|
}
|
|
489
|
+
// 故意不 __dispatchSpillEnd:webrtc 只 destroy 不 clear + fsBroken 粘性不再 spill,spillActive 卡 true 不可达;即便发生也仅 monitor 观测标志失真,不丢消息
|
|
489
490
|
this.spilled = false;
|
|
490
491
|
this.writtenBytes = 0;
|
|
491
492
|
this.readOffset = 0;
|
|
@@ -729,13 +729,13 @@ export class WebRtcPeer {
|
|
|
729
729
|
// 连接状态变更(校验 pc 归属,防止旧 PC 异步回调删除新 session)
|
|
730
730
|
pc.onconnectionstatechange = () => {
|
|
731
731
|
const state = pc.connectionState;
|
|
732
|
-
this.__remoteLog(`rtc.state conn=${connId} ${state}`);
|
|
733
|
-
this.logger.info?.(`${this.__rtcTag} [${connId}] connectionState: ${state}`);
|
|
734
|
-
|
|
735
732
|
// 校验 pc 归属:旧 PC 的异步回调可能在新 session 已建立后触发
|
|
736
733
|
const cur = this.__sessions.get(connId);
|
|
737
734
|
if (!cur || cur.pc !== pc) return;
|
|
738
735
|
|
|
736
|
+
this.__remoteLog(`rtc.state conn=${connId} ${state}`);
|
|
737
|
+
this.logger.info?.(`${this.__rtcTag} [${connId}] connectionState: ${state}`);
|
|
738
|
+
|
|
739
739
|
// 离开 failed 状态时清理 TTL timer(ICE restart 恢复、自然关闭等)
|
|
740
740
|
if (state !== 'failed' && cur.__failedTimer) {
|
|
741
741
|
clearTimeout(cur.__failedTimer);
|
|
@@ -829,7 +829,7 @@ export class WebRtcPeer {
|
|
|
829
829
|
// 后变 fire-and-forget(WebRTC 实现的 ondatachannel 是 sync 回调,不能 await)
|
|
830
830
|
session.rpcChannel = channel;
|
|
831
831
|
this.__setupDataChannel(connId, channel).catch((err) => {
|
|
832
|
-
/* c8 ignore next 2 --
|
|
832
|
+
/* c8 ignore next 2 -- async 装配段(FBQ 构造+init)无内部 try/catch,此 catch 是其抛错的真实吞没点(非"防御兜底");生产不可达(仅 server 违反 connId 契约触发)暂未修,详见 TODO「__setupDataChannel 装配抛错 → rpcQueue 半残静默丢消息」 */
|
|
833
833
|
this.logger.warn?.(`${this.__rtcTag} [${connId}] __setupDataChannel error: ${err?.message}`);
|
|
834
834
|
});
|
|
835
835
|
} else if (channel.label.startsWith('file:')) {
|
|
@@ -879,6 +879,8 @@ export class WebRtcPeer {
|
|
|
879
879
|
// 注入 __onRequest 或 enqueue 到新 rpcQueue。session 已删除时(rpcChannel===null 或
|
|
880
880
|
// sess undefined)也按 stale 处理,避免向已清空的 session 注入消息。
|
|
881
881
|
const sess = this.__sessions.get(connId);
|
|
882
|
+
// 身份守卫故意放在派发处(而非 dc.onmessage 入口):reassembler 每 dc 独立、陈旧 chunk 派发前
|
|
883
|
+
// 无害,放此处可挡住所有通向派发的路径(含未来 reassembler 定时刷新等非 onmessage 旁路)。
|
|
882
884
|
if (!sess || sess.rpcChannel !== dc) {
|
|
883
885
|
return;
|
|
884
886
|
}
|
|
@@ -924,6 +926,9 @@ export class WebRtcPeer {
|
|
|
924
926
|
}, { logger: this.logger });
|
|
925
927
|
|
|
926
928
|
dc.onopen = () => {
|
|
929
|
+
// 陈旧通道早返回:旧 dc 的迟到 onopen 不应对新 session 误报 transport
|
|
930
|
+
const sess = this.__sessions.get(connId);
|
|
931
|
+
if (!sess || sess.rpcChannel !== dc) return;
|
|
927
932
|
this.__remoteLog(`dc.open conn=${connId} label=${dc.label}`);
|
|
928
933
|
this.logger.info?.(`${this.__rtcTag} [${connId}] DataChannel "${dc.label}" opened`);
|
|
929
934
|
// rpc DC 建立后,把本端 transport 信息单播给 UI。
|
|
@@ -963,6 +968,9 @@ export class WebRtcPeer {
|
|
|
963
968
|
}
|
|
964
969
|
};
|
|
965
970
|
dc.onerror = (err) => {
|
|
971
|
+
// 陈旧通道早返回:旧 dc 的迟到 onerror 不打进新 session 上下文
|
|
972
|
+
const sess = this.__sessions.get(connId);
|
|
973
|
+
if (!sess || sess.rpcChannel !== dc) return;
|
|
966
974
|
this.__remoteLog(`dc.error conn=${connId} label=${dc.label}`);
|
|
967
975
|
/* c8 ignore next -- ?./?? fallback */
|
|
968
976
|
this.logger.warn?.(`${this.__rtcTag} [${connId}] DataChannel "${dc.label}" error: ${String(err?.message ?? err)}`);
|