@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
@@ -1,121 +0,0 @@
1
- import type {
2
- ChannelMessageAdapterShape,
3
- ChannelMessageSendMediaContext,
4
- ChannelMessageSendPayloadContext,
5
- ChannelMessageSendResult,
6
- ChannelMessageSendTextContext,
7
- } from 'openclaw/plugin-sdk/channel-message';
8
- import { defineChannelMessageAdapter } from 'openclaw/plugin-sdk/channel-message';
9
- import type { OpenClawConfig } from 'openclaw/plugin-sdk/core';
10
-
11
- import {
12
- buildFileTransferOutboxEntry,
13
- buildTextOutboxEntry,
14
- } from '../../core/outbox-entry-builders.ts';
15
- import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
16
- import { buildBncrDurableQueuedResult } from './durable-queue-adapter.ts';
17
-
18
- export type BncrDurableMessageQueuedAdapterDeps = {
19
- enqueueText: (
20
- ctx: ChannelMessageSendTextContext<OpenClawConfig>,
21
- ) => Promise<OutboxEntry> | OutboxEntry;
22
- enqueueMedia?: (
23
- ctx: ChannelMessageSendMediaContext<OpenClawConfig>,
24
- ) => Promise<OutboxEntry> | OutboxEntry;
25
- enqueuePayload?: (
26
- ctx: ChannelMessageSendPayloadContext<OpenClawConfig>,
27
- ) => Promise<OutboxEntry> | OutboxEntry;
28
- now?: () => number;
29
- };
30
-
31
- export type BncrDurableMessageQueuedAdapterBuilderDeps = {
32
- createMessageId: () => string;
33
- now: () => number;
34
- normalizeAccountId: (accountId?: string | null) => string;
35
- normalizeReplyToId: (value?: string | null) => string;
36
- resolveTarget: (ctx: ChannelMessageSendTextContext<OpenClawConfig>) => {
37
- route: BncrRoute;
38
- sessionKey: string;
39
- accountId?: string | null;
40
- };
41
- filePushEvent?: string;
42
- };
43
-
44
- // This adapter intentionally models only the OpenClaw -> bncr-plugin handoff.
45
- // Once a message is accepted into bncr's own outbox, OpenClaw should stop managing it;
46
- // client/platform ACK, retry, and deadLetter remain owned by the bncr service framework.
47
- export function createBncrDurableMessageQueuedAdapter(
48
- deps: BncrDurableMessageQueuedAdapterDeps,
49
- ): ChannelMessageAdapterShape<OpenClawConfig, ChannelMessageSendResult> {
50
- return defineChannelMessageAdapter({
51
- id: 'bncr-queued-outbox',
52
- receive: {
53
- defaultAckPolicy: 'manual',
54
- supportedAckPolicies: ['manual'],
55
- },
56
- send: {
57
- text: async (ctx) => toChannelMessageSendResult(await deps.enqueueText(ctx), deps.now),
58
- media: deps.enqueueMedia
59
- ? async (ctx) => toChannelMessageSendResult(await deps.enqueueMedia?.(ctx), deps.now)
60
- : undefined,
61
- payload: deps.enqueuePayload
62
- ? async (ctx) => toChannelMessageSendResult(await deps.enqueuePayload?.(ctx), deps.now)
63
- : undefined,
64
- },
65
- });
66
- }
67
-
68
- export function createBncrDurableMessageQueuedAdapterFromBuilders(
69
- deps: BncrDurableMessageQueuedAdapterBuilderDeps,
70
- ): ChannelMessageAdapterShape<OpenClawConfig, ChannelMessageSendResult> {
71
- return createBncrDurableMessageQueuedAdapter({
72
- now: deps.now,
73
- enqueueText: (ctx) => {
74
- const resolved = deps.resolveTarget(ctx);
75
- return buildTextOutboxEntry({
76
- createMessageId: deps.createMessageId,
77
- now: deps.now,
78
- normalizeAccountId: deps.normalizeAccountId,
79
- normalizeReplyToId: deps.normalizeReplyToId,
80
- accountId: resolved.accountId ?? ctx.accountId ?? '',
81
- sessionKey: resolved.sessionKey,
82
- route: resolved.route,
83
- text: ctx.text,
84
- kind: 'final',
85
- replyToId: ctx.replyToId ?? undefined,
86
- });
87
- },
88
- enqueueMedia: (ctx) => {
89
- const resolved = deps.resolveTarget(ctx);
90
- return buildFileTransferOutboxEntry({
91
- createMessageId: deps.createMessageId,
92
- now: deps.now,
93
- normalizeAccountId: deps.normalizeAccountId,
94
- pushEvent: deps.filePushEvent ?? 'bncr.file.push',
95
- accountId: resolved.accountId ?? ctx.accountId ?? '',
96
- sessionKey: resolved.sessionKey,
97
- route: resolved.route,
98
- mediaUrl: ctx.mediaUrl,
99
- mediaLocalRoots: ctx.mediaLocalRoots,
100
- text: ctx.text,
101
- asVoice: ctx.audioAsVoice,
102
- audioAsVoice: ctx.audioAsVoice,
103
- kind: 'final',
104
- replyToId: ctx.replyToId ?? undefined,
105
- });
106
- },
107
- });
108
- }
109
-
110
- function toChannelMessageSendResult(
111
- entry: OutboxEntry | undefined,
112
- now?: () => number,
113
- ): ChannelMessageSendResult {
114
- if (!entry) throw new Error('bncr durable message adapter did not receive an outbox entry');
115
- const queued = buildBncrDurableQueuedResult({ entry, sentAt: now?.() });
116
- const receipt: ChannelMessageSendResult['receipt'] = queued.receipt;
117
- return {
118
- receipt,
119
- messageId: queued.receipt.primaryPlatformMessageId,
120
- };
121
- }
@@ -1,114 +0,0 @@
1
- import { normalizeAccountId } from '../../core/accounts.ts';
2
- import { readOpenClawBooleanParam, readOpenClawStringParam } from '../../openclaw/sdk-helpers.ts';
3
- import { extractConsumptionFields, parseBncrMarker } from './marker-parser.ts';
4
-
5
- export type NormalizedBncrSendParams = {
6
- to: string;
7
- accountId: string;
8
- message: string;
9
- caption: string;
10
- mediaUrl?: string;
11
- mediaUrls?: string[];
12
- asVoice: boolean;
13
- audioAsVoice: boolean;
14
- type?: string;
15
- downloadMedia?: boolean;
16
- extra?: Record<string, unknown>;
17
- };
18
-
19
- function isPlainObject(value: unknown): value is Record<string, unknown> {
20
- return typeof value === 'object' && value !== null && !Array.isArray(value);
21
- }
22
-
23
- export function normalizeBncrSendParams(input: {
24
- params: unknown;
25
- accountId: string;
26
- }): NormalizedBncrSendParams {
27
- const paramsObj = isPlainObject(input.params) ? input.params : {};
28
- const to = readOpenClawStringParam(paramsObj, 'to', { required: true });
29
- const resolvedAccountId = normalizeAccountId(
30
- readOpenClawStringParam(paramsObj, 'accountId') ?? input.accountId,
31
- );
32
-
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;
40
- const mediaUrl =
41
- readOpenClawStringParam(paramsObj, 'media', { trim: false }) ??
42
- readOpenClawStringParam(paramsObj, 'path', { trim: false }) ??
43
- readOpenClawStringParam(paramsObj, 'filePath', { trim: false }) ??
44
- readOpenClawStringParam(paramsObj, 'mediaUrl', { trim: false });
45
- const rawMediaUrls = paramsObj.mediaUrls;
46
- const mediaUrls = Array.isArray(rawMediaUrls)
47
- ? Array.from(
48
- new Set(rawMediaUrls.map((v) => (typeof v === 'string' ? v.trim() : '')).filter(Boolean)),
49
- )
50
- : undefined;
51
- // 如果 mediaUrl 已经在 mediaUrls 中,去重避免重复发送
52
- const dedupedMediaUrls = mediaUrls?.length
53
- ? mediaUrl && mediaUrls.includes(mediaUrl)
54
- ? mediaUrls
55
- : mediaUrl
56
- ? [mediaUrl, ...mediaUrls]
57
- : mediaUrls
58
- : undefined;
59
- const asVoice = readOpenClawBooleanParam(paramsObj, 'asVoice') ?? false;
60
- const audioAsVoice = readOpenClawBooleanParam(paramsObj, 'audioAsVoice') ?? false;
61
- const type = readOpenClawStringParam(paramsObj, 'type') || undefined;
62
- const rawExtra = paramsObj.extra;
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;
77
-
78
- const hasMedia = Boolean(mediaUrl || dedupedMediaUrls?.length);
79
-
80
- if (effectiveAsVoice && !hasMedia) throw new Error('send voice requires media path');
81
-
82
- const normalizedMessage = hasMedia ? '' : message || caption || '';
83
- const normalizedCaption = hasMedia ? caption || message || '' : '';
84
-
85
- if (!normalizedMessage.trim() && !normalizedCaption.trim() && !hasMedia && !extra) {
86
- throw new Error('send requires message, media, or extra params');
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
- );
100
-
101
- return {
102
- to,
103
- accountId: resolvedAccountId,
104
- message: normalizedMessage,
105
- caption: normalizedCaption,
106
- mediaUrl: dedupedMediaUrls?.length ? undefined : mediaUrl || undefined,
107
- mediaUrls: dedupedMediaUrls,
108
- asVoice: effectiveAsVoice,
109
- ...(effectiveDownloadMedia ? { downloadMedia: true } : {}),
110
- audioAsVoice: effectiveAudioAsVoice,
111
- ...(effectiveType ? { type: effectiveType } : {}),
112
- ...(cleanedExtra ? { extra: cleanedExtra } : {}),
113
- };
114
- }
@@ -1,127 +0,0 @@
1
- import type { BncrRoute } from '../../core/types.ts';
2
-
3
- function normalizeReplyKind(value: unknown): 'tool' | 'block' | 'final' | undefined {
4
- return value === 'tool' || value === 'block' || value === 'final' ? value : undefined;
5
- }
6
-
7
- export async function sendBncrText(params: {
8
- channelId: string;
9
- extra?: Record<string, unknown>;
10
- accountId: string;
11
- to: string;
12
- text: string;
13
- kind?: string;
14
- replyToId?: string;
15
- mediaLocalRoots?: readonly string[];
16
- resolveVerifiedTarget: (
17
- to: string,
18
- accountId: string,
19
- ) => { sessionKey: string; route: BncrRoute; displayScope: string };
20
- rememberSessionRoute: (sessionKey: string, accountId: string, route: BncrRoute) => void;
21
- enqueueFromReply: (args: {
22
- accountId: string;
23
- sessionKey: string;
24
- route: BncrRoute;
25
- payload: {
26
- text?: string;
27
- mediaUrl?: string;
28
- mediaUrls?: string[];
29
- kind?: 'tool' | 'block' | 'final';
30
- extra?: Record<string, unknown>;
31
- replyToId?: string;
32
- };
33
- mediaLocalRoots?: readonly string[];
34
- }) => Promise<void>;
35
- createMessageId: () => string;
36
- }) {
37
- const verified = params.resolveVerifiedTarget(params.to, params.accountId);
38
- params.rememberSessionRoute(verified.sessionKey, params.accountId, verified.route);
39
-
40
- await params.enqueueFromReply({
41
- accountId: params.accountId,
42
- sessionKey: verified.sessionKey,
43
- route: verified.route,
44
- payload: {
45
- text: params.text,
46
- kind: normalizeReplyKind(params.kind),
47
- replyToId: params.replyToId,
48
- extra: params.extra,
49
- },
50
- mediaLocalRoots: params.mediaLocalRoots,
51
- });
52
-
53
- return {
54
- channel: params.channelId,
55
- messageId: params.createMessageId(),
56
- chatId: verified.sessionKey,
57
- };
58
- }
59
-
60
- export async function sendBncrMedia(params: {
61
- channelId: string;
62
- accountId: string;
63
- to: string;
64
- text?: string;
65
- mediaUrl?: string;
66
- mediaUrls?: string[];
67
- asVoice?: boolean;
68
- audioAsVoice?: boolean;
69
- type?: string;
70
- downloadMedia?: boolean;
71
- extra?: Record<string, unknown>;
72
- kind?: string;
73
- replyToId?: string;
74
- mediaLocalRoots?: readonly string[];
75
- resolveVerifiedTarget: (
76
- to: string,
77
- accountId: string,
78
- ) => { sessionKey: string; route: BncrRoute; displayScope: string };
79
- rememberSessionRoute: (sessionKey: string, accountId: string, route: BncrRoute) => void;
80
- enqueueFromReply: (args: {
81
- accountId: string;
82
- sessionKey: string;
83
- route: BncrRoute;
84
- payload: {
85
- text?: string;
86
- mediaUrl?: string;
87
- mediaUrls?: string[];
88
- asVoice?: boolean;
89
- audioAsVoice?: boolean;
90
- type?: string;
91
- downloadMedia?: boolean;
92
- extra?: Record<string, unknown>;
93
- kind?: 'tool' | 'block' | 'final';
94
- replyToId?: string;
95
- };
96
- mediaLocalRoots?: readonly string[];
97
- }) => Promise<void>;
98
- createMessageId: () => string;
99
- }) {
100
- const verified = params.resolveVerifiedTarget(params.to, params.accountId);
101
- params.rememberSessionRoute(verified.sessionKey, params.accountId, verified.route);
102
-
103
- await params.enqueueFromReply({
104
- accountId: params.accountId,
105
- sessionKey: verified.sessionKey,
106
- route: verified.route,
107
- payload: {
108
- text: params.text || '',
109
- mediaUrl: params.mediaUrl || '',
110
- mediaUrls: params.mediaUrls?.length ? params.mediaUrls : undefined,
111
- asVoice: params.asVoice === true ? true : undefined,
112
- audioAsVoice: params.audioAsVoice === true ? true : undefined,
113
- downloadMedia: params.downloadMedia === true ? true : undefined,
114
- type: params.type,
115
- extra: params.extra,
116
- kind: normalizeReplyKind(params.kind),
117
- replyToId: params.replyToId,
118
- },
119
- mediaLocalRoots: params.mediaLocalRoots,
120
- });
121
-
122
- return {
123
- channel: params.channelId,
124
- messageId: params.createMessageId(),
125
- chatId: verified.sessionKey,
126
- };
127
- }
@@ -1,25 +0,0 @@
1
- import {
2
- defineStableChannelIngressIdentity as sdkDefineStableChannelIngressIdentity,
3
- resolveChannelMessageIngress as sdkResolveChannelMessageIngress,
4
- } from 'openclaw/plugin-sdk/channel-ingress-runtime';
5
-
6
- type StableChannelIngressIdentityParams = Parameters<
7
- typeof sdkDefineStableChannelIngressIdentity
8
- >[0];
9
-
10
- type ResolveChannelMessageIngressParams = Parameters<typeof sdkResolveChannelMessageIngress>[0];
11
- type ResolveChannelMessageIngressResult = Awaited<
12
- ReturnType<typeof sdkResolveChannelMessageIngress>
13
- >;
14
-
15
- export function defineOpenClawStableChannelIngressIdentity(
16
- params: StableChannelIngressIdentityParams,
17
- ) {
18
- return sdkDefineStableChannelIngressIdentity(params);
19
- }
20
-
21
- export async function resolveOpenClawChannelMessageIngress(
22
- params: ResolveChannelMessageIngressParams,
23
- ): Promise<ResolveChannelMessageIngressResult> {
24
- return sdkResolveChannelMessageIngress(params);
25
- }
@@ -1,142 +0,0 @@
1
- import {
2
- buildFileTransferPushOkArgs,
3
- buildFileTransferPushSuccessArgs,
4
- } from '../core/outbox-file-transfer-bookkeeping.ts';
5
- import {
6
- buildFileTransferBroadcastPayload,
7
- buildFileTransferRouteSelectArgs,
8
- } from '../core/outbox-file-transfer-success.ts';
9
- import type { BncrConnection, BncrRoute, OutboxEntry } from '../core/types.ts';
10
-
11
- export function createBncrBridgeFileTransferPushFacade(runtime: {
12
- pushEvent: string;
13
- getGatewayContext: () => {
14
- broadcastToConnIds: (event: string, payload: unknown, connIds: ReadonlySet<string>) => void;
15
- } | null;
16
- transferMediaToBncrClient: (params: {
17
- accountId: string;
18
- sessionKey: string;
19
- route: BncrRoute;
20
- mediaUrl: string;
21
- mediaLocalRoots?: readonly string[];
22
- downloadMedia?: boolean;
23
- }) => Promise<{
24
- mode: 'base64' | 'chunk';
25
- mimeType?: string;
26
- fileName?: string;
27
- base64?: string;
28
- path?: string;
29
- }>;
30
- buildFileTransferOutboundFrame: (params: {
31
- entry: OutboxEntry;
32
- meta: Record<string, unknown>;
33
- media: { fileName?: string; mimeType?: string; path?: string; base64?: string; type?: string };
34
- mediaUrl: string;
35
- }) => Record<string, unknown>;
36
- logOutboxRouteSelect: (args: {
37
- messageId: string;
38
- accountId: string;
39
- kind?: 'file-transfer';
40
- routeReason: string;
41
- connIds: Iterable<string>;
42
- ownerConnId: string;
43
- ownerClientId: string;
44
- recentInboundReachable: boolean;
45
- event: string;
46
- }) => void;
47
- recordOutboxPushSuccess: (args: {
48
- entry: OutboxEntry;
49
- connIds: Iterable<string>;
50
- ownerConnId?: string;
51
- ownerClientId?: string;
52
- clearLastError?: boolean;
53
- }) => void;
54
- logOutboxPushOkSummary: (messageId: string) => void;
55
- logOutboxPushOk: (args: {
56
- messageId: string;
57
- accountId: string;
58
- kind?: 'file-transfer';
59
- connIds: Iterable<string>;
60
- ownerConnId: string;
61
- ownerClientId: string;
62
- recentInboundReachable: boolean;
63
- event: string;
64
- }) => void;
65
- handleFileTransferPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
66
- handleFileTransferPushGuardFailure: (args: {
67
- entry: OutboxEntry;
68
- guard: {
69
- reason: 'media-url-missing' | 'no-gateway-context' | 'no-active-connection';
70
- lastError: string;
71
- recentInboundReachable?: boolean;
72
- };
73
- }) => void;
74
- }) {
75
- const pushFileTransferSuccessPath = async (args: {
76
- entry: OutboxEntry;
77
- meta: Record<string, unknown>;
78
- owner: BncrConnection | null;
79
- connIds: Iterable<string>;
80
- recentInboundReachable: boolean;
81
- routeReason: string;
82
- mediaUrl: string;
83
- }): Promise<void> => {
84
- const media = await runtime.transferMediaToBncrClient({
85
- accountId: args.entry.accountId,
86
- sessionKey: args.entry.sessionKey,
87
- route: args.entry.route,
88
- mediaUrl: args.mediaUrl,
89
- mediaLocalRoots: Array.isArray(args.meta.mediaLocalRoots)
90
- ? args.meta.mediaLocalRoots.filter((v): v is string => typeof v === 'string')
91
- : undefined,
92
- downloadMedia: args.meta.downloadMedia === true,
93
- });
94
- const frame = runtime.buildFileTransferOutboundFrame({
95
- entry: args.entry,
96
- meta: args.meta,
97
- media,
98
- mediaUrl: args.mediaUrl,
99
- });
100
-
101
- runtime.getGatewayContext()!.broadcastToConnIds(
102
- runtime.pushEvent,
103
- buildFileTransferBroadcastPayload({
104
- frame,
105
- messageId: args.entry.messageId,
106
- }),
107
- new Set(args.connIds),
108
- );
109
- runtime.logOutboxRouteSelect(
110
- buildFileTransferRouteSelectArgs({
111
- entry: args.entry,
112
- connIds: args.connIds,
113
- routeReason: args.routeReason,
114
- recentInboundReachable: args.recentInboundReachable,
115
- owner: args.owner,
116
- event: runtime.pushEvent,
117
- }),
118
- );
119
- runtime.recordOutboxPushSuccess(
120
- buildFileTransferPushSuccessArgs({
121
- entry: args.entry,
122
- connIds: args.connIds,
123
- owner: args.owner,
124
- }),
125
- );
126
- runtime.logOutboxPushOkSummary(args.entry.messageId);
127
- runtime.logOutboxPushOk(
128
- buildFileTransferPushOkArgs({
129
- entry: args.entry,
130
- connIds: args.connIds,
131
- recentInboundReachable: args.recentInboundReachable,
132
- event: runtime.pushEvent,
133
- }),
134
- );
135
- };
136
-
137
- return {
138
- pushFileTransferSuccessPath,
139
- handleFileTransferPushFailure: runtime.handleFileTransferPushFailure,
140
- handleFileTransferPushGuardFailure: runtime.handleFileTransferPushGuardFailure,
141
- };
142
- }
@@ -1,37 +0,0 @@
1
- import type { BncrRoute } from '../core/types.ts';
2
- import { createBncrChannelSendRuntimeComponent } from './channel-components.ts';
3
-
4
- export function createBncrChannelSendRuntimeGroup(runtime: {
5
- channelId: string;
6
- asString: (value: unknown, fallback?: string) => string;
7
- syncDebugFlag: () => Promise<void>;
8
- logInfo: (scope: string, message: string, options?: Record<string, unknown>) => void;
9
- resolveVerifiedTarget: (
10
- to: string,
11
- accountId: string,
12
- ) => {
13
- sessionKey: string;
14
- route: BncrRoute;
15
- displayScope: string;
16
- };
17
- rememberSessionRoute: (sessionKey: string, accountId: string, route: BncrRoute) => void;
18
- enqueueFromReply: Parameters<typeof createBncrChannelSendRuntimeComponent>[0]['enqueueFromReply'];
19
- listOutboxEntries: Parameters<
20
- typeof createBncrChannelSendRuntimeComponent
21
- >[0]['listOutboxEntries'];
22
- }) {
23
- const channelSendRuntime = createBncrChannelSendRuntimeComponent({
24
- channelId: runtime.channelId,
25
- asString: runtime.asString,
26
- syncDebugFlag: runtime.syncDebugFlag,
27
- logInfo: runtime.logInfo,
28
- resolveVerifiedTarget: runtime.resolveVerifiedTarget,
29
- rememberSessionRoute: runtime.rememberSessionRoute,
30
- enqueueFromReply: runtime.enqueueFromReply,
31
- listOutboxEntries: runtime.listOutboxEntries,
32
- });
33
-
34
- return {
35
- channelSendRuntime,
36
- };
37
- }
@@ -1,69 +0,0 @@
1
- import { resolveFileTransferGuard } from '../core/outbox-file-transfer-guards.ts';
2
- import { prepareFileTransferRouteSelection } from '../core/outbox-file-transfer-prep.ts';
3
- import type { BncrConnection, OutboxEntry } from '../core/types.ts';
4
- import { selectOutboxFileTransferRouteCandidates } from '../messaging/outbound/queue-selectors.ts';
5
-
6
- export async function runBncrFileTransferOutboxPush(args: {
7
- entry: OutboxEntry;
8
- meta: Record<string, unknown>;
9
- gatewayContext: unknown;
10
- owner: BncrConnection | null;
11
- resolvePushConnIds: (accountId: string) => Set<string>;
12
- resolveRecentInboundConnIds: (accountId: string) => Set<string>;
13
- hasRecentInboundReachability: (accountId: string) => boolean;
14
- isRevalidatedAttemptedConn: (connId: string) => boolean;
15
- handleFileTransferPushGuardFailure: (args: {
16
- entry: OutboxEntry;
17
- guard: Exclude<ReturnType<typeof resolveFileTransferGuard>, { ok: true }>;
18
- }) => void;
19
- pushFileTransferSuccessPath: (args: {
20
- entry: OutboxEntry;
21
- meta: Record<string, unknown>;
22
- owner: BncrConnection | null;
23
- connIds: Iterable<string>;
24
- recentInboundReachable: boolean;
25
- routeReason: string;
26
- mediaUrl: string;
27
- }) => Promise<void>;
28
- handleFileTransferPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
29
- }) {
30
- const selection = prepareFileTransferRouteSelection({
31
- entry: args.entry,
32
- owner: args.owner,
33
- resolvePushConnIds: args.resolvePushConnIds,
34
- resolveRecentInboundConnIds: args.resolveRecentInboundConnIds,
35
- hasRecentInboundReachability: args.hasRecentInboundReachability,
36
- isRevalidatedAttemptedConn: args.isRevalidatedAttemptedConn,
37
- selectOutboxFileTransferRouteCandidates,
38
- });
39
- const guard = resolveFileTransferGuard({
40
- gatewayContext: args.gatewayContext,
41
- entry: args.entry,
42
- owner: args.owner,
43
- routeSelection: selection,
44
- mediaUrl: String(args.meta.mediaUrl || '').trim(),
45
- });
46
- if (!guard.ok) {
47
- args.handleFileTransferPushGuardFailure({
48
- entry: args.entry,
49
- guard,
50
- });
51
- return false;
52
- }
53
-
54
- try {
55
- await args.pushFileTransferSuccessPath({
56
- entry: args.entry,
57
- meta: args.meta,
58
- owner: args.owner,
59
- connIds: guard.connIds,
60
- recentInboundReachable: guard.recentInboundReachable,
61
- routeReason: guard.routeReason,
62
- mediaUrl: guard.mediaUrl,
63
- });
64
- return true;
65
- } catch (error) {
66
- args.handleFileTransferPushFailure({ entry: args.entry, error });
67
- return false;
68
- }
69
- }