@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.5.8",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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-constants.ts',
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/media-dedupe-runtime.ts',
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: `${platform}:${groupId}:${userId}`,
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
- return { ok: false, reason: `HTTP ${response.status} ${response.statusText}` };
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 > 0) {
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,