@xmoxmo/bncr 0.3.5 → 0.3.7

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.
Files changed (164) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +28 -5
  3. package/index.ts +55 -721
  4. package/package.json +8 -4
  5. package/scripts/check-pack.mjs +93 -18
  6. package/scripts/check-register-drift.mjs +35 -13
  7. package/scripts/selfcheck.mjs +80 -11
  8. package/src/bootstrap/channel-plugin-runtime.ts +81 -0
  9. package/src/bootstrap/cli.ts +97 -0
  10. package/src/bootstrap/register-runtime-gateway.ts +129 -0
  11. package/src/bootstrap/register-runtime-helpers.ts +140 -0
  12. package/src/bootstrap/register-runtime-singleton.ts +137 -0
  13. package/src/bootstrap/register-runtime.ts +201 -0
  14. package/src/bootstrap/runtime-discovery.ts +187 -0
  15. package/src/bootstrap/runtime-loader.ts +54 -0
  16. package/src/channel.ts +1590 -4967
  17. package/src/core/accounts.ts +23 -4
  18. package/src/core/dead-letter-diagnostics.ts +37 -5
  19. package/src/core/diagnostics.ts +31 -15
  20. package/src/core/downlink-health.ts +3 -11
  21. package/src/core/extended-diagnostics.ts +78 -36
  22. package/src/core/file-transfer-payloads.ts +1 -1
  23. package/src/core/logging.ts +1 -0
  24. package/src/core/outbox-enqueue.ts +13 -2
  25. package/src/core/outbox-entry-builders.ts +2 -0
  26. package/src/core/outbox-summary.ts +75 -3
  27. package/src/core/permissions.ts +15 -2
  28. package/src/core/persisted-outbox-entry.ts +21 -6
  29. package/src/core/policy.ts +45 -4
  30. package/src/core/probe.ts +3 -15
  31. package/src/core/register-trace.ts +3 -3
  32. package/src/core/status.ts +43 -4
  33. package/src/core/targets.ts +216 -205
  34. package/src/core/types.ts +221 -0
  35. package/src/core/value-sanitize.ts +29 -0
  36. package/src/messaging/inbound/commands.ts +147 -172
  37. package/src/messaging/inbound/context-facts.ts +4 -2
  38. package/src/messaging/inbound/contracts.ts +70 -0
  39. package/src/messaging/inbound/dispatch-prep.ts +303 -0
  40. package/src/messaging/inbound/dispatch.ts +49 -462
  41. package/src/messaging/inbound/gate.ts +18 -5
  42. package/src/messaging/inbound/last-route.ts +10 -4
  43. package/src/messaging/inbound/media-url-download.ts +109 -0
  44. package/src/messaging/inbound/native-command-runtime.ts +225 -0
  45. package/src/messaging/inbound/parse.ts +2 -1
  46. package/src/messaging/inbound/remote-media.ts +49 -0
  47. package/src/messaging/inbound/reply-config.ts +16 -4
  48. package/src/messaging/inbound/reply-dispatch.ts +162 -0
  49. package/src/messaging/inbound/runtime-compat.ts +31 -10
  50. package/src/messaging/inbound/session-label.ts +15 -7
  51. package/src/messaging/inbound/turn-context.ts +131 -0
  52. package/src/messaging/outbound/actions.ts +24 -10
  53. package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
  54. package/src/messaging/outbound/diagnostics.ts +31 -355
  55. package/src/messaging/outbound/durable-message-adapter.ts +20 -16
  56. package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
  57. package/src/messaging/outbound/media.ts +24 -13
  58. package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
  59. package/src/messaging/outbound/reply-enqueue.ts +57 -134
  60. package/src/messaging/outbound/send-params.ts +3 -0
  61. package/src/messaging/outbound/send.ts +19 -10
  62. package/src/messaging/outbound/session-route.ts +18 -3
  63. package/src/openclaw/channel-runtime-contracts.ts +76 -0
  64. package/src/openclaw/config-runtime.ts +13 -7
  65. package/src/openclaw/inbound-session-runtime.ts +7 -3
  66. package/src/openclaw/ingress-runtime.ts +17 -27
  67. package/src/openclaw/reply-runtime.ts +54 -59
  68. package/src/openclaw/routing-runtime.ts +35 -18
  69. package/src/openclaw/runtime-surface.ts +156 -12
  70. package/src/openclaw/sdk-helpers.ts +8 -1
  71. package/src/openclaw/session-route-runtime.ts +12 -12
  72. package/src/plugin/ack-outbox-runtime-group.ts +264 -0
  73. package/src/plugin/bridge-ack-facade.ts +137 -0
  74. package/src/plugin/bridge-connection-facade.ts +111 -0
  75. package/src/plugin/bridge-diagnostics-facade.ts +23 -0
  76. package/src/plugin/bridge-drain-facade.ts +98 -0
  77. package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
  78. package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
  79. package/src/plugin/bridge-lifecycle.ts +156 -0
  80. package/src/plugin/bridge-media-facade.ts +241 -0
  81. package/src/plugin/bridge-outbox-facade.ts +182 -0
  82. package/src/plugin/bridge-runtime-helpers.ts +266 -0
  83. package/src/plugin/bridge-runtime-snapshots.ts +104 -0
  84. package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
  85. package/src/plugin/bridge-status-facade.ts +76 -0
  86. package/src/plugin/bridge-status-worker-facade.ts +72 -0
  87. package/src/plugin/bridge-support-runtime.ts +137 -0
  88. package/src/plugin/bridge-surface-handlers-group.ts +242 -0
  89. package/src/plugin/bridge-surface-helpers.ts +28 -0
  90. package/src/plugin/capabilities.ts +1 -3
  91. package/src/plugin/channel-components.ts +289 -0
  92. package/src/plugin/channel-inbound-helpers.ts +149 -0
  93. package/src/plugin/channel-plugin-bridge-group.ts +129 -0
  94. package/src/plugin/channel-plugin-surface-group.ts +202 -0
  95. package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
  96. package/src/plugin/channel-runtime-builders-status.ts +331 -0
  97. package/src/plugin/channel-runtime-builders.ts +25 -0
  98. package/src/plugin/channel-runtime-constants.ts +40 -0
  99. package/src/plugin/channel-runtime-types.ts +146 -0
  100. package/src/plugin/channel-send-runtime-group.ts +37 -0
  101. package/src/plugin/channel-send.ts +226 -0
  102. package/src/plugin/channel-utils.ts +102 -0
  103. package/src/plugin/config.ts +24 -3
  104. package/src/plugin/connection-handlers-helpers.ts +254 -0
  105. package/src/plugin/connection-handlers.ts +440 -0
  106. package/src/plugin/connection-state-helpers.ts +159 -0
  107. package/src/plugin/connection-state-runtime-group.ts +51 -0
  108. package/src/plugin/connection-state.ts +527 -0
  109. package/src/plugin/diagnostics-handlers.ts +211 -0
  110. package/src/plugin/error-message.ts +15 -0
  111. package/src/plugin/file-ack-runtime.ts +284 -0
  112. package/src/plugin/file-inbound-abort.ts +112 -0
  113. package/src/plugin/file-inbound-chunk.ts +146 -0
  114. package/src/plugin/file-inbound-complete.ts +153 -0
  115. package/src/plugin/file-inbound-handlers.ts +19 -0
  116. package/src/plugin/file-inbound-init.ts +122 -0
  117. package/src/plugin/file-inbound-runtime.ts +51 -0
  118. package/src/plugin/file-inbound-state.ts +62 -0
  119. package/src/plugin/file-transfer-logs.ts +227 -0
  120. package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
  121. package/src/plugin/file-transfer-orchestrator.ts +304 -0
  122. package/src/plugin/file-transfer-runtime-group.ts +102 -0
  123. package/src/plugin/file-transfer-send.ts +89 -0
  124. package/src/plugin/file-transfer-setup.ts +206 -0
  125. package/src/plugin/gateway-event-context.ts +41 -0
  126. package/src/plugin/gateway-runtime.ts +14 -4
  127. package/src/plugin/inbound-acceptance.ts +107 -0
  128. package/src/plugin/inbound-handlers.ts +248 -0
  129. package/src/plugin/inbound-surface-handlers-group.ts +152 -0
  130. package/src/plugin/media-dedupe-runtime.ts +90 -0
  131. package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
  132. package/src/plugin/message-ack-runtime.ts +284 -0
  133. package/src/plugin/message-send.ts +16 -6
  134. package/src/plugin/messaging.ts +98 -36
  135. package/src/plugin/outbound.ts +50 -8
  136. package/src/plugin/outbox-ack-logs.ts +136 -0
  137. package/src/plugin/outbox-ack-outcome.ts +128 -0
  138. package/src/plugin/outbox-drain-ack.ts +145 -0
  139. package/src/plugin/outbox-drain-failure.ts +84 -0
  140. package/src/plugin/outbox-drain-loop.ts +554 -0
  141. package/src/plugin/outbox-drain-post-push.ts +159 -0
  142. package/src/plugin/outbox-drain-runtime.ts +141 -0
  143. package/src/plugin/outbox-drain-schedule.ts +116 -0
  144. package/src/plugin/outbox-file-push-flow.ts +69 -0
  145. package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
  146. package/src/plugin/outbox-push.ts +267 -0
  147. package/src/plugin/outbox-route.ts +181 -0
  148. package/src/plugin/outbox-text-push-flow.ts +90 -0
  149. package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
  150. package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
  151. package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
  152. package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
  153. package/src/plugin/setup.ts +33 -6
  154. package/src/plugin/state-store.ts +249 -0
  155. package/src/plugin/state-transient-runtime-group.ts +105 -0
  156. package/src/plugin/status-runtime.ts +251 -0
  157. package/src/plugin/status.ts +33 -7
  158. package/src/plugin/target-runtime.ts +141 -0
  159. package/src/plugin/target-status-runtime-group.ts +130 -0
  160. package/src/plugin/transient-state-runtime.ts +82 -0
  161. package/src/runtime/outbound-ack-timeout.ts +5 -3
  162. package/src/runtime/outbound-flags.ts +24 -8
  163. package/src/runtime/status-snapshots.ts +36 -7
  164. package/src/runtime/status-worker.ts +34 -4
@@ -1,3 +1,9 @@
1
+ import type {
2
+ BncrAccountConfig,
3
+ BncrChannelConfigRoot,
4
+ BncrChannelConfigSection,
5
+ } from '../plugin/channel-runtime-types.ts';
6
+
1
7
  const CHANNEL_ID = 'bncr';
2
8
  const BNCR_DEFAULT_ACCOUNT_ID = 'Primary';
3
9
 
@@ -29,8 +35,21 @@ export function resolveDefaultDisplayName(rawName: unknown, accountId: string):
29
35
  return raw;
30
36
  }
31
37
 
32
- export function resolveAccount(cfg: any, accountId?: string | null) {
33
- const accounts = cfg?.channels?.[CHANNEL_ID]?.accounts || {};
38
+ function getChannelConfig(cfg: BncrChannelConfigRoot | null | undefined): BncrChannelConfigSection {
39
+ return cfg?.channels?.[CHANNEL_ID] || {};
40
+ }
41
+
42
+ function getAccountsConfig(
43
+ cfg: BncrChannelConfigRoot | null | undefined,
44
+ ): Record<string, BncrAccountConfig | undefined> {
45
+ return getChannelConfig(cfg).accounts || {};
46
+ }
47
+
48
+ export function resolveAccount(
49
+ cfg: BncrChannelConfigRoot | null | undefined,
50
+ accountId?: string | null,
51
+ ) {
52
+ const accounts = getAccountsConfig(cfg);
34
53
  let key = normalizeAccountId(accountId);
35
54
 
36
55
  if (!accounts[key]) {
@@ -48,8 +67,8 @@ export function resolveAccount(cfg: any, accountId?: string | null) {
48
67
  };
49
68
  }
50
69
 
51
- export function listAccountIds(cfg: any): string[] {
52
- const ids = Object.keys(cfg?.channels?.[CHANNEL_ID]?.accounts || {});
70
+ export function listAccountIds(cfg: BncrChannelConfigRoot | null | undefined): string[] {
71
+ const ids = Object.keys(getAccountsConfig(cfg));
53
72
  return ids.length ? ids : [BNCR_DEFAULT_ACCOUNT_ID];
54
73
  }
55
74
 
@@ -1,6 +1,10 @@
1
1
  import { summarizeBncrTextPreview } from './logging.ts';
2
2
  import { formatDisplayScope } from './targets.ts';
3
- import type { OutboxEntry } from './types.ts';
3
+ import type {
4
+ BncrDeadLetterDiagnosticsSummary,
5
+ BncrDeadLetterEntrySummary,
6
+ OutboxEntry,
7
+ } from './types.ts';
4
8
 
5
9
  export type DeadLetterTopReason = { reason: string; count: number };
6
10
 
@@ -17,6 +21,15 @@ function asString(value: unknown, fallback = ''): string {
17
21
  return String(value);
18
22
  }
19
23
 
24
+ function asPayloadMessage(payload: OutboxEntry['payload']): {
25
+ msg?: string;
26
+ type?: string;
27
+ [key: string]: unknown;
28
+ } {
29
+ const raw = payload.message;
30
+ return raw && typeof raw === 'object' ? (raw as { msg?: string; type?: string }) : {};
31
+ }
32
+
20
33
  export function buildDeadLetterDiagnostics(options: BuildDeadLetterDiagnosticsOptions) {
21
34
  const reasonCounts = new Map<string, number>();
22
35
  let oldestAt: number | null = null;
@@ -43,7 +56,7 @@ export function buildDeadLetterDiagnostics(options: BuildDeadLetterDiagnosticsOp
43
56
  .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
44
57
  .slice(0, 5)
45
58
  .map(([reason, count]) => ({ reason, count })),
46
- };
59
+ } satisfies BncrDeadLetterDiagnosticsSummary;
47
60
  }
48
61
 
49
62
  export function formatDeadLetterTopReasons(topReasons: DeadLetterTopReason[]): string {
@@ -75,17 +88,36 @@ export function parseDeadLetterOlderThan(raw: unknown): number | null {
75
88
 
76
89
  export function summarizeDeadLetterEntry(entry: OutboxEntry) {
77
90
  const meta = entry.payload?._meta || {};
78
- const msg = (entry.payload as any)?.message || {};
91
+ const msg = asPayloadMessage(entry.payload);
79
92
  const text = asString(meta.text || msg.msg || '');
80
93
  return {
81
94
  messageId: entry.messageId,
82
95
  accountId: entry.accountId,
83
96
  sessionKey: entry.sessionKey,
84
97
  route: formatDisplayScope(entry.route),
85
- kind: asString(meta.kind || (entry.payload as any)?.type || 'message'),
98
+ kind: asString(meta.kind || msg.type || 'message'),
86
99
  createdAt: Number.isFinite(Number(entry.createdAt)) ? Number(entry.createdAt) : null,
87
100
  retryCount: Number.isFinite(Number(entry.retryCount)) ? Number(entry.retryCount) : 0,
88
101
  lastError: entry.lastError || null,
89
102
  textPreview: summarizeBncrTextPreview(text, 24),
90
- };
103
+ } satisfies BncrDeadLetterEntrySummary;
104
+ }
105
+
106
+ export function filterDeadLetterEntries(args: {
107
+ accountId: string;
108
+ entries: OutboxEntry[];
109
+ reason?: string | null;
110
+ olderThan?: number | null;
111
+ }) {
112
+ const normalizedAccountId = asString(args.accountId).trim().toLowerCase();
113
+ const reason = asString(args.reason || '').trim();
114
+ return args.entries.filter((entry) => {
115
+ if (asString(entry.accountId).trim().toLowerCase() !== normalizedAccountId) return false;
116
+ if (reason && entry.lastError !== reason) return false;
117
+ if (typeof args.olderThan === 'number') {
118
+ const createdAt = Number(entry.createdAt);
119
+ if (!Number.isFinite(createdAt) || createdAt >= args.olderThan) return false;
120
+ }
121
+ return true;
122
+ });
91
123
  }
@@ -1,31 +1,47 @@
1
+ import type { BncrRuntimeFlags } from '../runtime/outbound-flags.ts';
2
+ import type { BncrExtendedDiagnostics } from './extended-diagnostics.ts';
3
+ import type { BncrPermissionSummary } from './permissions.ts';
1
4
  import { buildBncrPermissionSummary } from './permissions.ts';
5
+ import type { BncrAccountProbe } from './probe.ts';
2
6
  import { probeBncrAccount } from './probe.ts';
7
+ import type { BncrAccountRuntimeSnapshot } from './status.ts';
8
+ import type { BncrDownlinkHealthSummary } from './types.ts';
9
+ import { nonNegativeFiniteNumberOr } from './value-sanitize.ts';
3
10
 
4
- function finiteNumberOr(value: unknown, fallback: number): number {
5
- const n = Number(value);
6
- return Number.isFinite(n) ? n : fallback;
7
- }
8
-
9
- function nonNegativeFiniteNumberOr(value: unknown, fallback: number): number {
10
- return Math.max(0, finiteNumberOr(value, fallback));
11
- }
11
+ export type BncrDiagnosticsWaiters = {
12
+ messageAck: number;
13
+ fileAck: number;
14
+ };
12
15
 
13
16
  type DiagnosticsPayloadArgs = {
14
- cfg: any;
17
+ cfg: unknown;
15
18
  channelId: string;
16
19
  accountId: string;
17
- runtime: any;
18
- diagnostics: any;
19
- downlinkHealth: any;
20
- runtimeFlags: any;
21
- waiters: { messageAck: number; fileAck: number };
20
+ runtime: BncrAccountRuntimeSnapshot;
21
+ diagnostics: BncrExtendedDiagnostics;
22
+ downlinkHealth: BncrDownlinkHealthSummary;
23
+ runtimeFlags: BncrRuntimeFlags;
24
+ waiters: BncrDiagnosticsWaiters;
22
25
  activeConnections: number;
23
26
  invalidOutboxSessionKeys: number;
24
27
  legacyAccountResidue: number;
25
28
  now: number;
26
29
  };
27
30
 
28
- export function buildDiagnosticsPayload(args: DiagnosticsPayloadArgs) {
31
+ export type BncrDiagnosticsPayload = {
32
+ channel: string;
33
+ accountId: string;
34
+ runtime: BncrAccountRuntimeSnapshot;
35
+ diagnostics: BncrExtendedDiagnostics;
36
+ downlinkHealth: BncrDownlinkHealthSummary;
37
+ runtimeFlags: BncrRuntimeFlags;
38
+ waiters: BncrDiagnosticsWaiters;
39
+ permissions: BncrPermissionSummary;
40
+ probe: BncrAccountProbe;
41
+ now: number;
42
+ };
43
+
44
+ export function buildDiagnosticsPayload(args: DiagnosticsPayloadArgs): BncrDiagnosticsPayload {
29
45
  const permissions = buildBncrPermissionSummary(args.cfg ?? {});
30
46
  const probe = probeBncrAccount({
31
47
  accountId: args.accountId,
@@ -1,13 +1,5 @@
1
- import type { OutboxEntry } from './types.ts';
2
-
3
- function finiteNumberOr(value: unknown, fallback: number): number {
4
- const n = Number(value);
5
- return Number.isFinite(n) ? n : fallback;
6
- }
7
-
8
- function nonNegativeFiniteNumberOr(value: unknown, fallback: number): number {
9
- return Math.max(0, finiteNumberOr(value, fallback));
10
- }
1
+ import type { BncrDownlinkHealthSummary, OutboxEntry } from './types.ts';
2
+ import { finiteNumberOr, nonNegativeFiniteNumberOr } from './value-sanitize.ts';
11
3
 
12
4
  type DownlinkHealthInput = {
13
5
  accountId: string;
@@ -62,5 +54,5 @@ export function buildDownlinkHealth(input: DownlinkHealthInput) {
62
54
  recommendReason: ackStalled
63
55
  ? 'single-conn pending outbox with recent ack timeout and no recent ack-ok while inbound/activity is still alive'
64
56
  : '',
65
- };
57
+ } satisfies BncrDownlinkHealthSummary;
66
58
  }
@@ -1,11 +1,46 @@
1
- import type { RegisterTraceEntry } from './register-trace.ts';
1
+ import type { OpenClawChannelRuntimeSurfaceDiagnostics } from '../openclaw/runtime-surface.ts';
2
+ import type {
3
+ RegisterDriftSnapshot,
4
+ RegisterTraceEntry,
5
+ RegisterTraceSummary,
6
+ } from './register-trace.ts';
7
+ import type {
8
+ BncrDeadLetterDiagnosticsSummary,
9
+ BncrDiagnosticsSummary,
10
+ BncrExtendedOutboundDiagnostics,
11
+ BncrStaleCounterSummary,
12
+ } from './types.ts';
2
13
 
3
- type ExtendedDiagnosticsInput = {
4
- diagnostics: Record<string, any>;
5
- runtimeSurface?: {
6
- channel: Record<string, boolean>;
7
- missing: string[];
8
- };
14
+ type ExtendedConnectionDiagnostics = {
15
+ active: number;
16
+ hasGatewayContext?: boolean;
17
+ lastGatewayContextAt?: number | null;
18
+ primaryLeaseId: string | null;
19
+ primaryEpoch: number | null;
20
+ acceptedConnections: number;
21
+ lastConnectAt: number | null;
22
+ lastDisconnectAt: number | null;
23
+ lastActivityAt: number | null;
24
+ lastInboundAt: number | null;
25
+ lastAckAt: number | null;
26
+ recent: Array<{
27
+ leaseId: string;
28
+ epoch: number;
29
+ connectedAt: number;
30
+ lastActivityAt: number | null;
31
+ isPrimary: boolean;
32
+ }>;
33
+ };
34
+
35
+ type ExtendedProtocolDiagnostics = {
36
+ bridgeVersion: number;
37
+ protocolVersion: number;
38
+ minClientProtocol: number;
39
+ features: Record<string, boolean>;
40
+ };
41
+
42
+ export type BncrExtendedDiagnostics = BncrDiagnosticsSummary & {
43
+ runtimeSurface?: OpenClawChannelRuntimeSurfaceDiagnostics;
9
44
  register: {
10
45
  bridgeId: string;
11
46
  gatewayPid: number;
@@ -19,44 +54,51 @@ type ExtendedDiagnosticsInput = {
19
54
  lastApiRebindAt: number | null;
20
55
  apiGeneration: number;
21
56
  traceRecent: RegisterTraceEntry[];
22
- traceSummary: Record<string, any>;
23
- lastDriftSnapshot: any;
57
+ traceSummary: RegisterTraceSummary;
58
+ lastDriftSnapshot: RegisterDriftSnapshot | null;
24
59
  };
25
- outbound?: Record<string, any>;
26
- deadLetterSummary?: Record<string, any>;
27
- connection: {
28
- active: number;
29
- primaryLeaseId: string | null;
30
- primaryEpoch: number | null;
31
- acceptedConnections: number;
32
- lastConnectAt: number | null;
33
- lastDisconnectAt: number | null;
34
- lastActivityAt: number | null;
35
- lastInboundAt: number | null;
36
- lastAckAt: number | null;
37
- recent: Array<{
38
- leaseId: string;
39
- epoch: number;
40
- connectedAt: number;
41
- lastActivityAt: number | null;
42
- isPrimary: boolean;
43
- }>;
44
- };
45
- protocol: {
46
- bridgeVersion: number;
47
- protocolVersion: number;
48
- minClientProtocol: number;
49
- features: Record<string, boolean>;
60
+ connection: ExtendedConnectionDiagnostics;
61
+ outbound?: BncrExtendedOutboundDiagnostics;
62
+ deadLetterSummary?: BncrDeadLetterDiagnosticsSummary;
63
+ protocol: ExtendedProtocolDiagnostics;
64
+ stale: BncrStaleCounterSummary;
65
+ };
66
+
67
+ type ExtendedDiagnosticsInput = {
68
+ diagnostics: BncrDiagnosticsSummary;
69
+ runtimeSurface?: OpenClawChannelRuntimeSurfaceDiagnostics;
70
+ register: {
71
+ bridgeId: string;
72
+ gatewayPid: number;
73
+ pluginVersion: string | null;
74
+ source: string | null;
75
+ apiInstanceId: string | null;
76
+ registryFingerprint: string | null;
77
+ registerCount: number;
78
+ firstRegisterAt: number | null;
79
+ lastRegisterAt: number | null;
80
+ lastApiRebindAt: number | null;
81
+ apiGeneration: number;
82
+ traceRecent: RegisterTraceEntry[];
83
+ traceSummary: RegisterTraceSummary;
84
+ lastDriftSnapshot: RegisterDriftSnapshot | null;
50
85
  };
51
- stale: Record<string, any>;
86
+ outbound?: BncrExtendedOutboundDiagnostics;
87
+ deadLetterSummary?: BncrDeadLetterDiagnosticsSummary;
88
+ connection: ExtendedConnectionDiagnostics;
89
+ protocol: ExtendedProtocolDiagnostics;
90
+ stale: BncrStaleCounterSummary;
52
91
  };
53
92
 
54
- export function buildExtendedDiagnostics(input: ExtendedDiagnosticsInput) {
93
+ export function buildExtendedDiagnostics(input: ExtendedDiagnosticsInput): BncrExtendedDiagnostics {
55
94
  return {
56
95
  ...input.diagnostics,
57
96
  runtimeSurface: input.runtimeSurface
58
97
  ? {
98
+ runtime: { ...input.runtimeSurface.runtime },
59
99
  channel: { ...input.runtimeSurface.channel },
100
+ channelMedia: { ...input.runtimeSurface.channelMedia },
101
+ contract: { ...input.runtimeSurface.contract },
60
102
  missing: input.runtimeSurface.missing.slice(),
61
103
  }
62
104
  : undefined,
@@ -1,4 +1,4 @@
1
- import type { BncrRoute } from './accounts.ts';
1
+ import type { BncrRoute } from './types.ts';
2
2
 
3
3
  export function buildFileTransferInitPayload(args: {
4
4
  transferId: string;
@@ -15,6 +15,7 @@ const TEXT_PAYLOAD_KEYS = new Set([
15
15
  'msg',
16
16
  'rawText',
17
17
  'text',
18
+ 'textPreview',
18
19
  ]);
19
20
  const MEDIA_PAYLOAD_KEYS = new Set(['mediaUrl', 'path']);
20
21
  const MEDIA_LIST_PAYLOAD_KEYS = new Set(['mediaUrls', 'mediaList']);
@@ -1,13 +1,24 @@
1
1
  import type { OutboxEntry } from './types.ts';
2
2
 
3
+ type OutboxEnqueueMessage = {
4
+ type?: unknown;
5
+ msg?: unknown;
6
+ };
7
+
8
+ type OutboxEnqueuePayload = {
9
+ type?: unknown;
10
+ message?: OutboxEnqueueMessage;
11
+ };
12
+
3
13
  export function buildOutboxEnqueueDebugInfo(args: {
4
14
  bridgeId: string;
5
15
  entry: OutboxEntry;
6
16
  asString: (value: unknown) => string;
7
17
  formatDisplayScope: (route: OutboxEntry['route']) => string;
8
18
  }) {
9
- const msg = (args.entry.payload as any)?.message || {};
10
- const type = args.asString(msg.type || (args.entry.payload as any)?.type || 'unknown');
19
+ const payload = args.entry.payload as OutboxEnqueuePayload;
20
+ const msg = payload?.message || {};
21
+ const type = args.asString(msg.type || payload?.type || 'unknown');
11
22
  const text = args.asString(msg.msg || '');
12
23
  return {
13
24
  bridge: args.bridgeId,
@@ -15,6 +15,7 @@ export function buildFileTransferOutboxEntry(args: {
15
15
  text: string;
16
16
  asVoice?: boolean;
17
17
  audioAsVoice?: boolean;
18
+ type?: string;
18
19
  kind?: 'tool' | 'block' | 'final';
19
20
  replyToId?: string;
20
21
  replyTargetPolicy?: OutboundReplyTargetPolicy;
@@ -36,6 +37,7 @@ export function buildFileTransferOutboxEntry(args: {
36
37
  text: args.text,
37
38
  asVoice: args.asVoice === true,
38
39
  audioAsVoice: args.audioAsVoice === true,
40
+ type: args.type,
39
41
  finalEvent: args.pushEvent,
40
42
  replyToId:
41
43
  normalizeOutboundReplyToId({
@@ -1,14 +1,86 @@
1
1
  import type { OutboxEntry } from './types.ts';
2
2
 
3
+ type OutboxSummaryMeta = {
4
+ kind?: string;
5
+ asVoice?: boolean;
6
+ audioAsVoice?: boolean;
7
+ type?: unknown;
8
+ mediaUrl?: unknown;
9
+ text?: unknown;
10
+ };
11
+
12
+ type OutboxSummaryMessage = {
13
+ type?: unknown;
14
+ msg?: unknown;
15
+ };
16
+
17
+ type OutboxSummaryPayload = {
18
+ type?: unknown;
19
+ message?: OutboxSummaryMessage;
20
+ _meta?: OutboxSummaryMeta;
21
+ };
22
+
23
+ function inferMediaTypeFromUrl(raw: string): 'image' | 'video' | 'audio' | 'file' {
24
+ const clean = String(raw || '').split(/[?#]/, 1)[0] || '';
25
+ const ext = clean.includes('.') ? clean.slice(clean.lastIndexOf('.') + 1).toLowerCase() : '';
26
+ if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'heic', 'heif'].includes(ext)) return 'image';
27
+ if (['mp4', 'mov', 'mkv', 'webm', 'avi', 'm4v'].includes(ext)) return 'video';
28
+ if (['mp3', 'wav', 'ogg', 'oga', 'opus', 'm4a', 'aac', 'flac'].includes(ext)) return 'audio';
29
+ return 'file';
30
+ }
31
+
32
+ function filenameFromUrl(raw: string): string {
33
+ const clean = String(raw || '').split(/[?#]/, 1)[0] || '';
34
+ const name = clean.split(/[\\/]/).filter(Boolean).pop() || '';
35
+ if (!name) return '';
36
+ try {
37
+ return decodeURIComponent(name);
38
+ } catch {
39
+ return name;
40
+ }
41
+ }
42
+
43
+ function summarizeOutboxType(
44
+ payload: OutboxSummaryPayload,
45
+ msg: OutboxSummaryMessage,
46
+ asString: (value: unknown) => string,
47
+ ) {
48
+ const directType = asString(msg.type || '');
49
+ if (directType) return directType;
50
+
51
+ const meta = payload?._meta || {};
52
+ if (meta.kind === 'file-transfer') {
53
+ if (meta.asVoice === true || meta.audioAsVoice === true) return 'voice';
54
+ const hintedType = asString(meta.type || '').trim();
55
+ if (hintedType) return hintedType;
56
+ return inferMediaTypeFromUrl(asString(meta.mediaUrl || ''));
57
+ }
58
+
59
+ return asString(payload?.type || 'unknown');
60
+ }
61
+
62
+ function summarizeOutboxText(
63
+ payload: OutboxSummaryPayload,
64
+ msg: OutboxSummaryMessage,
65
+ asString: (value: unknown) => string,
66
+ ) {
67
+ const text = asString(msg.msg || payload?._meta?.text || '').trim();
68
+ if (text) return text;
69
+ const meta = payload?._meta || {};
70
+ if (meta.kind === 'file-transfer') return filenameFromUrl(asString(meta.mediaUrl || ''));
71
+ return '';
72
+ }
73
+
3
74
  export function summarizeOutboxEntry(args: {
4
75
  entry: OutboxEntry;
5
76
  asString: (value: unknown) => string;
6
77
  formatDisplayScope: (route: OutboxEntry['route']) => string;
7
78
  summarizeTextPreview: (raw: string, limit?: number) => string;
8
79
  }) {
9
- const msg = (args.entry.payload as any)?.message || {};
10
- const type = args.asString(msg.type || (args.entry.payload as any)?.type || 'unknown');
11
- const text = args.asString(msg.msg || '');
80
+ const payload = args.entry.payload as OutboxSummaryPayload;
81
+ const msg = payload?.message || {};
82
+ const type = summarizeOutboxType(payload, msg, args.asString);
83
+ const text = summarizeOutboxText(payload, msg, args.asString);
12
84
  const preview = args.summarizeTextPreview(text);
13
85
  return [type, args.formatDisplayScope(args.entry.route), preview].join('|');
14
86
  }
@@ -4,7 +4,18 @@ function asString(v: unknown, fallback = ''): string {
4
4
  return String(v);
5
5
  }
6
6
 
7
- export function getBncrElevatedConfig(rootCfg: any) {
7
+ type BncrElevatedConfigRoot = {
8
+ tools?: {
9
+ elevated?: {
10
+ enabled?: boolean;
11
+ allowFrom?: {
12
+ bncr?: unknown;
13
+ };
14
+ };
15
+ };
16
+ };
17
+
18
+ export function getBncrElevatedConfig(rootCfg: BncrElevatedConfigRoot | null | undefined) {
8
19
  const elevated = rootCfg?.tools?.elevated || {};
9
20
  const allowFrom = elevated?.allowFrom || {};
10
21
  const bncrRules = Array.isArray(allowFrom?.bncr)
@@ -18,7 +29,7 @@ export function getBncrElevatedConfig(rootCfg: any) {
18
29
  };
19
30
  }
20
31
 
21
- export function buildBncrPermissionSummary(rootCfg: any) {
32
+ export function buildBncrPermissionSummary(rootCfg: BncrElevatedConfigRoot | null | undefined) {
22
33
  const elevated = getBncrElevatedConfig(rootCfg);
23
34
  return {
24
35
  elevatedEnabled: elevated.enabled,
@@ -29,3 +40,5 @@ export function buildBncrPermissionSummary(rootCfg: any) {
29
40
  : 'bncr elevated not explicitly allowed',
30
41
  };
31
42
  }
43
+
44
+ export type BncrPermissionSummary = ReturnType<typeof buildBncrPermissionSummary>;
@@ -2,6 +2,10 @@ import { normalizeAccountId } from './accounts.ts';
2
2
  import { normalizeStoredSessionKey, parseRouteLike } from './targets.ts';
3
3
  import type { OutboxEntry } from './types.ts';
4
4
 
5
+ type PersistedOutboxEntryInput = Partial<OutboxEntry> & {
6
+ payload?: Record<string, unknown>;
7
+ };
8
+
5
9
  function asString(v: unknown, fallback = ''): string {
6
10
  if (typeof v === 'string') return v;
7
11
  if (v == null) return fallback;
@@ -20,7 +24,7 @@ function optionalFiniteNumber(value: unknown): number | undefined {
20
24
  }
21
25
 
22
26
  export function normalizePersistedOutboxEntry(args: {
23
- entry: any;
27
+ entry: PersistedOutboxEntryInput | null | undefined;
24
28
  canonicalAgentId: string;
25
29
  now: () => number;
26
30
  }): OutboxEntry | null {
@@ -32,14 +36,16 @@ export function normalizePersistedOutboxEntry(args: {
32
36
  if (!normalized) return null;
33
37
 
34
38
  const route = parseRouteLike(entry.route) || normalized.route;
35
- const payload = entry.payload && typeof entry.payload === 'object' ? { ...entry.payload } : {};
36
- (payload as any).sessionKey = normalized.sessionKey;
37
- (payload as any).platform = route.platform;
38
- (payload as any).groupId = route.groupId;
39
- (payload as any).userId = route.userId;
39
+ const payload: Record<string, unknown> =
40
+ entry.payload && typeof entry.payload === 'object' ? { ...entry.payload } : {};
41
+ payload.sessionKey = normalized.sessionKey;
42
+ payload.platform = route.platform;
43
+ payload.groupId = route.groupId;
44
+ payload.userId = route.userId;
40
45
 
41
46
  return {
42
47
  ...entry,
48
+ messageId: asString(entry.messageId).trim(),
43
49
  accountId,
44
50
  sessionKey: normalized.sessionKey,
45
51
  route,
@@ -49,5 +55,14 @@ export function normalizePersistedOutboxEntry(args: {
49
55
  nextAttemptAt: finiteNumberOr(entry.nextAttemptAt, args.now()),
50
56
  lastAttemptAt: optionalFiniteNumber(entry.lastAttemptAt),
51
57
  lastError: entry.lastError ? asString(entry.lastError) : undefined,
58
+ lastPushAt: optionalFiniteNumber(entry.lastPushAt),
59
+ lastPushConnId: entry.lastPushConnId ? asString(entry.lastPushConnId) : undefined,
60
+ lastPushClientId: entry.lastPushClientId ? asString(entry.lastPushClientId) : undefined,
61
+ routeAttemptConnIds: Array.isArray(entry.routeAttemptConnIds)
62
+ ? entry.routeAttemptConnIds.map((value) => asString(value)).filter(Boolean)
63
+ : undefined,
64
+ routeAttemptRound: optionalFiniteNumber(entry.routeAttemptRound),
65
+ fastReroutePending: entry.fastReroutePending === true,
66
+ awaitingRetryPush: entry.awaitingRetryPush === true,
52
67
  };
53
68
  }
@@ -4,6 +4,18 @@ function asString(v: unknown, fallback = ''): string {
4
4
  return String(v);
5
5
  }
6
6
 
7
+ export type BncrChannelPolicyConfig = {
8
+ enabled?: boolean;
9
+ dmPolicy?: unknown;
10
+ groupPolicy?: unknown;
11
+ allowFrom?: unknown;
12
+ groupAllowFrom?: unknown;
13
+ requireMention?: unknown;
14
+ };
15
+
16
+ export type BncrDmPolicy = 'open' | 'allowlist' | 'disabled' | 'pairing';
17
+ export type BncrGroupPolicy = 'open' | 'allowlist' | 'disabled';
18
+
7
19
  function asList(v: unknown): string[] {
8
20
  if (!Array.isArray(v)) return [];
9
21
  return v.map((x) => asString(x).trim()).filter(Boolean);
@@ -15,18 +27,47 @@ function asBoolean(v: unknown, fallback = false): boolean {
15
27
  return String(v).trim().toLowerCase() === 'true';
16
28
  }
17
29
 
18
- export function resolveBncrChannelPolicy(channelCfg: any) {
30
+ function normalizeDmPolicy(value: unknown): BncrDmPolicy {
31
+ const normalized = asString(value || 'open')
32
+ .trim()
33
+ .toLowerCase();
34
+ switch (normalized) {
35
+ case 'allowlist':
36
+ case 'disabled':
37
+ case 'pairing':
38
+ return normalized;
39
+ default:
40
+ return 'open';
41
+ }
42
+ }
43
+
44
+ function normalizeGroupPolicy(value: unknown): BncrGroupPolicy {
45
+ const normalized = asString(value || 'open')
46
+ .trim()
47
+ .toLowerCase();
48
+ switch (normalized) {
49
+ case 'allowlist':
50
+ case 'disabled':
51
+ return normalized;
52
+ default:
53
+ return 'open';
54
+ }
55
+ }
56
+
57
+ export function resolveBncrChannelPolicy(channelCfg: BncrChannelPolicyConfig | null | undefined) {
19
58
  return {
20
59
  enabled: channelCfg?.enabled !== false,
21
- dmPolicy: asString(channelCfg?.dmPolicy || 'open').toLowerCase(),
22
- groupPolicy: asString(channelCfg?.groupPolicy || 'open').toLowerCase(),
60
+ dmPolicy: normalizeDmPolicy(channelCfg?.dmPolicy),
61
+ groupPolicy: normalizeGroupPolicy(channelCfg?.groupPolicy),
23
62
  allowFrom: asList(channelCfg?.allowFrom),
24
63
  groupAllowFrom: asList(channelCfg?.groupAllowFrom),
25
64
  requireMention: asBoolean(channelCfg?.requireMention, false),
26
65
  };
27
66
  }
28
67
 
29
- export function resolveBncrConfigWarnings(channelCfg: any): string[] {
68
+ export function resolveBncrConfigWarnings(
69
+ channelCfg: BncrChannelPolicyConfig | null | undefined,
70
+ ): string[] {
30
71
  const policy = resolveBncrChannelPolicy(channelCfg || {});
31
72
  const warnings: string[] = [];
32
73
  if (policy.requireMention) {