@xmoxmo/bncr 0.4.3 → 0.4.4
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
package/scripts/selfcheck.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import { createRequire } from 'node:module';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -182,6 +183,19 @@ const readPackageVersion = () => {
|
|
|
182
183
|
return typeof pkg?.version === 'string' ? pkg.version.trim() : '';
|
|
183
184
|
};
|
|
184
185
|
|
|
186
|
+
const readNpmLatestVersion = (packageName) => {
|
|
187
|
+
try {
|
|
188
|
+
const raw = execFileSync('npm', ['view', packageName, 'version'], {
|
|
189
|
+
encoding: 'utf8',
|
|
190
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
191
|
+
timeout: 10000,
|
|
192
|
+
}).trim();
|
|
193
|
+
return raw || null;
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
185
199
|
const requiredOpenClawSdkSubpaths = [
|
|
186
200
|
'openclaw/plugin-sdk',
|
|
187
201
|
'openclaw/plugin-sdk/boolean-param',
|
|
@@ -213,7 +227,7 @@ const resolveOpenClawSdkSubpaths = () => {
|
|
|
213
227
|
});
|
|
214
228
|
};
|
|
215
229
|
|
|
216
|
-
const validateVersionPolicy = (version) => {
|
|
230
|
+
const validateVersionPolicy = (version, latestVersion) => {
|
|
217
231
|
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
218
232
|
if (!match) {
|
|
219
233
|
return {
|
|
@@ -232,12 +246,101 @@ const validateVersionPolicy = (version) => {
|
|
|
232
246
|
};
|
|
233
247
|
}
|
|
234
248
|
|
|
249
|
+
// Prevent jumping minor when current minor still has unused patch slots
|
|
250
|
+
if (latestVersion) {
|
|
251
|
+
const lm = latestVersion.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
252
|
+
if (lm) {
|
|
253
|
+
const vMajor = Number.parseInt(match[1], 10);
|
|
254
|
+
const vMinor = Number.parseInt(match[2], 10);
|
|
255
|
+
const lMajor = Number.parseInt(lm[1], 10);
|
|
256
|
+
const lMinor = Number.parseInt(lm[2], 10);
|
|
257
|
+
const lPatch = Number.parseInt(lm[3], 10);
|
|
258
|
+
|
|
259
|
+
// Version unchanged
|
|
260
|
+
if (vMajor === lMajor && vMinor === lMinor && patch === lPatch) {
|
|
261
|
+
return { ok: false, reason: `version unchanged: ${version}`, version };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Downgrade: any component decreased
|
|
265
|
+
if (
|
|
266
|
+
vMajor < lMajor ||
|
|
267
|
+
(vMajor === lMajor && vMinor < lMinor) ||
|
|
268
|
+
(vMajor === lMajor && vMinor === lMinor && patch < lPatch)
|
|
269
|
+
) {
|
|
270
|
+
return { ok: false, reason: `downgrade from ${latestVersion} to ${version}`, version };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Patch bump: same major and minor, patch must increment by exactly +1
|
|
274
|
+
if (vMajor === lMajor && vMinor === lMinor) {
|
|
275
|
+
if (patch === lPatch + 1) return { ok: true, version };
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
reason: `patch jump from ${latestVersion} to ${version} (delta=${patch - lPatch}); expected ${lMajor}.${lMinor}.${lPatch + 1}`,
|
|
279
|
+
version,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Minor bump: same major, minor + 1
|
|
284
|
+
if (vMajor === lMajor && vMinor === lMinor + 1) {
|
|
285
|
+
if (lPatch === 9 && patch === 0) return { ok: true, version };
|
|
286
|
+
if (lPatch !== 9)
|
|
287
|
+
return {
|
|
288
|
+
ok: false,
|
|
289
|
+
reason: `minor bumped from ${latestVersion} to ${version} but ${9 - lPatch} patch slots remain in ${lMajor}.${lMinor}; prefer ${lMajor}.${lMinor}.${lPatch + 1}`,
|
|
290
|
+
version,
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
reason: `minor bump must reset patch to 0; got ${vMajor}.${vMinor}.${patch}`,
|
|
295
|
+
version,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Minor jumped by more than 1
|
|
300
|
+
if (vMajor === lMajor && vMinor > lMinor + 1) {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
reason: `minor jumped from ${latestVersion} to ${version}; expected ${lMajor}.${lMinor + 1}.0`,
|
|
304
|
+
version,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Major bump: major + 1, requires previous minor fully exhausted
|
|
309
|
+
if (vMajor === lMajor + 1) {
|
|
310
|
+
if (lMinor === 9 && lPatch === 9 && vMinor === 0 && patch === 0)
|
|
311
|
+
return { ok: true, version };
|
|
312
|
+
if (lMinor !== 9 || lPatch !== 9)
|
|
313
|
+
return {
|
|
314
|
+
ok: false,
|
|
315
|
+
reason: `major bumped from ${latestVersion} to ${version} but previous major series not exhausted; expected ${lMajor + 1}.0.0 after ${lMajor}.9.9`,
|
|
316
|
+
version,
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
ok: false,
|
|
320
|
+
reason: `major bump must reset to .0.0; got ${vMajor}.${vMinor}.${patch}`,
|
|
321
|
+
version,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Major jumped by more than 1
|
|
326
|
+
if (vMajor > lMajor + 1) {
|
|
327
|
+
return {
|
|
328
|
+
ok: false,
|
|
329
|
+
reason: `major jumped from ${latestVersion} to ${version}; expected ${lMajor + 1}.0.0`,
|
|
330
|
+
version,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
235
336
|
return { ok: true, version };
|
|
236
337
|
};
|
|
237
338
|
|
|
238
339
|
const missing = requiredFiles.filter((rel) => !fs.existsSync(path.join(root, rel)));
|
|
239
340
|
const version = readPackageVersion();
|
|
240
|
-
const
|
|
341
|
+
const packageName = '@xmoxmo/bncr';
|
|
342
|
+
const latestVersion = readNpmLatestVersion(packageName);
|
|
343
|
+
const versionPolicy = validateVersionPolicy(version, latestVersion);
|
|
241
344
|
const sdkSubpaths = resolveOpenClawSdkSubpaths();
|
|
242
345
|
const missingSdkSubpaths = sdkSubpaths.filter((entry) => !entry.ok);
|
|
243
346
|
const result = {
|
package/src/channel.ts
CHANGED
|
@@ -1727,6 +1727,10 @@ class BncrBridgeRuntime {
|
|
|
1727
1727
|
handleFileTransferPushFailure: (args) => this.handleFileTransferPushFailure(args),
|
|
1728
1728
|
handleTextPushFailure: (args) => this.handleTextPushFailure(args),
|
|
1729
1729
|
isPrePushGuardDeferral: (entry) => this.isPrePushGuardDeferral(entry),
|
|
1730
|
+
resolveAccountIdForSession: (sessionKey) => {
|
|
1731
|
+
const hit = this.sessionRoutes.get(sessionKey);
|
|
1732
|
+
return hit ? hit.accountId : BNCR_DEFAULT_ACCOUNT_ID;
|
|
1733
|
+
},
|
|
1730
1734
|
scheduleSave: () => this.scheduleSave(),
|
|
1731
1735
|
logInfo: (scope, message, options) => this.logInfo(scope, message, options),
|
|
1732
1736
|
logWarn: (scope, message, options) => this.logWarn(scope, message, options),
|
|
@@ -19,6 +19,7 @@ export function createBncrAckOutboxRuntimeGroup(runtime: {
|
|
|
19
19
|
isPlainObject: (value: unknown) => value is Record<string, unknown>;
|
|
20
20
|
clampFiniteNumber: (value: unknown, fallback: number, min?: number, max?: number) => number;
|
|
21
21
|
normalizeAccountId: (accountId: string) => string;
|
|
22
|
+
resolveAccountIdForSession: (sessionKey: string) => string | null;
|
|
22
23
|
formatDisplayScope: (route: OutboxEntry['route']) => string;
|
|
23
24
|
isFileTransferEntry: (entry: OutboxEntry) => boolean;
|
|
24
25
|
recommendedAckTimeoutMaxMs: number;
|
|
@@ -235,6 +236,7 @@ export function createBncrAckOutboxRuntimeGroup(runtime: {
|
|
|
235
236
|
handleFileTransferPushFailure: runtime.handleFileTransferPushFailure,
|
|
236
237
|
handleTextPushFailure: runtime.handleTextPushFailure,
|
|
237
238
|
isPrePushGuardDeferral: runtime.isPrePushGuardDeferral,
|
|
239
|
+
resolveAccountIdForSession: runtime.resolveAccountIdForSession,
|
|
238
240
|
moveToDeadLetter: runtime.moveToDeadLetter,
|
|
239
241
|
scheduleSave: runtime.scheduleSave,
|
|
240
242
|
logInfo: runtime.logInfo,
|
|
@@ -20,6 +20,9 @@ type BncrOutboxDrainScheduleRuntime = {
|
|
|
20
20
|
type BncrOutboxDrainFailureRuntime = {
|
|
21
21
|
backoffMs: (retryCount: number) => number;
|
|
22
22
|
outbox: Map<string, OutboxEntry>;
|
|
23
|
+
resolveAccountIdForSession: (sessionKey: string) => string | null;
|
|
24
|
+
logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
|
|
25
|
+
logWarn: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
|
|
23
26
|
isPrePushGuardDeferral: (entry: OutboxEntry) => boolean;
|
|
24
27
|
moveToDeadLetter: (entry: OutboxEntry, reason: string) => void;
|
|
25
28
|
scheduleSave: () => void;
|
|
@@ -39,6 +42,39 @@ export function createBncrOutboxDrainFailure(runtime: BncrOutboxDrainFailureRunt
|
|
|
39
42
|
const { accountId, entry, attemptedAt, updateMinOutboxDelay } = args;
|
|
40
43
|
let { localNextDelay } = args;
|
|
41
44
|
|
|
45
|
+
// If the entry keeps hitting pre-push guard (no active connection), the
|
|
46
|
+
// accountId on the entry might be wrong (e.g. constructed from route data).
|
|
47
|
+
// Try to correct it from recent inbound session context before giving up.
|
|
48
|
+
if (runtime.isPrePushGuardDeferral(entry) && entry.retryCount > 0) {
|
|
49
|
+
const corrected = runtime.resolveAccountIdForSession(entry.sessionKey);
|
|
50
|
+
if (corrected && corrected !== entry.accountId) {
|
|
51
|
+
const oldAccountId = entry.accountId;
|
|
52
|
+
runtime.logWarn(
|
|
53
|
+
'outbound',
|
|
54
|
+
`account corrected sessionKey=${entry.sessionKey} ${oldAccountId}→${corrected}`,
|
|
55
|
+
);
|
|
56
|
+
runtime.logInfo(
|
|
57
|
+
'outbound',
|
|
58
|
+
JSON.stringify({
|
|
59
|
+
event: 'account-corrected',
|
|
60
|
+
messageId: entry.messageId,
|
|
61
|
+
sessionKey: entry.sessionKey,
|
|
62
|
+
oldAccountId,
|
|
63
|
+
corrected,
|
|
64
|
+
retryCount: entry.retryCount,
|
|
65
|
+
lastError: entry.lastError,
|
|
66
|
+
}),
|
|
67
|
+
{ debugOnly: true },
|
|
68
|
+
);
|
|
69
|
+
entry.accountId = corrected;
|
|
70
|
+
entry.retryCount = 0;
|
|
71
|
+
entry.lastError = undefined;
|
|
72
|
+
runtime.outbox.set(entry.messageId, entry);
|
|
73
|
+
runtime.scheduleSave();
|
|
74
|
+
return { action: 'continue', localNextDelay };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
42
78
|
if (runtime.isPrePushGuardDeferral(entry)) {
|
|
43
79
|
const wait = runtime.prePushGuardRetryDelayMs;
|
|
44
80
|
localNextDelay = runtime.outboxDrainSchedule.scheduleAccountWait({
|
|
@@ -58,6 +58,7 @@ type BncrOutboxDrainRuntime = {
|
|
|
58
58
|
backoffMs: (retryCount: number) => number;
|
|
59
59
|
isPlainObject: (value: unknown) => value is Record<string, unknown>;
|
|
60
60
|
normalizeAccountId: (accountId: string) => string;
|
|
61
|
+
resolveAccountIdForSession: (sessionKey: string) => string | null;
|
|
61
62
|
stopped: () => boolean;
|
|
62
63
|
outbox: Map<string, OutboxEntry>;
|
|
63
64
|
deadLetter: () => OutboxEntry[];
|
|
@@ -132,7 +133,19 @@ type BncrOutboxDrainRuntime = {
|
|
|
132
133
|
|
|
133
134
|
export function createBncrOutboxDrainRuntime(runtime: BncrOutboxDrainRuntime) {
|
|
134
135
|
const handlePushedDrainEntry = createBncrOutboxDrainPostPush(runtime);
|
|
135
|
-
const handleFailedDrainEntry = createBncrOutboxDrainFailure(
|
|
136
|
+
const handleFailedDrainEntry = createBncrOutboxDrainFailure({
|
|
137
|
+
backoffMs: runtime.backoffMs,
|
|
138
|
+
outbox: runtime.outbox,
|
|
139
|
+
resolveAccountIdForSession: runtime.resolveAccountIdForSession,
|
|
140
|
+
logInfo: runtime.logInfo,
|
|
141
|
+
logWarn: runtime.logWarn,
|
|
142
|
+
isPrePushGuardDeferral: runtime.isPrePushGuardDeferral,
|
|
143
|
+
moveToDeadLetter: runtime.moveToDeadLetter,
|
|
144
|
+
scheduleSave: runtime.scheduleSave,
|
|
145
|
+
outboxDrainSchedule: runtime.outboxDrainSchedule,
|
|
146
|
+
maxRetry: runtime.maxRetry,
|
|
147
|
+
prePushGuardRetryDelayMs: runtime.prePushGuardRetryDelayMs,
|
|
148
|
+
});
|
|
136
149
|
|
|
137
150
|
return createBncrOutboxDrainLoop(runtime, {
|
|
138
151
|
handlePushedDrainEntry,
|
|
@@ -133,9 +133,17 @@ export function createBncrTargetRuntime(runtime: {
|
|
|
133
133
|
return verified;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
function resolveSessionAccountId(sessionKey: string): string | null {
|
|
137
|
+
const key = asString(sessionKey).trim();
|
|
138
|
+
if (!key) return null;
|
|
139
|
+
const hit = runtime.sessionRoutes.get(key);
|
|
140
|
+
return hit ? hit.accountId : null;
|
|
141
|
+
}
|
|
142
|
+
|
|
136
143
|
return {
|
|
137
144
|
rememberSessionRoute,
|
|
138
145
|
resolveRouteBySession,
|
|
139
146
|
resolveVerifiedTarget,
|
|
147
|
+
resolveSessionAccountId,
|
|
140
148
|
};
|
|
141
149
|
}
|