@xmoxmo/bncr 0.5.5 → 0.5.6

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.
@@ -8,11 +8,6 @@ function asString(v: unknown, fallback = ''): string {
8
8
  return String(v);
9
9
  }
10
10
 
11
- function isAudioMimeType(mimeType?: string): boolean {
12
- const mt = asString(mimeType || '').toLowerCase();
13
- return mt.startsWith('audio/');
14
- }
15
-
16
11
  function isBncrOutboundMessageType(value: string): value is BncrOutboundMessageType {
17
12
  return (
18
13
  value === 'text' ||
@@ -43,11 +38,7 @@ export function resolveBncrOutboundMessageType(params: {
43
38
  const isStandard = isBncrOutboundMessageType(hinted);
44
39
 
45
40
  if (hasPayload && major === 'text' && (hinted === 'text' || !isStandard)) return 'file';
46
- if (hinted === 'voice') {
47
- if (isAudioMimeType(mt)) return 'voice';
48
- if (isMimeMajorOutboundMessageType(major)) return major;
49
- return 'file';
50
- }
41
+ if (hinted === 'voice') return 'voice';
51
42
  if (isStandard) return hinted;
52
43
  if (isMimeMajorOutboundMessageType(major)) return major;
53
44
  return 'file';
@@ -61,7 +52,7 @@ export function buildBncrMediaOutboundFrame(params: {
61
52
  mode: 'base64' | 'chunk';
62
53
  mimeType?: string;
63
54
  fileName?: string;
64
- mediaBase64?: string;
55
+ base64?: string;
65
56
  path?: string;
66
57
  };
67
58
  mediaUrl: string;
@@ -87,17 +78,17 @@ export function buildBncrMediaOutboundFrame(params: {
87
78
  type: resolveBncrOutboundMessageType({
88
79
  mimeType: params.media.mimeType,
89
80
  fileName: params.media.fileName,
90
- hasPayload: !!(params.media.path || params.media.mediaBase64),
81
+ hasPayload: !!(params.media.path || params.media.base64),
91
82
  hintedType: params.hintedType,
92
83
  }),
93
84
  kind: params.kind,
94
85
  mimeType: params.media.mimeType || '',
95
86
  msg: params.mediaMsg,
96
87
  path: params.media.path || params.mediaUrl,
97
- base64: params.media.mediaBase64 || '',
88
+ base64: params.media.base64 || '',
98
89
  fileName: params.fileName,
99
90
  transferMode: params.media.mode,
100
- ...(params.extra ? { extra: { ...params.extra } } : {}),
91
+ ...(params.extra ? { ...params.extra } : {}),
101
92
  },
102
93
  ts: params.now,
103
94
  };
@@ -24,6 +24,7 @@ export function enqueueReplyMediaFallbackTextEntry(
24
24
  sessionKey: string;
25
25
  route: BncrRoute;
26
26
  text: string;
27
+ extra?: Record<string, unknown>;
27
28
  kind?: 'tool' | 'block' | 'final';
28
29
  replyToId?: string;
29
30
  replyTargetPolicy?: OutboundReplyTargetPolicy;
@@ -61,6 +62,7 @@ export function enqueueReplyMediaFileTransferEntry(
61
62
  text: string;
62
63
  asVoice: boolean;
63
64
  audioAsVoice: boolean;
65
+ downloadMedia?: boolean;
64
66
  type?: string;
65
67
  extra?: Record<string, unknown>;
66
68
  kind?: 'tool' | 'block' | 'final';
@@ -86,6 +88,7 @@ export function enqueueReplyMediaFileTransferEntry(
86
88
  text: params.text,
87
89
  asVoice: params.asVoice,
88
90
  audioAsVoice: params.audioAsVoice,
91
+ downloadMedia: params.downloadMedia,
89
92
  type: params.type,
90
93
  extra: params.extra,
91
94
  kind: params.kind,
@@ -133,6 +136,7 @@ export function enqueueSingleReplyMediaEntry(
133
136
  normalizedText: params.normalizedText,
134
137
  asVoice: params.params.payload.asVoice,
135
138
  audioAsVoice: params.params.payload.audioAsVoice,
139
+ downloadMedia: params.params.payload.downloadMedia,
136
140
  type: params.params.payload.type,
137
141
  extra: params.params.payload.extra,
138
142
  kind: params.params.payload.kind,
@@ -1,4 +1,5 @@
1
1
  import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
2
+ import { extractConsumptionFields, parseBncrMarker } from './marker-parser.ts';
2
3
  import { hasReplyMediaEntries } from './reply-enqueue-media.ts';
3
4
  import type { OutboundReplyTargetPolicy } from './reply-target-policy.ts';
4
5
  import { normalizeOutboundReplyToId } from './reply-target-policy.ts';
@@ -18,6 +19,7 @@ export type ReplyPayloadInput = {
18
19
  mediaUrls?: string[];
19
20
  asVoice?: boolean;
20
21
  audioAsVoice?: boolean;
22
+ downloadMedia?: boolean;
21
23
  type?: string;
22
24
  extra?: Record<string, unknown>;
23
25
  kind?: 'tool' | 'block' | 'final';
@@ -31,6 +33,7 @@ export type NormalizedReplyPayload = {
31
33
  mediaList: string[];
32
34
  asVoice: boolean;
33
35
  audioAsVoice: boolean;
36
+ downloadMedia?: boolean;
34
37
  type?: string;
35
38
  extra?: Record<string, unknown>;
36
39
  kind?: 'tool' | 'block' | 'final';
@@ -65,6 +68,7 @@ export type ReplyMediaFileTransferParams = {
65
68
  normalizedText: string;
66
69
  asVoice: boolean;
67
70
  audioAsVoice: boolean;
71
+ downloadMedia?: boolean;
68
72
  type?: string;
69
73
  extra?: Record<string, unknown>;
70
74
  kind?: 'tool' | 'block' | 'final';
@@ -127,6 +131,7 @@ export function buildReplyTextOutboxEntry(
127
131
  kind?: 'tool' | 'block' | 'final';
128
132
  replyToId: string;
129
133
  replyTargetPolicy: OutboundReplyTargetPolicy;
134
+ extra?: Record<string, unknown>;
130
135
  },
131
136
  helpers: {
132
137
  buildTextOutboxEntry: (args: {
@@ -137,6 +142,7 @@ export function buildReplyTextOutboxEntry(
137
142
  kind?: 'tool' | 'block' | 'final';
138
143
  replyToId?: string;
139
144
  replyTargetPolicy?: OutboundReplyTargetPolicy;
145
+ extra?: Record<string, unknown>;
140
146
  }) => OutboxEntry;
141
147
  },
142
148
  ): OutboxEntry {
@@ -148,6 +154,7 @@ export function buildReplyTextOutboxEntry(
148
154
  kind: params.kind,
149
155
  replyToId: params.replyToId || undefined,
150
156
  replyTargetPolicy: params.replyTargetPolicy,
157
+ extra: params.extra,
151
158
  });
152
159
  }
153
160
 
@@ -168,10 +175,11 @@ export function enqueueReplyTextEntry(
168
175
  kind?: 'tool' | 'block' | 'final';
169
176
  replyToId?: string;
170
177
  replyTargetPolicy?: OutboundReplyTargetPolicy;
178
+ extra?: Record<string, unknown>;
171
179
  }) => OutboxEntry;
172
180
  },
173
181
  ): void {
174
- if (!params.payload.text) return;
182
+ if (!params.payload.text && !params.payload.extra) return;
175
183
 
176
184
  helpers.enqueueOutbound(
177
185
  buildReplyTextOutboxEntry(
@@ -180,6 +188,7 @@ export function enqueueReplyTextEntry(
180
188
  sessionKey: params.sessionKey,
181
189
  route: params.route,
182
190
  text: params.payload.text,
191
+ extra: params.payload.extra,
183
192
  kind: params.payload.kind,
184
193
  replyToId: params.payload.replyToId,
185
194
  replyTargetPolicy: params.payload.replyTargetPolicy,
@@ -257,12 +266,24 @@ export function normalizeReplyPayload(
257
266
  helpers: { asString: (value: unknown, fallback?: string) => string },
258
267
  options?: { replyTargetPolicy?: OutboundReplyTargetPolicy },
259
268
  ): NormalizedReplyPayload {
260
- const text = helpers.asString(payload?.text || '').trim();
269
+ const rawText = helpers.asString(payload?.text || '').trim();
270
+ // Parse [BncrParam:...] marker from reply text
271
+ const { cleanText, params: markerParams } = parseBncrMarker(rawText);
272
+ const text = cleanText;
261
273
  const mediaUrl = helpers.asString(payload?.mediaUrl || '').trim();
262
274
  const mediaUrls = Array.isArray(payload?.mediaUrls)
263
275
  ? payload.mediaUrls.map((v) => helpers.asString(v || '').trim()).filter(Boolean)
264
276
  : undefined;
265
277
  const type = helpers.asString(payload?.type || '').trim();
278
+ // Merge marker params with payload extra (marker wins), strip consumption fields
279
+ const mergedExtra: Record<string, unknown> = {
280
+ ...(payload?.extra ? { ...payload.extra } : {}),
281
+ ...markerParams,
282
+ };
283
+ const { remaining } = extractConsumptionFields(
284
+ Object.keys(mergedExtra).length > 0 ? mergedExtra : undefined,
285
+ );
286
+ const finalExtra = Object.keys(remaining).length > 0 ? remaining : undefined;
266
287
  return {
267
288
  text,
268
289
  mediaUrl,
@@ -270,8 +291,9 @@ export function normalizeReplyPayload(
270
291
  mediaList: mediaUrls?.length ? mediaUrls : mediaUrl ? [mediaUrl] : [],
271
292
  asVoice: payload?.asVoice === true,
272
293
  audioAsVoice: payload?.audioAsVoice === true,
294
+ downloadMedia: payload?.downloadMedia === true ? true : undefined,
273
295
  ...(type ? { type } : {}),
274
- ...(payload?.extra ? { extra: { ...payload.extra } } : {}),
296
+ ...(finalExtra ? { extra: finalExtra } : {}),
275
297
  kind: payload?.kind,
276
298
  replyTargetPolicy: options?.replyTargetPolicy ?? 'agent-default',
277
299
  replyToId: normalizeOutboundReplyToId({
@@ -1,5 +1,6 @@
1
1
  import { normalizeAccountId } from '../../core/accounts.ts';
2
2
  import { readOpenClawBooleanParam, readOpenClawStringParam } from '../../openclaw/sdk-helpers.ts';
3
+ import { extractConsumptionFields, parseBncrMarker } from './marker-parser.ts';
3
4
 
4
5
  export type NormalizedBncrSendParams = {
5
6
  to: string;
@@ -11,6 +12,7 @@ export type NormalizedBncrSendParams = {
11
12
  asVoice: boolean;
12
13
  audioAsVoice: boolean;
13
14
  type?: string;
15
+ downloadMedia?: boolean;
14
16
  extra?: Record<string, unknown>;
15
17
  };
16
18
 
@@ -28,8 +30,13 @@ export function normalizeBncrSendParams(input: {
28
30
  readOpenClawStringParam(paramsObj, 'accountId') ?? input.accountId,
29
31
  );
30
32
 
31
- const message = readOpenClawStringParam(paramsObj, 'message', { allowEmpty: true }) ?? '';
32
- const caption = readOpenClawStringParam(paramsObj, 'caption', { allowEmpty: true }) ?? '';
33
+ const rawMessage = readOpenClawStringParam(paramsObj, 'message', { allowEmpty: true }) ?? '';
34
+ const rawCaption = readOpenClawStringParam(paramsObj, 'caption', { allowEmpty: true }) ?? '';
35
+ // Parse [BncrParam:...] markers from message and caption text
36
+ const { cleanText: parsedMessage, params: msgMarker } = parseBncrMarker(rawMessage);
37
+ const { cleanText: parsedCaption, params: capMarker } = parseBncrMarker(rawCaption);
38
+ const message = parsedMessage;
39
+ const caption = parsedCaption;
33
40
  const mediaUrl =
34
41
  readOpenClawStringParam(paramsObj, 'media', { trim: false }) ??
35
42
  readOpenClawStringParam(paramsObj, 'path', { trim: false }) ??
@@ -53,18 +60,43 @@ export function normalizeBncrSendParams(input: {
53
60
  const audioAsVoice = readOpenClawBooleanParam(paramsObj, 'audioAsVoice') ?? false;
54
61
  const type = readOpenClawStringParam(paramsObj, 'type') || undefined;
55
62
  const rawExtra = paramsObj.extra;
56
- const extra = isPlainObject(rawExtra) ? { ...rawExtra } : undefined;
63
+ const paramsExtra = isPlainObject(rawExtra) ? { ...rawExtra } : {};
64
+ // Merge marker params into extra (marker takes priority over direct extra)
65
+ const mergedExtra = { ...paramsExtra, ...msgMarker, ...capMarker };
66
+ const extra = Object.keys(mergedExtra).length > 0 ? mergedExtra : undefined;
67
+
68
+ // Extract consumption fields from merged extra - marker-specified values override params
69
+ const { consumed, remaining } = extractConsumptionFields(
70
+ Object.keys(mergedExtra).length > 0 ? mergedExtra : undefined,
71
+ );
72
+ const effectiveAsVoice = consumed.asVoice ?? asVoice;
73
+ const effectiveAudioAsVoice = consumed.audioAsVoice ?? audioAsVoice;
74
+ const effectiveDownloadMedia = consumed.downloadMedia === true;
75
+ const effectiveType = consumed.type ?? type;
76
+ const cleanedExtra = Object.keys(remaining).length > 0 ? remaining : undefined;
57
77
 
58
78
  const hasMedia = Boolean(mediaUrl || dedupedMediaUrls?.length);
59
79
 
60
- if (asVoice && !hasMedia) throw new Error('send voice requires media path');
80
+ if (effectiveAsVoice && !hasMedia) throw new Error('send voice requires media path');
61
81
 
62
82
  const normalizedMessage = hasMedia ? '' : message || caption || '';
63
83
  const normalizedCaption = hasMedia ? caption || message || '' : '';
64
84
 
65
- if (!normalizedMessage.trim() && !normalizedCaption.trim() && !hasMedia) {
66
- throw new Error('send requires message or media');
85
+ if (!normalizedMessage.trim() && !normalizedCaption.trim() && !hasMedia && !extra) {
86
+ throw new Error('send requires message, media, or extra params');
67
87
  }
88
+ console.log(
89
+ '[bncr] normalizeBncrSendParams rawMessage=' +
90
+ JSON.stringify(rawMessage) +
91
+ '|msgMarker=' +
92
+ JSON.stringify(msgMarker) +
93
+ '|consumed=' +
94
+ JSON.stringify(consumed) +
95
+ '|effectiveDownloadMedia=' +
96
+ effectiveDownloadMedia +
97
+ '|mediaUrl=' +
98
+ JSON.stringify(mediaUrl),
99
+ );
68
100
 
69
101
  return {
70
102
  to,
@@ -73,9 +105,10 @@ export function normalizeBncrSendParams(input: {
73
105
  caption: normalizedCaption,
74
106
  mediaUrl: dedupedMediaUrls?.length ? undefined : mediaUrl || undefined,
75
107
  mediaUrls: dedupedMediaUrls,
76
- asVoice,
77
- audioAsVoice,
78
- ...(type ? { type } : {}),
79
- ...(extra ? { extra } : {}),
108
+ asVoice: effectiveAsVoice,
109
+ ...(effectiveDownloadMedia ? { downloadMedia: true } : {}),
110
+ audioAsVoice: effectiveAudioAsVoice,
111
+ ...(effectiveType ? { type: effectiveType } : {}),
112
+ ...(cleanedExtra ? { extra: cleanedExtra } : {}),
80
113
  };
81
114
  }
@@ -6,6 +6,7 @@ function normalizeReplyKind(value: unknown): 'tool' | 'block' | 'final' | undefi
6
6
 
7
7
  export async function sendBncrText(params: {
8
8
  channelId: string;
9
+ extra?: Record<string, unknown>;
9
10
  accountId: string;
10
11
  to: string;
11
12
  text: string;
@@ -26,6 +27,7 @@ export async function sendBncrText(params: {
26
27
  mediaUrl?: string;
27
28
  mediaUrls?: string[];
28
29
  kind?: 'tool' | 'block' | 'final';
30
+ extra?: Record<string, unknown>;
29
31
  replyToId?: string;
30
32
  };
31
33
  mediaLocalRoots?: readonly string[];
@@ -43,6 +45,7 @@ export async function sendBncrText(params: {
43
45
  text: params.text,
44
46
  kind: normalizeReplyKind(params.kind),
45
47
  replyToId: params.replyToId,
48
+ extra: params.extra,
46
49
  },
47
50
  mediaLocalRoots: params.mediaLocalRoots,
48
51
  });
@@ -64,6 +67,7 @@ export async function sendBncrMedia(params: {
64
67
  asVoice?: boolean;
65
68
  audioAsVoice?: boolean;
66
69
  type?: string;
70
+ downloadMedia?: boolean;
67
71
  extra?: Record<string, unknown>;
68
72
  kind?: string;
69
73
  replyToId?: string;
@@ -84,6 +88,7 @@ export async function sendBncrMedia(params: {
84
88
  asVoice?: boolean;
85
89
  audioAsVoice?: boolean;
86
90
  type?: string;
91
+ downloadMedia?: boolean;
87
92
  extra?: Record<string, unknown>;
88
93
  kind?: 'tool' | 'block' | 'final';
89
94
  replyToId?: string;
@@ -105,6 +110,7 @@ export async function sendBncrMedia(params: {
105
110
  mediaUrls: params.mediaUrls?.length ? params.mediaUrls : undefined,
106
111
  asVoice: params.asVoice === true ? true : undefined,
107
112
  audioAsVoice: params.audioAsVoice === true ? true : undefined,
113
+ downloadMedia: params.downloadMedia === true ? true : undefined,
108
114
  type: params.type,
109
115
  extra: params.extra,
110
116
  kind: normalizeReplyKind(params.kind),
@@ -19,11 +19,12 @@ export function createBncrBridgeFileTransferPushFacade(runtime: {
19
19
  route: BncrRoute;
20
20
  mediaUrl: string;
21
21
  mediaLocalRoots?: readonly string[];
22
+ downloadMedia?: boolean;
22
23
  }) => Promise<{
23
24
  mode: 'base64' | 'chunk';
24
25
  mimeType?: string;
25
26
  fileName?: string;
26
- mediaBase64?: string;
27
+ base64?: string;
27
28
  path?: string;
28
29
  }>;
29
30
  buildFileTransferOutboundFrame: (params: {
@@ -88,6 +89,7 @@ export function createBncrBridgeFileTransferPushFacade(runtime: {
88
89
  mediaLocalRoots: Array.isArray(args.meta.mediaLocalRoots)
89
90
  ? args.meta.mediaLocalRoots.filter((v): v is string => typeof v === 'string')
90
91
  : undefined,
92
+ downloadMedia: args.meta.downloadMedia === true,
91
93
  });
92
94
  const frame = runtime.buildFileTransferOutboundFrame({
93
95
  entry: args.entry,
@@ -48,7 +48,7 @@ export function createBncrBridgeMediaFacade(runtime: {
48
48
  mode: 'base64' | 'chunk';
49
49
  mimeType?: string;
50
50
  fileName?: string;
51
- mediaBase64?: string;
51
+ base64?: string;
52
52
  path?: string;
53
53
  }>;
54
54
  };
@@ -161,6 +161,7 @@ export function createBncrBridgeMediaFacade(runtime: {
161
161
  const loadOutboundTransferMedia = async (params: {
162
162
  mediaUrl: string;
163
163
  mediaLocalRoots?: readonly string[];
164
+ downloadMedia?: boolean;
164
165
  }): Promise<{
165
166
  loaded: OpenClawLoadedMedia;
166
167
  size: number;
@@ -10,6 +10,7 @@ import { normalizeBncrSendParams } from '../messaging/outbound/send-params.ts';
10
10
  import type { OpenClawChannelToolSend, openClawJsonResult } from '../openclaw/sdk-helpers.ts';
11
11
  import type {
12
12
  BncrChannelConfigRoot,
13
+ BncrSceneRecord,
13
14
  BncrStatusRuntimeSnapshot,
14
15
  BncrVerifiedTarget,
15
16
  ChannelMessageActionAdapter,
@@ -37,6 +38,7 @@ type PluginSurfaceResult = ReturnType<typeof openClawJsonResult>;
37
38
 
38
39
  export function createBncrChannelPluginSurfaceGroup(runtime: {
39
40
  channelId: string;
41
+ sceneRegistry: Map<string, BncrSceneRecord>;
40
42
  getMessageSendBridge: () => {
41
43
  channelMessageSendText: (
42
44
  ctx: Record<string, unknown>,
@@ -138,6 +140,32 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
138
140
  throw new Error(`Action ${action} is not supported for provider ${runtime.channelId}.`);
139
141
  }
140
142
  const normalized = normalizeBncrSendParams({ params, accountId: accountId || '' });
143
+ console.log(
144
+ '[bncr] handleAction normalized.downloadMedia=' +
145
+ JSON.stringify(normalized.downloadMedia) +
146
+ '|mediaUrl=' +
147
+ JSON.stringify(normalized.mediaUrl) +
148
+ '|hasMedia=' +
149
+ Boolean(normalized.mediaUrl || normalized.mediaUrls?.length),
150
+ );
151
+
152
+ // Priority: marker → scene config → global default → false
153
+ if (normalized.downloadMedia === undefined) {
154
+ const parts = normalized.to.split(':');
155
+ if (parts.length >= 3) {
156
+ const sceneKey = `${parts[1]}:${parts[2]}`;
157
+ const scene = runtime.sceneRegistry.get(sceneKey);
158
+ if (scene?.downloadMedia === true) {
159
+ normalized.downloadMedia = true;
160
+ }
161
+ if (normalized.downloadMedia === undefined) {
162
+ const globalScene = runtime.sceneRegistry.get('__global__');
163
+ if (globalScene?.downloadMedia === true) {
164
+ normalized.downloadMedia = true;
165
+ }
166
+ }
167
+ }
168
+ }
141
169
 
142
170
  const toolActionBridge = runtime.getToolActionBridge();
143
171
  const result =
@@ -152,6 +180,7 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
152
180
  asVoice: normalized.asVoice,
153
181
  audioAsVoice: normalized.audioAsVoice,
154
182
  type: normalized.type,
183
+ downloadMedia: normalized.downloadMedia,
155
184
  extra: normalized.extra,
156
185
  mediaLocalRoots,
157
186
  resolveVerifiedTarget: (to, accountId) =>
@@ -166,6 +195,7 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
166
195
  accountId: normalized.accountId,
167
196
  to: normalized.to,
168
197
  text: normalized.message,
198
+ extra: normalized.extra,
169
199
  mediaLocalRoots,
170
200
  resolveVerifiedTarget: (to, accountId) =>
171
201
  toolActionBridge.resolveVerifiedTarget(to, accountId),
@@ -333,6 +333,7 @@ export function buildBncrMediaOrchestratorsRuntime(deps: {
333
333
  sessionKey: string;
334
334
  route: BncrRoute;
335
335
  text: string;
336
+ extra?: Record<string, unknown>;
336
337
  kind?: 'tool' | 'block' | 'final';
337
338
  replyToId?: string;
338
339
  replyTargetPolicy?: OutboundReplyTargetPolicy;
@@ -351,6 +352,7 @@ export function buildBncrMediaOrchestratorsRuntime(deps: {
351
352
  kind?: 'tool' | 'block' | 'final';
352
353
  replyToId?: string;
353
354
  replyTargetPolicy?: OutboundReplyTargetPolicy;
355
+ downloadMedia?: boolean;
354
356
  }) => OutboxEntry;
355
357
  rememberRecentMediaSend: (args: {
356
358
  sessionKey: string;
@@ -63,6 +63,7 @@ export type BncrSceneRecord = {
63
63
  groupReplyMode?: BncrGroupReplyMode;
64
64
  historyLimit?: number;
65
65
  historyForce?: boolean;
66
+ downloadMedia?: boolean;
66
67
  lastSeenAt: number;
67
68
  };
68
69
 
@@ -133,6 +134,13 @@ export type BncrChannelSendContext = {
133
134
  sessionKey?: string;
134
135
  mirror?: { sessionKey?: string };
135
136
  threadId?: string;
137
+ /** Pass-through fields from host - bncr plugin does NOT consume these. */
138
+ forceDocument?: boolean;
139
+ gifPlayback?: boolean;
140
+ silent?: boolean;
141
+ downloadMedia?: boolean;
142
+ /** Extra key-value pairs from marker parsing or direct pass-through. */
143
+ extra?: Record<string, unknown>;
136
144
  };
137
145
 
138
146
  export type BncrRegisterRuntimeSnapshot = {