@xmoxmo/bncr 0.5.6 → 0.5.8

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 (90) hide show
  1. package/dist/index.js +1 -9
  2. package/index.ts +1 -9
  3. package/package.json +1 -1
  4. package/scripts/check-pack.mjs +0 -8
  5. package/scripts/selfcheck.mjs +0 -10
  6. package/src/bootstrap/runtime-loader.ts +1 -1
  7. package/src/channel.ts +118 -171
  8. package/src/core/accounts.ts +1 -6
  9. package/src/core/connection-reachability.ts +2 -6
  10. package/src/core/dead-letter-diagnostics.ts +4 -10
  11. package/src/core/logging.ts +1 -1
  12. package/src/core/outbox-entry-builders.ts +78 -84
  13. package/src/core/outbox-file-transfer-failure.ts +5 -0
  14. package/src/core/outbox-file-transfer-guards.ts +6 -0
  15. package/src/core/outbox-file-transfer-prep.ts +8 -5
  16. package/src/core/outbox-file-transfer-success.ts +5 -0
  17. package/src/core/outbox-queue.ts +1 -7
  18. package/src/core/outbox-summary.ts +10 -56
  19. package/src/core/permissions.ts +2 -10
  20. package/src/core/persisted-outbox-entry.ts +54 -11
  21. package/src/core/policy.ts +1 -11
  22. package/src/core/register-trace.ts +4 -7
  23. package/src/core/status-meta.ts +1 -9
  24. package/src/core/status.ts +1 -21
  25. package/src/core/targets.ts +52 -70
  26. package/src/core/types.ts +1 -17
  27. package/src/core/value-sanitize.ts +38 -1
  28. package/src/messaging/inbound/commands.ts +2 -24
  29. package/src/messaging/inbound/dispatch-prep.ts +1 -1
  30. package/src/messaging/inbound/gate.ts +2 -31
  31. package/src/messaging/inbound/group-history.ts +0 -75
  32. package/src/messaging/inbound/parse.ts +2 -32
  33. package/src/messaging/inbound/scene-admin.ts +8 -8
  34. package/src/messaging/outbound/diagnostics-debug-builders.ts +0 -15
  35. package/src/messaging/outbound/diagnostics.ts +1 -7
  36. package/src/messaging/outbound/durable-queue-adapter.ts +17 -11
  37. package/src/messaging/outbound/marker-parser.ts +277 -28
  38. package/src/messaging/outbound/media-dedupe.ts +3 -10
  39. package/src/messaging/outbound/media.ts +1 -6
  40. package/src/messaging/outbound/normalize-outbound-send.ts +329 -0
  41. package/src/messaging/outbound/queue-selectors.ts +1 -5
  42. package/src/messaging/outbound/reply-enqueue-media.ts +1 -184
  43. package/src/messaging/outbound/reply-enqueue.ts +313 -175
  44. package/src/messaging/outbound/session-route.ts +6 -11
  45. package/src/messaging/outbound/target-resolver.ts +1 -6
  46. package/src/openclaw/media-runtime.ts +1 -1
  47. package/src/openclaw/routing-runtime.ts +0 -11
  48. package/src/openclaw/sdk-helpers.ts +0 -2
  49. package/src/plugin/bridge-media-facade.ts +0 -4
  50. package/src/plugin/bridge-outbox-facade.ts +0 -1
  51. package/src/plugin/channel-components.ts +0 -13
  52. package/src/plugin/channel-plugin-surface-group.ts +52 -65
  53. package/src/plugin/channel-runtime-builders-delivery.ts +3 -362
  54. package/src/plugin/channel-runtime-builders-status.ts +0 -160
  55. package/src/plugin/channel-runtime-builders.ts +3 -17
  56. package/src/plugin/channel-runtime-constants.ts +1 -1
  57. package/src/plugin/channel-runtime-types.ts +2 -24
  58. package/src/plugin/channel-send.ts +206 -221
  59. package/src/plugin/channel-utils.ts +8 -29
  60. package/src/plugin/connection-handlers-helpers.ts +0 -15
  61. package/src/plugin/connection-handlers.ts +6 -5
  62. package/src/plugin/file-inbound-abort.ts +5 -0
  63. package/src/plugin/file-inbound-chunk.ts +5 -0
  64. package/src/plugin/file-inbound-complete.ts +5 -0
  65. package/src/plugin/file-transfer-orchestrator.ts +39 -2
  66. package/src/plugin/media-dedupe-runtime.ts +1 -6
  67. package/src/plugin/media-orchestrators-runtime-group.ts +20 -149
  68. package/src/plugin/message-send.ts +7 -8
  69. package/src/plugin/messaging.ts +3 -3
  70. package/src/plugin/outbound.ts +6 -26
  71. package/src/plugin/outbox-drain-loop.ts +2 -2
  72. package/src/plugin/outbox-push.ts +1 -52
  73. package/src/plugin/outbox-unified-push-flow.ts +307 -0
  74. package/src/plugin/scene-registry.ts +1 -1
  75. package/src/plugin/target-runtime.ts +1 -6
  76. package/src/runtime/log-dedupe.ts +3 -3
  77. package/src/runtime/outbound-ack-timeout.ts +3 -12
  78. package/src/runtime/outbox-transitions.ts +1 -4
  79. package/src/runtime/status-snapshots.ts +2 -2
  80. package/src/runtime/status-worker.ts +3 -15
  81. package/src/core/outbox-text-push-prep.ts +0 -36
  82. package/src/messaging/outbound/build-send-action.ts +0 -130
  83. package/src/messaging/outbound/durable-message-adapter.ts +0 -121
  84. package/src/messaging/outbound/send-params.ts +0 -114
  85. package/src/messaging/outbound/send.ts +0 -127
  86. package/src/openclaw/ingress-runtime.ts +0 -25
  87. package/src/plugin/bridge-file-transfer-push-facade.ts +0 -142
  88. package/src/plugin/channel-send-runtime-group.ts +0 -37
  89. package/src/plugin/outbox-file-push-flow.ts +0 -69
  90. package/src/plugin/outbox-text-push-flow.ts +0 -90
package/dist/index.js CHANGED
@@ -1,6 +1,3 @@
1
- // index.ts
2
- import path3 from "node:path";
3
-
4
1
  // src/bootstrap/channel-plugin-runtime.ts
5
2
  function createDynamicChannelPlugin(args) {
6
3
  const { loaded, getCurrentBridge: getCurrentBridge2 } = args;
@@ -786,12 +783,7 @@ var BncrConfigSchema = {
786
783
  };
787
784
 
788
785
  // index.ts
789
- var readPluginVersion2 = (rootDir = pluginRoot) => {
790
- const packageJsonPath = path3.join(rootDir, "package.json");
791
- void packageJsonPath;
792
- return pluginVersion;
793
- };
794
- var pluginVersion2 = readPluginVersion2();
786
+ var pluginVersion2 = pluginVersion;
795
787
  var registerRuntime = createBncrRegisterRuntime();
796
788
  var {
797
789
  ensureGatewayMethodRegistered,
package/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import path from 'node:path';
2
1
  import { createDynamicChannelPlugin } from './src/bootstrap/channel-plugin-runtime.ts';
3
2
  import {
4
3
  type BncrRegistrationApi,
@@ -10,7 +9,6 @@ import {
10
9
  type ChannelModule,
11
10
  type LoadedRuntime,
12
11
  loadBncrRuntimeSync,
13
- pluginRoot,
14
12
  pluginVersion as runtimePluginVersion,
15
13
  } from './src/bootstrap/runtime-loader.ts';
16
14
  import { BncrConfigSchema } from './src/core/config-schema.ts';
@@ -19,13 +17,7 @@ import { getOpenClawRuntimeConfig } from './src/openclaw/config-runtime.ts';
19
17
 
20
18
  type ChannelPlugin = ReturnType<ChannelModule['createBncrChannelPlugin']>;
21
19
 
22
- const readPluginVersion = (rootDir = pluginRoot) => {
23
- const packageJsonPath = path.join(rootDir, 'package.json');
24
- void packageJsonPath;
25
- return runtimePluginVersion;
26
- };
27
-
28
- const pluginVersion = readPluginVersion();
20
+ const pluginVersion = runtimePluginVersion;
29
21
 
30
22
  const registerRuntime = createBncrRegisterRuntime();
31
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -76,10 +76,8 @@ const requiredSourceFiles = [
76
76
  'src/messaging/inbound/session-label.ts',
77
77
  'src/messaging/inbound/turn-context.ts',
78
78
  'src/messaging/outbound/actions.ts',
79
- 'src/messaging/outbound/build-send-action.ts',
80
79
  'src/messaging/outbound/diagnostics.ts',
81
80
  'src/messaging/outbound/diagnostics-debug-builders.ts',
82
- 'src/messaging/outbound/durable-message-adapter.ts',
83
81
  'src/messaging/outbound/durable-queue-adapter.ts',
84
82
  'src/messaging/outbound/media-dedupe.ts',
85
83
  'src/messaging/outbound/media.ts',
@@ -88,13 +86,10 @@ const requiredSourceFiles = [
88
86
  'src/messaging/outbound/reply-enqueue.ts',
89
87
  'src/messaging/outbound/reply-target-policy.ts',
90
88
  'src/messaging/outbound/retry-policy.ts',
91
- 'src/messaging/outbound/send-params.ts',
92
- 'src/messaging/outbound/send.ts',
93
89
  'src/messaging/outbound/session-route.ts',
94
90
  'src/messaging/outbound/target-resolver.ts',
95
91
  'src/openclaw/config-runtime.ts',
96
92
  'src/openclaw/inbound-session-runtime.ts',
97
- 'src/openclaw/ingress-runtime.ts',
98
93
  'src/openclaw/media-runtime.ts',
99
94
  'src/openclaw/reply-runtime.ts',
100
95
  'src/openclaw/routing-runtime.ts',
@@ -111,7 +106,6 @@ const requiredSourceFiles = [
111
106
  'src/plugin/channel-plugin-surface-group.ts',
112
107
  'src/plugin/channel-runtime-constants.ts',
113
108
  'src/plugin/channel-runtime-builders.ts',
114
- 'src/plugin/channel-send-runtime-group.ts',
115
109
  'src/plugin/channel-runtime-types.ts',
116
110
  'src/plugin/channel-send.ts',
117
111
  'src/plugin/channel-utils.ts',
@@ -155,11 +149,9 @@ const requiredSourceFiles = [
155
149
  'src/plugin/outbox-drain-post-push.ts',
156
150
  'src/plugin/outbox-drain-runtime.ts',
157
151
  'src/plugin/outbox-drain-schedule.ts',
158
- 'src/plugin/outbox-file-push-flow.ts',
159
152
  'src/plugin/outbox-push-route-runtime-group.ts',
160
153
  'src/plugin/outbox-push.ts',
161
154
  'src/plugin/outbox-route.ts',
162
- 'src/plugin/outbox-text-push-flow.ts',
163
155
  'src/plugin/runtime-diagnostics-snapshot.ts',
164
156
  'src/plugin/setup.ts',
165
157
  'src/plugin/state-transient-runtime-group.ts',
@@ -44,7 +44,6 @@ const requiredSourceFiles = [
44
44
  'src/core/outbox-summary.ts',
45
45
  'src/core/outbox-text-push-failure.ts',
46
46
  'src/core/outbox-text-push-guards.ts',
47
- 'src/core/outbox-text-push-prep.ts',
48
47
  'src/core/outbox-text-push-success.ts',
49
48
  'src/core/permissions.ts',
50
49
  'src/core/persisted-outbox-entry.ts',
@@ -72,10 +71,8 @@ const requiredSourceFiles = [
72
71
  'src/messaging/inbound/session-label.ts',
73
72
  'src/messaging/inbound/turn-context.ts',
74
73
  'src/messaging/outbound/actions.ts',
75
- 'src/messaging/outbound/build-send-action.ts',
76
74
  'src/messaging/outbound/diagnostics.ts',
77
75
  'src/messaging/outbound/diagnostics-debug-builders.ts',
78
- 'src/messaging/outbound/durable-message-adapter.ts',
79
76
  'src/messaging/outbound/durable-queue-adapter.ts',
80
77
  'src/messaging/outbound/media-dedupe.ts',
81
78
  'src/messaging/outbound/media.ts',
@@ -84,13 +81,10 @@ const requiredSourceFiles = [
84
81
  'src/messaging/outbound/reply-enqueue.ts',
85
82
  'src/messaging/outbound/reply-target-policy.ts',
86
83
  'src/messaging/outbound/retry-policy.ts',
87
- 'src/messaging/outbound/send-params.ts',
88
- 'src/messaging/outbound/send.ts',
89
84
  'src/messaging/outbound/session-route.ts',
90
85
  'src/messaging/outbound/target-resolver.ts',
91
86
  'src/openclaw/config-runtime.ts',
92
87
  'src/openclaw/inbound-session-runtime.ts',
93
- 'src/openclaw/ingress-runtime.ts',
94
88
  'src/openclaw/media-runtime.ts',
95
89
  'src/openclaw/reply-runtime.ts',
96
90
  'src/openclaw/routing-runtime.ts',
@@ -107,7 +101,6 @@ const requiredSourceFiles = [
107
101
  'src/plugin/channel-plugin-surface-group.ts',
108
102
  'src/plugin/channel-runtime-constants.ts',
109
103
  'src/plugin/channel-runtime-builders.ts',
110
- 'src/plugin/channel-send-runtime-group.ts',
111
104
  'src/plugin/channel-runtime-types.ts',
112
105
  'src/plugin/channel-send.ts',
113
106
  'src/plugin/channel-utils.ts',
@@ -151,11 +144,9 @@ const requiredSourceFiles = [
151
144
  'src/plugin/outbox-drain-post-push.ts',
152
145
  'src/plugin/outbox-drain-runtime.ts',
153
146
  'src/plugin/outbox-drain-schedule.ts',
154
- 'src/plugin/outbox-file-push-flow.ts',
155
147
  'src/plugin/outbox-push-route-runtime-group.ts',
156
148
  'src/plugin/outbox-push.ts',
157
149
  'src/plugin/outbox-route.ts',
158
- 'src/plugin/outbox-text-push-flow.ts',
159
150
  'src/plugin/runtime-diagnostics-snapshot.ts',
160
151
  'src/plugin/setup.ts',
161
152
  'src/plugin/state-transient-runtime-group.ts',
@@ -198,7 +189,6 @@ const readNpmLatestVersion = (packageName) => {
198
189
 
199
190
  const requiredOpenClawSdkSubpaths = [
200
191
  'openclaw/plugin-sdk',
201
- 'openclaw/plugin-sdk/boolean-param',
202
192
  'openclaw/plugin-sdk/channel-outbound',
203
193
  'openclaw/plugin-sdk/channel-ingress-runtime',
204
194
  'openclaw/plugin-sdk/channel-message',
@@ -44,7 +44,7 @@ export const pluginRoot = resolveBncrPluginRoot(pluginFile);
44
44
  const runtimeSourceDir = resolveBncrRuntimeSourceDir(pluginDir);
45
45
  let runtime: LoadedRuntime | null = null;
46
46
 
47
- export const readPluginVersion = (rootDir = pluginRoot) => {
47
+ const readPluginVersion = (rootDir = pluginRoot) => {
48
48
  try {
49
49
  const raw = fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8');
50
50
  const parsed = JSON.parse(raw);
package/src/channel.ts CHANGED
@@ -19,10 +19,7 @@ import {
19
19
  emitBncrLog,
20
20
  summarizeBncrTextPreview,
21
21
  } from './core/logging.ts';
22
- import {
23
- buildFileTransferOutboxEntry as buildFileTransferOutboxEntryFromRuntime,
24
- buildTextOutboxEntry as buildTextOutboxEntryFromRuntime,
25
- } from './core/outbox-entry-builders.ts';
22
+ import { buildOutboxEntry as buildOutboxEntryFromRuntime } from './core/outbox-entry-builders.ts';
26
23
  import { summarizeOutboxEntry } from './core/outbox-summary.ts';
27
24
  import { normalizePersistedOutboxEntry as normalizePersistedOutboxEntryFromRuntime } from './core/persisted-outbox-entry.ts';
28
25
  import {
@@ -53,7 +50,6 @@ import { OUTBOUND_FLUSH_REASON, OUTBOUND_FLUSH_TRIGGER } from './messaging/outbo
53
50
  import type {
54
51
  NormalizedReplyPayload,
55
52
  OutboundReplyTargetPolicy,
56
- ReplyMediaEntriesParams,
57
53
  ReplyPayloadInput,
58
54
  } from './messaging/outbound/reply-enqueue.ts';
59
55
  import type { OpenClawChannelRuntimeApiHolder } from './openclaw/channel-runtime-contracts.ts';
@@ -67,7 +63,6 @@ import { createBncrBridgeConnectionFacade } from './plugin/bridge-connection-fac
67
63
  import { createBncrBridgeDiagnosticsFacade } from './plugin/bridge-diagnostics-facade.ts';
68
64
  import { createBncrBridgeDrainFacade } from './plugin/bridge-drain-facade.ts';
69
65
  import { createBncrBridgeExtendedDiagnosticsFacade } from './plugin/bridge-extended-diagnostics-facade.ts';
70
- import { createBncrBridgeFileTransferPushFacade } from './plugin/bridge-file-transfer-push-facade.ts';
71
66
  import {
72
67
  cleanupBncrBridgeRuntime,
73
68
  shutdownBncrBridgeService,
@@ -152,10 +147,9 @@ import type {
152
147
  BncrChannelConfigRoot,
153
148
  BncrChannelSendContext,
154
149
  BncrSceneRecord,
155
- FileAckPayloadState,
156
150
  PersistedState,
157
151
  } from './plugin/channel-runtime-types.ts';
158
- import { createBncrChannelSendRuntimeGroup } from './plugin/channel-send-runtime-group.ts';
152
+ import { createBncrChannelSendRuntime } from './plugin/channel-send.ts';
159
153
  import {
160
154
  asString,
161
155
  backoffMs,
@@ -168,6 +162,7 @@ import {
168
162
  } from './plugin/channel-utils.ts';
169
163
  import { BNCR_CONFIG_SURFACE } from './plugin/config.ts';
170
164
  import { createBncrConnectionStateRuntimeGroup } from './plugin/connection-state-runtime-group.ts';
165
+ import type { FileAckPayloadState } from './plugin/file-ack-runtime.ts';
171
166
  import { createBncrFileTransferRuntimeGroup } from './plugin/file-transfer-runtime-group.ts';
172
167
  import { BNCR_GATEWAY_METHODS } from './plugin/gateway-methods.ts';
173
168
  import { prepareBncrInboundAcceptance } from './plugin/inbound-acceptance.ts';
@@ -175,9 +170,9 @@ import { createBncrInboundSurfaceHandlersGroup } from './plugin/inbound-surface-
175
170
  import { createBncrMediaOrchestratorsRuntimeGroup } from './plugin/media-orchestrators-runtime-group.ts';
176
171
  import { BNCR_CHANNEL_META } from './plugin/meta.ts';
177
172
  import type { BncrOutboxAckOkTelemetryPatch } from './plugin/outbox-ack-outcome.ts';
178
- import { runBncrFileTransferOutboxPush } from './plugin/outbox-file-push-flow.ts';
173
+
179
174
  import { createBncrOutboxPushRouteRuntimeGroup } from './plugin/outbox-push-route-runtime-group.ts';
180
- import { runBncrTextOutboxPush } from './plugin/outbox-text-push-flow.ts';
175
+ import { runBncrOutboxPush } from './plugin/outbox-unified-push-flow.ts';
181
176
  import {
182
177
  createBncrDeadLetterDiagnosticsHelpers,
183
178
  createBncrDiagnosticsSelectionHelpers,
@@ -1131,85 +1126,54 @@ class BncrBridgeRuntime {
1131
1126
  // This block prepares media, chooses transfer mode, sends chunk/complete
1132
1127
  // frames, and feeds successful media pushes back into the shared outbox path.
1133
1128
 
1134
- private async pushFileTransferSuccessPath(args: {
1135
- entry: OutboxEntry;
1136
- meta: Record<string, unknown>;
1137
- owner: ReturnType<BncrBridgeRuntime['resolveOutboxPushOwner']>;
1138
- connIds: Iterable<string>;
1139
- recentInboundReachable: boolean;
1140
- routeReason: string;
1141
- mediaUrl: string;
1142
- }): Promise<void> {
1143
- await this.fileTransferPushFacade.pushFileTransferSuccessPath(args);
1144
- }
1145
-
1146
1129
  private handleFileTransferPushFailure(args: { entry: OutboxEntry; error: unknown }) {
1147
- this.fileTransferPushFacade.handleFileTransferPushFailure(args);
1130
+ this.outboxPush.handleFileTransferPushFailure(args);
1148
1131
  }
1149
1132
 
1150
1133
  private handleFileTransferPushGuardFailure(
1151
- args: Parameters<typeof this.fileTransferPushFacade.handleFileTransferPushGuardFailure>[0],
1134
+ args: Parameters<typeof this.outboxPush.handleFileTransferPushGuardFailure>[0],
1152
1135
  ) {
1153
- this.fileTransferPushFacade.handleFileTransferPushGuardFailure(args);
1136
+ this.outboxPush.handleFileTransferPushGuardFailure(args);
1154
1137
  }
1155
1138
 
1156
1139
  // File-transfer outbound entry builders / dedupe -------------------------
1157
1140
 
1158
- private async tryPushFileTransferEntry(
1159
- entry: OutboxEntry,
1160
- meta: Record<string, unknown>,
1161
- ): Promise<boolean> {
1162
- return await runBncrFileTransferOutboxPush({
1163
- entry,
1164
- meta,
1165
- gatewayContext: this.gatewayContext,
1166
- owner: this.resolveOutboxPushOwner(entry.accountId),
1167
- resolvePushConnIds: (accountId) => this.resolvePushConnIds(accountId),
1168
- resolveRecentInboundConnIds: (accountId) => this.resolveRecentInboundConnIds(accountId),
1169
- hasRecentInboundReachability: (accountId) => this.hasRecentInboundReachability(accountId),
1170
- isRevalidatedAttemptedConn: (connId) => this.isRevalidatedAttemptedConn(entry, connId),
1171
- handleFileTransferPushGuardFailure: (flowArgs) =>
1172
- this.handleFileTransferPushGuardFailure(flowArgs),
1173
- pushFileTransferSuccessPath: (flowArgs) => this.pushFileTransferSuccessPath(flowArgs),
1174
- handleFileTransferPushFailure: (flowArgs) => this.handleFileTransferPushFailure(flowArgs),
1175
- });
1176
- }
1177
-
1178
- private buildFileTransferOutboxEntry(params: {
1141
+ private buildOutboxEntry(params: {
1179
1142
  accountId: string;
1180
1143
  sessionKey: string;
1181
1144
  route: BncrRoute;
1182
- mediaUrl: string;
1183
- mediaLocalRoots?: readonly string[];
1184
- text?: string;
1185
- asVoice?: boolean;
1186
- audioAsVoice?: boolean;
1187
1145
  type?: string;
1188
- extra?: Record<string, unknown>;
1146
+ msg: string;
1189
1147
  kind?: 'tool' | 'block' | 'final';
1190
1148
  replyToId?: string;
1191
1149
  replyTargetPolicy?: OutboundReplyTargetPolicy;
1150
+ extra?: Record<string, unknown>;
1151
+ transferMode?: 'text' | 'media';
1152
+ mediaUrl?: string;
1153
+ mediaLocalRoots?: readonly string[];
1154
+ asVoice?: boolean;
1155
+ audioAsVoice?: boolean;
1192
1156
  downloadMedia?: boolean;
1193
1157
  }): OutboxEntry {
1194
- return buildFileTransferOutboxEntryFromRuntime({
1158
+ return buildOutboxEntryFromRuntime({
1195
1159
  createMessageId: () => randomUUID(),
1196
1160
  now,
1197
1161
  normalizeAccountId,
1198
- pushEvent: BNCR_PUSH_EVENT,
1199
1162
  accountId: params.accountId,
1200
1163
  sessionKey: params.sessionKey,
1201
1164
  route: params.route,
1202
- mediaUrl: params.mediaUrl,
1203
- mediaLocalRoots: params.mediaLocalRoots,
1204
- text: asString(params.text || ''),
1205
- asVoice: params.asVoice,
1206
- audioAsVoice: params.audioAsVoice,
1207
1165
  type: params.type,
1208
- extra: params.extra,
1166
+ msg: params.msg,
1209
1167
  kind: params.kind,
1210
1168
  replyToId: asString(params.replyToId || '').trim() || undefined,
1211
1169
  replyTargetPolicy: params.replyTargetPolicy,
1212
- downloadMedia: params.downloadMedia === true ? true : undefined,
1170
+ extra: params.extra,
1171
+ transferMode: params.transferMode,
1172
+ mediaUrl: params.mediaUrl,
1173
+ mediaLocalRoots: params.mediaLocalRoots,
1174
+ asVoice: params.asVoice,
1175
+ audioAsVoice: params.audioAsVoice,
1176
+ downloadMedia: params.downloadMedia,
1213
1177
  });
1214
1178
  }
1215
1179
 
@@ -1243,29 +1207,55 @@ class BncrBridgeRuntime {
1243
1207
 
1244
1208
  private buildFileTransferOutboundFrame(params: {
1245
1209
  entry: OutboxEntry;
1246
- meta: Record<string, unknown>;
1210
+ msg: Record<string, unknown>;
1247
1211
  media: { fileName?: string; mimeType?: string; path?: string; base64?: string; type?: string };
1248
1212
  mediaUrl: string;
1249
1213
  }) {
1250
- const wantsVoice = params.meta.asVoice === true || params.meta.audioAsVoice === true;
1251
- const messageKind =
1252
- params.meta.messageKind === 'tool' ||
1253
- params.meta.messageKind === 'block' ||
1254
- params.meta.messageKind === 'final'
1255
- ? params.meta.messageKind
1214
+ const wantsVoice = params.msg.asVoice === true || params.msg.audioAsVoice === true;
1215
+ const rawKind = params.msg.kind ?? (params.msg as Record<string, unknown>).messageKind;
1216
+ const messageKind: 'tool' | 'block' | 'final' | undefined =
1217
+ rawKind === 'tool' || rawKind === 'block' || rawKind === 'final'
1218
+ ? (rawKind as 'tool' | 'block' | 'final')
1256
1219
  : undefined;
1257
1220
 
1258
1221
  const hasPayload = !!(params.media.base64 || params.media.path);
1259
- if (hasPayload) {
1260
- console.log(
1261
- '[bncr] outbound-media download=true|media=' +
1262
- (params.media.path || `base64:${(params.media.base64 || '').substring(0, 12)}...`) +
1263
- '|url=' +
1264
- params.mediaUrl.split('?')[0],
1265
- );
1266
- } else {
1267
- console.log(`[bncr] outbound-media pass-through|url=${params.mediaUrl.split('?')[0]}`);
1222
+ this.logInfoJson(
1223
+ 'outbound',
1224
+ 'media-frame',
1225
+ {
1226
+ mid: params.entry.messageId,
1227
+ mode: hasPayload ? (params.media.path ? 'chunk' : 'base64') : 'pass-through',
1228
+ type: wantsVoice ? 'voice' : asString(params.msg.type || '') || undefined,
1229
+ url: params.mediaUrl.split('?')[0],
1230
+ ...(params.media.path ? { path: params.media.path } : {}),
1231
+ ...(params.media.base64 ? { base64: true } : {}),
1232
+ },
1233
+ { debugOnly: true },
1234
+ );
1235
+ // Collect non-control fields from msg for passthrough into message.*
1236
+ const MSG_PASSTHROUGH_KEYS = new Set([
1237
+ 'platform',
1238
+ 'groupId',
1239
+ 'userId',
1240
+ 'mediaUrl',
1241
+ 'mediaLocalRoots',
1242
+ 'asVoice',
1243
+ 'audioAsVoice',
1244
+ 'downloadMedia',
1245
+ 'transferMode',
1246
+ 'path',
1247
+ 'base64',
1248
+ 'fileName',
1249
+ 'kind',
1250
+ 'replyToId',
1251
+ ]);
1252
+ const passthroughExtra: Record<string, unknown> = {};
1253
+ for (const [key, value] of Object.entries(params.msg)) {
1254
+ if (!MSG_PASSTHROUGH_KEYS.has(key) && value !== undefined) {
1255
+ passthroughExtra[key] = value;
1256
+ }
1268
1257
  }
1258
+
1269
1259
  return buildBncrMediaOutboundFrame({
1270
1260
  messageId: params.entry.messageId,
1271
1261
  sessionKey: params.entry.sessionKey,
@@ -1278,16 +1268,16 @@ class BncrBridgeRuntime {
1278
1268
  path: params.media.path,
1279
1269
  },
1280
1270
  mediaUrl: params.mediaUrl,
1281
- mediaMsg: asString(params.meta.text || ''),
1271
+ mediaMsg: asString(params.msg.msg ?? ''),
1282
1272
  fileName: resolveOutboundFileName({
1283
1273
  mediaUrl: params.mediaUrl,
1284
1274
  fileName: params.media.fileName,
1285
1275
  mimeType: params.media.mimeType,
1286
1276
  }),
1287
- hintedType: wantsVoice ? 'voice' : asString(params.meta.type || '') || undefined,
1288
- extra: params.meta.extra as Record<string, unknown> | undefined,
1277
+ hintedType: wantsVoice ? 'voice' : asString(params.msg.type || '') || undefined,
1278
+ extra: Object.keys(passthroughExtra).length > 0 ? passthroughExtra : undefined,
1289
1279
  kind: messageKind,
1290
- replyToId: normalizeReplyToId(params.meta.replyToId) || undefined,
1280
+ replyToId: normalizeReplyToId(asString(params.msg.replyToId || '')) || undefined,
1291
1281
  now: now(),
1292
1282
  });
1293
1283
  }
@@ -1296,66 +1286,8 @@ class BncrBridgeRuntime {
1296
1286
  // This is the core outbound state machine: build entries, enqueue them,
1297
1287
  // push them, observe ACK outcomes, and terminate in retry or dead-letter.
1298
1288
 
1299
- private buildTextOutboxEntry(params: {
1300
- accountId: string;
1301
- sessionKey: string;
1302
- route: BncrRoute;
1303
- text: string;
1304
- extra?: Record<string, unknown>;
1305
- kind?: 'tool' | 'block' | 'final';
1306
- replyToId?: string;
1307
- replyTargetPolicy?: OutboundReplyTargetPolicy;
1308
- downloadMedia?: boolean;
1309
- }): OutboxEntry {
1310
- return buildTextOutboxEntryFromRuntime({
1311
- createMessageId: () => randomUUID(),
1312
- now,
1313
- normalizeAccountId,
1314
- normalizeReplyToId,
1315
- accountId: params.accountId,
1316
- sessionKey: params.sessionKey,
1317
- route: params.route,
1318
- text: params.text,
1319
- extra: params.extra && Object.keys(params.extra).length > 0 ? { ...params.extra } : undefined,
1320
- kind: params.kind,
1321
- replyToId: params.replyToId,
1322
- replyTargetPolicy: params.replyTargetPolicy,
1323
- downloadMedia: params.downloadMedia === true ? true : undefined,
1324
- });
1325
- }
1326
-
1327
1289
  private async tryPushEntry(entry: OutboxEntry): Promise<boolean> {
1328
- const meta = isPlainObject(entry.payload?._meta) ? entry.payload._meta : null;
1329
- if (meta?.kind === 'file-transfer') {
1330
- return this.tryPushFileTransferEntry(entry, meta);
1331
- }
1332
-
1333
- return this.tryPushTextEntry(entry);
1334
- }
1335
-
1336
- private pushTextSuccessPath(args: {
1337
- entry: OutboxEntry;
1338
- owner: ReturnType<BncrBridgeRuntime['resolveOutboxPushOwner']>;
1339
- connIds: Iterable<string>;
1340
- recentInboundReachable: boolean;
1341
- routeReason: string;
1342
- ownerConnId?: string;
1343
- }) {
1344
- this.outboxPush.pushTextSuccessPath(args);
1345
- }
1346
-
1347
- private handleTextPushFailure(args: { entry: OutboxEntry; error: unknown }) {
1348
- this.outboxPush.handleTextPushFailure(args);
1349
- }
1350
-
1351
- // Text outbound push runtime bridge --------------------------------------
1352
-
1353
- // Outbound diagnostics/logging helpers -----------------------------------
1354
- // Keep the compact logging helpers adjacent to the outbound state machine so
1355
- // queue transitions and emitted diagnostics remain easy to correlate.
1356
-
1357
- private async tryPushTextEntry(entry: OutboxEntry): Promise<boolean> {
1358
- return await runBncrTextOutboxPush({
1290
+ return runBncrOutboxPush({
1359
1291
  entry,
1360
1292
  gatewayContext: this.gatewayContext,
1361
1293
  owner: this.resolveOutboxPushOwner(entry.accountId),
@@ -1365,11 +1297,34 @@ class BncrBridgeRuntime {
1365
1297
  isRevalidatedAttemptedConn: (connId) => this.isRevalidatedAttemptedConn(entry, connId),
1366
1298
  recordOutboxPrePushFailure: (flowArgs) => this.recordOutboxPrePushFailure(flowArgs),
1367
1299
  logOutboxPushSkip: (flowArgs) => this.logOutboxPushSkip(flowArgs),
1368
- pushTextSuccessPath: (flowArgs) => this.pushTextSuccessPath(flowArgs),
1300
+ handleFileTransferPushGuardFailure: (flowArgs) =>
1301
+ this.handleFileTransferPushGuardFailure(flowArgs),
1369
1302
  handleTextPushFailure: (flowArgs) => this.handleTextPushFailure(flowArgs),
1303
+ handleFileTransferPushFailure: (flowArgs) => this.handleFileTransferPushFailure(flowArgs),
1304
+ // Unified push deps
1305
+ pushEvent: BNCR_PUSH_EVENT,
1306
+ gatewayBroadcastToConnIds: (event, payload, connIds) =>
1307
+ this.gatewayContext?.broadcastToConnIds(event, payload, connIds),
1308
+ recordOutboxPushSuccess: (args) => this.recordOutboxPushSuccess(args),
1309
+ logOutboxRouteSelect: (args) => this.logOutboxRouteSelect(args),
1310
+ logOutboxPushOkSummary: (mid) => this.logOutboxPushOkSummary(mid),
1311
+ logOutboundSummary: (entry) => this.logOutboundSummary(entry),
1312
+ logOutboxPushOk: (args) => this.logOutboxPushOk(args),
1313
+ transferMediaToBncrClient: (params) => this.transferMediaToBncrClient(params),
1314
+ buildFileTransferOutboundFrame: (params) => this.buildFileTransferOutboundFrame(params),
1370
1315
  });
1371
1316
  }
1372
1317
 
1318
+ private handleTextPushFailure(args: { entry: OutboxEntry; error: unknown }) {
1319
+ this.outboxPush.handleTextPushFailure(args);
1320
+ }
1321
+
1322
+ // Text outbound push runtime bridge --------------------------------------
1323
+
1324
+ // Outbound diagnostics/logging helpers -----------------------------------
1325
+ // Keep the compact logging helpers adjacent to the outbound state machine so
1326
+ // queue transitions and emitted diagnostics remain easy to correlate.
1327
+
1373
1328
  private logOutboxPushSkip(args: {
1374
1329
  messageId: string;
1375
1330
  accountId: string;
@@ -1753,8 +1708,12 @@ class BncrBridgeRuntime {
1753
1708
  clampFiniteNumber(value, fallback, min ?? fallback, max ?? fallback),
1754
1709
  normalizeAccountId,
1755
1710
  formatDisplayScope,
1756
- isFileTransferEntry: (entry) =>
1757
- isPlainObject(entry.payload?._meta) && entry.payload?._meta?.kind === 'file-transfer',
1711
+ isFileTransferEntry: (entry) => {
1712
+ const msg = isPlainObject(entry.payload)
1713
+ ? (entry.payload as Record<string, unknown>).message
1714
+ : null;
1715
+ return isPlainObject(msg) && (msg as Record<string, unknown>).transferMode === 'media';
1716
+ },
1758
1717
  recommendedAckTimeoutMaxMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
1759
1718
  adaptiveAckTimeoutEnabled: ADAPTIVE_ACK_TIMEOUT_DEFAULT_ENABLED,
1760
1719
  defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
@@ -2705,8 +2664,7 @@ class BncrBridgeRuntime {
2705
2664
  logInfo: (scope, message, options) => this.logInfo(scope, message, options),
2706
2665
  logEnqueueFromReply: (args) => this.logEnqueueFromReply(args),
2707
2666
  enqueueOutbound: (entry) => this.enqueueOutbound(entry),
2708
- buildTextOutboxEntry: (args) => this.buildTextOutboxEntry(args),
2709
- buildFileTransferOutboxEntry: (args) => this.buildFileTransferOutboxEntry(args),
2667
+ buildOutboxEntry: (args) => this.buildOutboxEntry(args),
2710
2668
  rememberRecentMediaSend: (args) => this.rememberRecentMediaSend(args),
2711
2669
  tryBuildMediaDedupeFallback: (args) => this.tryBuildMediaDedupeFallback(args),
2712
2670
  });
@@ -2760,7 +2718,7 @@ class BncrBridgeRuntime {
2760
2718
  mediaLocalRoots?: readonly string[];
2761
2719
  replyTargetPolicy?: OutboundReplyTargetPolicy;
2762
2720
  }) {
2763
- this.bridgeMediaFacade.enqueueFromReply(params);
2721
+ return this.bridgeMediaFacade.enqueueFromReply(params);
2764
2722
  }
2765
2723
 
2766
2724
  private logEnqueueFromReply(args: {
@@ -2800,24 +2758,6 @@ class BncrBridgeRuntime {
2800
2758
  normalizeAccountId,
2801
2759
  });
2802
2760
 
2803
- private readonly fileTransferPushFacade = createBncrBridgeFileTransferPushFacade({
2804
- pushEvent: BNCR_PUSH_EVENT,
2805
- getGatewayContext: () => this.gatewayContext,
2806
- transferMediaToBncrClient: (params) => this.transferMediaToBncrClient(params),
2807
- buildFileTransferOutboundFrame: (params) => this.buildFileTransferOutboundFrame(params),
2808
- logOutboxRouteSelect: (args) => this.logOutboxRouteSelect(args),
2809
- recordOutboxPushSuccess: (args) => this.recordOutboxPushSuccess(args),
2810
- logOutboxPushOkSummary: (messageId) => this.logOutboxPushOkSummary(messageId),
2811
- logOutboxPushOk: (args) => this.logOutboxPushOk(args),
2812
- handleFileTransferPushFailure: (args) => this.outboxPush.handleFileTransferPushFailure(args),
2813
- handleFileTransferPushGuardFailure: (args) =>
2814
- this.outboxPush.handleFileTransferPushGuardFailure(args),
2815
- });
2816
-
2817
- private enqueueReplyMediaEntries(params: ReplyMediaEntriesParams) {
2818
- this.bridgeMediaFacade.enqueueReplyMediaEntries(params);
2819
- }
2820
-
2821
2761
  // Surface runtime assembly -----------------------------------------------
2822
2762
  // The bridge remains the ownership root, but these builders keep the final
2823
2763
  // gateway/inbound/channel send wiring readable by concern instead of as one
@@ -2937,6 +2877,18 @@ class BncrBridgeRuntime {
2937
2877
  logInfo: (scope, message, options) => this.logInfo(scope, message, options),
2938
2878
  ...targetRuntime,
2939
2879
  listOutboxEntries: () => Array.from(this.outbox.values()),
2880
+ resolveSceneDownloadMedia: (to: string) => {
2881
+ const parts = to.split(':');
2882
+ if (parts.length >= 3) {
2883
+ const key = `${parts[1]}:${parts[2]}`;
2884
+ const sc = this.sceneRegistry.get(key);
2885
+ // Scene boolean wins (including explicit false); else fall to global.
2886
+ if (typeof sc?.downloadMedia === 'boolean') return sc.downloadMedia;
2887
+ const g = this.sceneRegistry.get('__global__');
2888
+ if (typeof g?.downloadMedia === 'boolean') return g.downloadMedia;
2889
+ }
2890
+ return undefined;
2891
+ },
2940
2892
  });
2941
2893
  }
2942
2894
 
@@ -3021,20 +2973,15 @@ class BncrBridgeRuntime {
3021
2973
  if (ctx.forceDocument === true) extra.forceDocument = true;
3022
2974
  if (ctx.gifPlayback === true) extra.gifPlayback = true;
3023
2975
  if (ctx.silent === true) extra.silent = true;
3024
- // Clear original fields so resolveUnifiedOutboundExtra doesn't re-add and override marker values
2976
+ // Promote host flags into extra so marker params can override them via mergeMarkerAndHostFields
3025
2977
  return { ...ctx, extra, forceDocument: undefined, gifPlayback: undefined, silent: undefined };
3026
2978
  }
3027
2979
 
3028
2980
  // Channel send surface assembly ------------------------------------------
3029
- // This group backs the public channel send APIs and should stay adjacent to
3030
- // the final exposed channel* methods for quick top-down scanning.
3031
-
3032
- private readonly channelSendRuntimeGroup = createBncrChannelSendRuntimeGroup(
2981
+ private readonly channelSendRuntime = createBncrChannelSendRuntime(
3033
2982
  this.buildChannelSendRuntime(),
3034
2983
  );
3035
2984
 
3036
- private readonly channelSendRuntime = this.channelSendRuntimeGroup.channelSendRuntime;
3037
-
3038
2985
  channelSendText = async (ctx: BncrChannelSendContext) =>
3039
2986
  this.channelSendRuntime.channelSendText(this.mergeHostFields(ctx));
3040
2987
 
@@ -3,16 +3,11 @@ import type {
3
3
  BncrChannelConfigRoot,
4
4
  BncrChannelConfigSection,
5
5
  } from '../plugin/channel-runtime-types.ts';
6
+ import { asString } from './value-sanitize.ts';
6
7
 
7
8
  const CHANNEL_ID = 'bncr';
8
9
  const BNCR_DEFAULT_ACCOUNT_ID = 'Primary';
9
10
 
10
- function asString(v: unknown, fallback = ''): string {
11
- if (typeof v === 'string') return v;
12
- if (v == null) return fallback;
13
- return String(v);
14
- }
15
-
16
11
  export function normalizeAccountId(accountId?: string | null): string {
17
12
  const v = asString(accountId || '').trim();
18
13
  if (!v) return BNCR_DEFAULT_ACCOUNT_ID;
@@ -1,9 +1,5 @@
1
1
  import type { BncrConnection, 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
- }
2
+ import { finiteNumberOr } from './value-sanitize.ts';
7
3
 
8
4
  export function hasRecentInboundReachability(args: {
9
5
  now: number;
@@ -145,7 +141,7 @@ function scoreOutboundPushConnection(args: {
145
141
  };
146
142
  }
147
143
 
148
- export function compareOutboundPushConnections(args: {
144
+ function compareOutboundPushConnections(args: {
149
145
  a: BncrConnection;
150
146
  b: BncrConnection;
151
147
  now: number;