@xmoxmo/bncr 0.5.8 → 0.6.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/package.json +1 -1
- package/scripts/selfcheck.mjs +48 -10
- package/src/channel.ts +1 -0
- package/src/messaging/inbound/dispatch-prep.ts +5 -1
- package/src/messaging/outbound/marker-parser.ts +20 -1
- package/src/plugin/ack-outbox-runtime-group.ts +2 -0
- package/src/plugin/bridge-outbox-facade.ts +14 -1
- package/src/plugin/outbox-drain-failure.ts +34 -1
- package/src/plugin/outbox-drain-runtime.ts +3 -0
package/package.json
CHANGED
package/scripts/selfcheck.mjs
CHANGED
|
@@ -12,13 +12,15 @@ const require = createRequire(import.meta.url);
|
|
|
12
12
|
const requiredRootFiles = ['index.ts', 'openclaw.plugin.json'];
|
|
13
13
|
|
|
14
14
|
const requiredSourceFiles = [
|
|
15
|
-
'src/channel.ts',
|
|
16
15
|
'src/bootstrap/channel-plugin-runtime.ts',
|
|
17
16
|
'src/bootstrap/cli.ts',
|
|
17
|
+
'src/bootstrap/register-runtime-gateway.ts',
|
|
18
18
|
'src/bootstrap/register-runtime-helpers.ts',
|
|
19
|
+
'src/bootstrap/register-runtime-singleton.ts',
|
|
19
20
|
'src/bootstrap/register-runtime.ts',
|
|
20
21
|
'src/bootstrap/runtime-discovery.ts',
|
|
21
22
|
'src/bootstrap/runtime-loader.ts',
|
|
23
|
+
'src/channel.ts',
|
|
22
24
|
'src/core/accounts.ts',
|
|
23
25
|
'src/core/config-schema.ts',
|
|
24
26
|
'src/core/connection-capability.ts',
|
|
@@ -54,35 +56,46 @@ const requiredSourceFiles = [
|
|
|
54
56
|
'src/core/status.ts',
|
|
55
57
|
'src/core/targets.ts',
|
|
56
58
|
'src/core/types.ts',
|
|
59
|
+
'src/core/value-sanitize.ts',
|
|
57
60
|
'src/messaging/inbound/commands.ts',
|
|
58
61
|
'src/messaging/inbound/context-facts.ts',
|
|
62
|
+
'src/messaging/inbound/contracts.ts',
|
|
59
63
|
'src/messaging/inbound/dispatch-prep.ts',
|
|
60
64
|
'src/messaging/inbound/dispatch.ts',
|
|
61
65
|
'src/messaging/inbound/gate.ts',
|
|
66
|
+
'src/messaging/inbound/group-history.ts',
|
|
62
67
|
'src/messaging/inbound/last-route.ts',
|
|
63
68
|
'src/messaging/inbound/media-url-download.ts',
|
|
69
|
+
'src/messaging/inbound/native-command-runtime.ts',
|
|
64
70
|
'src/messaging/inbound/native-command.ts',
|
|
65
71
|
'src/messaging/inbound/native-reply-delivery.ts',
|
|
66
72
|
'src/messaging/inbound/parse.ts',
|
|
67
73
|
'src/messaging/inbound/remote-media.ts',
|
|
68
74
|
'src/messaging/inbound/reply-config.ts',
|
|
75
|
+
'src/messaging/inbound/reply-dispatch-serial.ts',
|
|
69
76
|
'src/messaging/inbound/reply-dispatch.ts',
|
|
70
77
|
'src/messaging/inbound/runtime-compat.ts',
|
|
78
|
+
'src/messaging/inbound/scene-admin.ts',
|
|
71
79
|
'src/messaging/inbound/session-label.ts',
|
|
80
|
+
'src/messaging/inbound/session-meta-task.ts',
|
|
72
81
|
'src/messaging/inbound/turn-context.ts',
|
|
73
82
|
'src/messaging/outbound/actions.ts',
|
|
74
|
-
'src/messaging/outbound/diagnostics.ts',
|
|
75
83
|
'src/messaging/outbound/diagnostics-debug-builders.ts',
|
|
84
|
+
'src/messaging/outbound/diagnostics.ts',
|
|
76
85
|
'src/messaging/outbound/durable-queue-adapter.ts',
|
|
86
|
+
'src/messaging/outbound/marker-parser.ts',
|
|
77
87
|
'src/messaging/outbound/media-dedupe.ts',
|
|
78
88
|
'src/messaging/outbound/media.ts',
|
|
89
|
+
'src/messaging/outbound/normalize-outbound-send.ts',
|
|
79
90
|
'src/messaging/outbound/queue-selectors.ts',
|
|
80
91
|
'src/messaging/outbound/reasons.ts',
|
|
92
|
+
'src/messaging/outbound/reply-enqueue-media.ts',
|
|
81
93
|
'src/messaging/outbound/reply-enqueue.ts',
|
|
82
94
|
'src/messaging/outbound/reply-target-policy.ts',
|
|
83
95
|
'src/messaging/outbound/retry-policy.ts',
|
|
84
96
|
'src/messaging/outbound/session-route.ts',
|
|
85
97
|
'src/messaging/outbound/target-resolver.ts',
|
|
98
|
+
'src/openclaw/channel-runtime-contracts.ts',
|
|
86
99
|
'src/openclaw/config-runtime.ts',
|
|
87
100
|
'src/openclaw/inbound-session-runtime.ts',
|
|
88
101
|
'src/openclaw/media-runtime.ts',
|
|
@@ -92,6 +105,20 @@ const requiredSourceFiles = [
|
|
|
92
105
|
'src/openclaw/sdk-helpers.ts',
|
|
93
106
|
'src/openclaw/session-route-runtime.ts',
|
|
94
107
|
'src/plugin/ack-outbox-runtime-group.ts',
|
|
108
|
+
'src/plugin/bridge-ack-facade.ts',
|
|
109
|
+
'src/plugin/bridge-connection-facade.ts',
|
|
110
|
+
'src/plugin/bridge-diagnostics-facade.ts',
|
|
111
|
+
'src/plugin/bridge-drain-facade.ts',
|
|
112
|
+
'src/plugin/bridge-extended-diagnostics-facade.ts',
|
|
113
|
+
'src/plugin/bridge-lifecycle.ts',
|
|
114
|
+
'src/plugin/bridge-media-facade.ts',
|
|
115
|
+
'src/plugin/bridge-outbox-facade.ts',
|
|
116
|
+
'src/plugin/bridge-runtime-helpers.ts',
|
|
117
|
+
'src/plugin/bridge-runtime-snapshots.ts',
|
|
118
|
+
'src/plugin/bridge-runtime-surface-facade.ts',
|
|
119
|
+
'src/plugin/bridge-status-facade.ts',
|
|
120
|
+
'src/plugin/bridge-status-worker-facade.ts',
|
|
121
|
+
'src/plugin/bridge-support-runtime.ts',
|
|
95
122
|
'src/plugin/bridge-surface-handlers-group.ts',
|
|
96
123
|
'src/plugin/bridge-surface-helpers.ts',
|
|
97
124
|
'src/plugin/capabilities.ts',
|
|
@@ -99,16 +126,21 @@ const requiredSourceFiles = [
|
|
|
99
126
|
'src/plugin/channel-inbound-helpers.ts',
|
|
100
127
|
'src/plugin/channel-plugin-bridge-group.ts',
|
|
101
128
|
'src/plugin/channel-plugin-surface-group.ts',
|
|
102
|
-
'src/plugin/channel-runtime-
|
|
129
|
+
'src/plugin/channel-runtime-builders-delivery.ts',
|
|
130
|
+
'src/plugin/channel-runtime-builders-status.ts',
|
|
103
131
|
'src/plugin/channel-runtime-builders.ts',
|
|
132
|
+
'src/plugin/channel-runtime-constants.ts',
|
|
104
133
|
'src/plugin/channel-runtime-types.ts',
|
|
105
134
|
'src/plugin/channel-send.ts',
|
|
106
135
|
'src/plugin/channel-utils.ts',
|
|
107
136
|
'src/plugin/config.ts',
|
|
137
|
+
'src/plugin/connection-handlers-helpers.ts',
|
|
108
138
|
'src/plugin/connection-handlers.ts',
|
|
109
|
-
'src/plugin/connection-state.ts',
|
|
139
|
+
'src/plugin/connection-state-helpers.ts',
|
|
110
140
|
'src/plugin/connection-state-runtime-group.ts',
|
|
141
|
+
'src/plugin/connection-state.ts',
|
|
111
142
|
'src/plugin/diagnostics-handlers.ts',
|
|
143
|
+
'src/plugin/error-message.ts',
|
|
112
144
|
'src/plugin/file-ack-runtime.ts',
|
|
113
145
|
'src/plugin/file-inbound-abort.ts',
|
|
114
146
|
'src/plugin/file-inbound-chunk.ts',
|
|
@@ -118,9 +150,8 @@ const requiredSourceFiles = [
|
|
|
118
150
|
'src/plugin/file-inbound-runtime.ts',
|
|
119
151
|
'src/plugin/file-inbound-state.ts',
|
|
120
152
|
'src/plugin/file-transfer-logs.ts',
|
|
121
|
-
'src/plugin/
|
|
153
|
+
'src/plugin/file-transfer-orchestrator-chunk.ts',
|
|
122
154
|
'src/plugin/file-transfer-orchestrator.ts',
|
|
123
|
-
'src/plugin/media-orchestrators-runtime-group.ts',
|
|
124
155
|
'src/plugin/file-transfer-runtime-group.ts',
|
|
125
156
|
'src/plugin/file-transfer-send.ts',
|
|
126
157
|
'src/plugin/file-transfer-setup.ts',
|
|
@@ -128,8 +159,10 @@ const requiredSourceFiles = [
|
|
|
128
159
|
'src/plugin/gateway-methods.ts',
|
|
129
160
|
'src/plugin/gateway-runtime.ts',
|
|
130
161
|
'src/plugin/inbound-acceptance.ts',
|
|
131
|
-
'src/plugin/inbound-surface-handlers-group.ts',
|
|
132
162
|
'src/plugin/inbound-handlers.ts',
|
|
163
|
+
'src/plugin/inbound-surface-handlers-group.ts',
|
|
164
|
+
'src/plugin/media-dedupe-runtime.ts',
|
|
165
|
+
'src/plugin/media-orchestrators-runtime-group.ts',
|
|
133
166
|
'src/plugin/message-ack-runtime.ts',
|
|
134
167
|
'src/plugin/message-policy.ts',
|
|
135
168
|
'src/plugin/message-send.ts',
|
|
@@ -147,15 +180,20 @@ const requiredSourceFiles = [
|
|
|
147
180
|
'src/plugin/outbox-push-route-runtime-group.ts',
|
|
148
181
|
'src/plugin/outbox-push.ts',
|
|
149
182
|
'src/plugin/outbox-route.ts',
|
|
183
|
+
'src/plugin/outbox-unified-push-flow.ts',
|
|
184
|
+
'src/plugin/runtime-diagnostics-assembler.ts',
|
|
185
|
+
'src/plugin/runtime-diagnostics-helpers.ts',
|
|
186
|
+
'src/plugin/runtime-diagnostics-payload-builders.ts',
|
|
150
187
|
'src/plugin/runtime-diagnostics-snapshot.ts',
|
|
188
|
+
'src/plugin/scene-registry.ts',
|
|
151
189
|
'src/plugin/setup.ts',
|
|
152
|
-
'src/plugin/state-transient-runtime-group.ts',
|
|
153
190
|
'src/plugin/state-store.ts',
|
|
191
|
+
'src/plugin/state-transient-runtime-group.ts',
|
|
192
|
+
'src/plugin/status-runtime.ts',
|
|
193
|
+
'src/plugin/status.ts',
|
|
154
194
|
'src/plugin/target-runtime.ts',
|
|
155
195
|
'src/plugin/target-status-runtime-group.ts',
|
|
156
196
|
'src/plugin/transient-state-runtime.ts',
|
|
157
|
-
'src/plugin/status-runtime.ts',
|
|
158
|
-
'src/plugin/status.ts',
|
|
159
197
|
'src/runtime/log-dedupe.ts',
|
|
160
198
|
'src/runtime/outbound-ack-timeout.ts',
|
|
161
199
|
'src/runtime/outbound-flags.ts',
|
package/src/channel.ts
CHANGED
|
@@ -1741,6 +1741,7 @@ class BncrBridgeRuntime {
|
|
|
1741
1741
|
handleFileTransferPushFailure: (args) => this.handleFileTransferPushFailure(args),
|
|
1742
1742
|
handleTextPushFailure: (args) => this.handleTextPushFailure(args),
|
|
1743
1743
|
isPrePushGuardDeferral: (entry) => this.isPrePushGuardDeferral(entry),
|
|
1744
|
+
resolveActiveAccountIds: () => this.activeConnectionByAccount.keys(),
|
|
1744
1745
|
resolveAccountIdForSession: (sessionKey) => {
|
|
1745
1746
|
const hit = this.sessionRoutes.get(sessionKey);
|
|
1746
1747
|
return hit ? hit.accountId : BNCR_DEFAULT_ACCOUNT_ID;
|
|
@@ -392,7 +392,11 @@ export async function prepareBncrInboundSessionContext(args: {
|
|
|
392
392
|
});
|
|
393
393
|
const body = formatOpenClawAgentEnvelope(api, {
|
|
394
394
|
channel: 'Bncr',
|
|
395
|
-
from:
|
|
395
|
+
from:
|
|
396
|
+
(parsed.userId
|
|
397
|
+
? `${parsed.userName} (${parsed.userId})`
|
|
398
|
+
: parsed.userName || `${platform}:${groupId}:${userId}`) +
|
|
399
|
+
(parsed.msgId ? ` #${parsed.msgId}` : ''),
|
|
396
400
|
timestamp: Date.now(),
|
|
397
401
|
previousTimestamp: readBncrSessionUpdatedAt(api, {
|
|
398
402
|
storePath,
|
|
@@ -233,9 +233,28 @@ async function probeMediaPath(raw: string): Promise<{
|
|
|
233
233
|
});
|
|
234
234
|
clearTimeout(timer);
|
|
235
235
|
if (response.ok || response.status === 206) return { ok: true };
|
|
236
|
-
|
|
236
|
+
// HEAD failed — try Range GET (first 4 bytes) as fallback.
|
|
237
|
+
// Some CDNs / object stores reject HEAD but serve GET with Range.
|
|
238
|
+
const probeReason = `HEAD ${response.status}`;
|
|
239
|
+
try {
|
|
240
|
+
const controller2 = new AbortController();
|
|
241
|
+
const timer2 = setTimeout(() => controller2.abort(), PROBE_TIMEOUT_MS);
|
|
242
|
+
const rangeResp = await fetch(path, {
|
|
243
|
+
method: 'GET',
|
|
244
|
+
headers: { Range: 'bytes=0-3' },
|
|
245
|
+
signal: controller2.signal,
|
|
246
|
+
redirect: 'follow',
|
|
247
|
+
});
|
|
248
|
+
clearTimeout(timer2);
|
|
249
|
+
if (rangeResp.status === 206 || rangeResp.ok) return { ok: true }; // 206=Range ok, other 2xx=server reachable
|
|
250
|
+
return { ok: false, reason: `${probeReason}, Range GET ${rangeResp.status}` };
|
|
251
|
+
} catch (err2: unknown) {
|
|
252
|
+
const reason2 = err2 instanceof Error ? err2.message : String(err2);
|
|
253
|
+
return { ok: false, reason: `${probeReason}, Range GET error: ${reason2}` };
|
|
254
|
+
}
|
|
237
255
|
} catch (err: unknown) {
|
|
238
256
|
const reason = err instanceof Error ? err.message : String(err);
|
|
257
|
+
// fetch-level error (DNS / timeout) — don't attempt fallback
|
|
239
258
|
return { ok: false, reason };
|
|
240
259
|
}
|
|
241
260
|
}
|
|
@@ -20,6 +20,7 @@ export function createBncrAckOutboxRuntimeGroup(runtime: {
|
|
|
20
20
|
clampFiniteNumber: (value: unknown, fallback: number, min?: number, max?: number) => number;
|
|
21
21
|
normalizeAccountId: (accountId: string) => string;
|
|
22
22
|
resolveAccountIdForSession: (sessionKey: string) => string | null;
|
|
23
|
+
resolveActiveAccountIds?: () => Iterable<string>;
|
|
23
24
|
formatDisplayScope: (route: OutboxEntry['route']) => string;
|
|
24
25
|
isFileTransferEntry: (entry: OutboxEntry) => boolean;
|
|
25
26
|
recommendedAckTimeoutMaxMs: number;
|
|
@@ -237,6 +238,7 @@ export function createBncrAckOutboxRuntimeGroup(runtime: {
|
|
|
237
238
|
handleTextPushFailure: runtime.handleTextPushFailure,
|
|
238
239
|
isPrePushGuardDeferral: runtime.isPrePushGuardDeferral,
|
|
239
240
|
resolveAccountIdForSession: runtime.resolveAccountIdForSession,
|
|
241
|
+
resolveActiveAccountIds: runtime.resolveActiveAccountIds,
|
|
240
242
|
moveToDeadLetter: runtime.moveToDeadLetter,
|
|
241
243
|
scheduleSave: runtime.scheduleSave,
|
|
242
244
|
logInfo: runtime.logInfo,
|
|
@@ -53,8 +53,21 @@ export function createBncrBridgeOutboxFacade(runtime: {
|
|
|
53
53
|
lastError: string;
|
|
54
54
|
persist?: boolean;
|
|
55
55
|
}) => {
|
|
56
|
+
const nowMs = runtime.now();
|
|
57
|
+
const nextRetry = (args.entry.retryCount || 0) + 1;
|
|
58
|
+
if (nextRetry > runtime.maxRetry) {
|
|
59
|
+
// Budget exhausted — dead-letter the entry
|
|
60
|
+
Object.assign(args.entry, {
|
|
61
|
+
lastError: args.lastError,
|
|
62
|
+
retryCount: nextRetry,
|
|
63
|
+
lastAttemptAt: nowMs,
|
|
64
|
+
});
|
|
65
|
+
moveToDeadLetter(args.entry, args.lastError);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const nextAttemptAt = nowMs + runtime.backoffMs(nextRetry);
|
|
56
69
|
const nextEntry = buildBncrOutboxFailureEntryPatch({
|
|
57
|
-
entry: args.entry,
|
|
70
|
+
entry: { ...args.entry, retryCount: nextRetry, lastAttemptAt: nowMs, nextAttemptAt },
|
|
58
71
|
lastError: args.lastError,
|
|
59
72
|
});
|
|
60
73
|
Object.assign(args.entry, nextEntry);
|
|
@@ -21,6 +21,8 @@ type BncrOutboxDrainFailureRuntime = {
|
|
|
21
21
|
backoffMs: (retryCount: number) => number;
|
|
22
22
|
outbox: Map<string, OutboxEntry>;
|
|
23
23
|
resolveAccountIdForSession: (sessionKey: string) => string | null;
|
|
24
|
+
resolveActiveAccountIds?: () => Iterable<string>;
|
|
25
|
+
resolvePushConnIds?: (accountId: string) => Iterable<string>;
|
|
24
26
|
logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
|
|
25
27
|
logWarn: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
|
|
26
28
|
isPrePushGuardDeferral: (entry: OutboxEntry) => boolean;
|
|
@@ -45,7 +47,7 @@ export function createBncrOutboxDrainFailure(runtime: BncrOutboxDrainFailureRunt
|
|
|
45
47
|
// If the entry keeps hitting pre-push guard (no active connection), the
|
|
46
48
|
// accountId on the entry might be wrong (e.g. constructed from route data).
|
|
47
49
|
// Try to correct it from recent inbound session context before giving up.
|
|
48
|
-
if (runtime.isPrePushGuardDeferral(entry) && entry.retryCount
|
|
50
|
+
if (runtime.isPrePushGuardDeferral(entry) && entry.retryCount >= 1) {
|
|
49
51
|
const corrected = runtime.resolveAccountIdForSession(entry.sessionKey);
|
|
50
52
|
if (corrected && corrected !== entry.accountId) {
|
|
51
53
|
const oldAccountId = entry.accountId;
|
|
@@ -75,7 +77,38 @@ export function createBncrOutboxDrainFailure(runtime: BncrOutboxDrainFailureRunt
|
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
// Round-robin fallback: try all active accounts when session healing fails
|
|
81
|
+
if (
|
|
82
|
+
runtime.isPrePushGuardDeferral(entry) &&
|
|
83
|
+
runtime.resolveActiveAccountIds &&
|
|
84
|
+
runtime.resolvePushConnIds
|
|
85
|
+
) {
|
|
86
|
+
const currentId = entry.accountId;
|
|
87
|
+
for (const candidateId of runtime.resolveActiveAccountIds()) {
|
|
88
|
+
if (candidateId === currentId) continue;
|
|
89
|
+
const connIds = Array.from(runtime.resolvePushConnIds(candidateId));
|
|
90
|
+
if (connIds.length > 0) {
|
|
91
|
+
const oldAccountId = entry.accountId;
|
|
92
|
+
runtime.logWarn(
|
|
93
|
+
'outbound',
|
|
94
|
+
`account round-robin corrected sessionKey=${entry.sessionKey} ${oldAccountId}→${candidateId}`,
|
|
95
|
+
);
|
|
96
|
+
entry.accountId = candidateId;
|
|
97
|
+
entry.retryCount = 0;
|
|
98
|
+
entry.lastError = undefined;
|
|
99
|
+
runtime.outbox.set(entry.messageId, entry);
|
|
100
|
+
runtime.scheduleSave();
|
|
101
|
+
return { action: 'continue', localNextDelay };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
78
106
|
if (runtime.isPrePushGuardDeferral(entry)) {
|
|
107
|
+
// Budget check: guard failures that exceed maxRetry go to dead-letter
|
|
108
|
+
if ((entry.retryCount || 0) >= runtime.maxRetry) {
|
|
109
|
+
runtime.moveToDeadLetter(entry, entry.lastError || 'guard-failure-limit');
|
|
110
|
+
return { action: 'continue', localNextDelay };
|
|
111
|
+
}
|
|
79
112
|
const wait = runtime.prePushGuardRetryDelayMs;
|
|
80
113
|
localNextDelay = runtime.outboxDrainSchedule.scheduleAccountWait({
|
|
81
114
|
accountId,
|
|
@@ -59,6 +59,7 @@ type BncrOutboxDrainRuntime = {
|
|
|
59
59
|
isPlainObject: (value: unknown) => value is Record<string, unknown>;
|
|
60
60
|
normalizeAccountId: (accountId: string) => string;
|
|
61
61
|
resolveAccountIdForSession: (sessionKey: string) => string | null;
|
|
62
|
+
resolveActiveAccountIds?: () => Iterable<string>;
|
|
62
63
|
stopped: () => boolean;
|
|
63
64
|
outbox: Map<string, OutboxEntry>;
|
|
64
65
|
deadLetter: () => OutboxEntry[];
|
|
@@ -137,6 +138,8 @@ export function createBncrOutboxDrainRuntime(runtime: BncrOutboxDrainRuntime) {
|
|
|
137
138
|
backoffMs: runtime.backoffMs,
|
|
138
139
|
outbox: runtime.outbox,
|
|
139
140
|
resolveAccountIdForSession: runtime.resolveAccountIdForSession,
|
|
141
|
+
resolveActiveAccountIds: runtime.resolveActiveAccountIds,
|
|
142
|
+
resolvePushConnIds: runtime.resolvePushConnIds,
|
|
140
143
|
logInfo: runtime.logInfo,
|
|
141
144
|
logWarn: runtime.logWarn,
|
|
142
145
|
isPrePushGuardDeferral: runtime.isPrePushGuardDeferral,
|