@xmoxmo/bncr 0.3.6 → 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 +46 -155
  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,37 +1,95 @@
1
+ import type { ChatType } from 'openclaw/plugin-sdk/core';
1
2
  import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
2
- import { formatDisplayScope, formatTargetDisplay, parseExplicitTarget } from '../core/targets.ts';
3
+ import { formatDisplayScope, parseExplicitTarget } from '../core/targets.ts';
4
+ import type { BncrRoute } from '../core/types.ts';
5
+ import { asSanitizedString } from '../core/value-sanitize.ts';
3
6
  import { resolveBncrOutboundSessionRoute } from '../messaging/outbound/session-route.ts';
4
7
  import {
5
8
  looksLikeBncrExplicitTarget,
6
9
  resolveBncrOutboundTarget,
7
10
  } from '../messaging/outbound/target-resolver.ts';
11
+ import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
8
12
 
9
13
  type BncrMessagingRuntimeBridge = {
10
14
  canonicalAgentId?: string;
11
- ensureCanonicalAgentId: (params: { cfg: any; accountId: string }) => string;
12
- resolveRouteBySession: (raw: string, accountId: string) => any;
15
+ ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) => string;
16
+ resolveRouteBySession: (raw: string, accountId: string) => BncrRoute | null;
13
17
  };
14
18
 
15
- function asString(v: unknown, fallback = ''): string {
16
- return typeof v === 'string' ? v : fallback;
19
+ type BncrMessagingTargetDisplayInput = {
20
+ target?:
21
+ | string
22
+ | {
23
+ displayScope?: string;
24
+ to?: string;
25
+ platform?: string;
26
+ groupId?: string;
27
+ userId?: string;
28
+ }
29
+ | null;
30
+ display?: string;
31
+ kind?: string;
32
+ };
33
+
34
+ type BncrMessagingExplicitTargetArgs = { raw: string };
35
+
36
+ type BncrMessagingSessionTargetArgs = {
37
+ kind?: 'group' | 'channel' | string;
38
+ id: string;
39
+ threadId?: string | null;
40
+ };
41
+
42
+ type BncrMessagingOutboundSessionRouteArgs = {
43
+ agentId: string;
44
+ target: string;
45
+ resolvedTarget?: { to?: string } | null;
46
+ currentSessionKey?: string;
47
+ replyToId?: string | null;
48
+ threadId?: string | number | null;
49
+ accountId?: string | null;
50
+ cfg: BncrChannelConfigRoot;
51
+ };
52
+
53
+ type BncrMessagingResolveTargetArgs = {
54
+ accountId?: string | null;
55
+ input?: string;
56
+ normalized?: string;
57
+ preferredKind?: string;
58
+ };
59
+
60
+ function isDisplayTarget(
61
+ value: unknown,
62
+ ): value is Exclude<NonNullable<BncrMessagingTargetDisplayInput['target']>, string> {
63
+ return Boolean(value && typeof value === 'object');
17
64
  }
18
65
 
19
66
  export function normalizeBncrMessagingTarget(raw: string) {
20
- const input = asString(raw).trim();
67
+ const input = asSanitizedString(raw).trim();
21
68
  return input || undefined;
22
69
  }
23
70
 
24
- export function formatBncrMessagingTargetDisplay({ target }: any) {
25
- return formatTargetDisplay(target);
71
+ export function formatBncrMessagingTargetDisplay({ target }: BncrMessagingTargetDisplayInput) {
72
+ if (typeof target === 'string') return asSanitizedString(target).trim();
73
+ if (!isDisplayTarget(target)) return '';
74
+ const displayScope = asSanitizedString(target?.displayScope || target?.to).trim();
75
+ if (displayScope) return displayScope;
76
+ if (target.platform || target.groupId || target.userId) {
77
+ return formatDisplayScope({
78
+ platform: asSanitizedString(target.platform).trim(),
79
+ groupId: asSanitizedString(target.groupId).trim(),
80
+ userId: asSanitizedString(target.userId).trim(),
81
+ });
82
+ }
83
+ return '';
26
84
  }
27
85
 
28
86
  function resolveMessagingAccountId(accountId: unknown) {
29
- return normalizeAccountId(asString(accountId || BNCR_DEFAULT_ACCOUNT_ID));
87
+ return normalizeAccountId(asSanitizedString(accountId || BNCR_DEFAULT_ACCOUNT_ID));
30
88
  }
31
89
 
32
90
  function resolveMessagingCanonicalAgentId(
33
91
  runtimeBridge: BncrMessagingRuntimeBridge,
34
- cfg: any,
92
+ cfg: BncrChannelConfigRoot,
35
93
  accountId: string,
36
94
  ) {
37
95
  return runtimeBridge.canonicalAgentId || runtimeBridge.ensureCanonicalAgentId({ cfg, accountId });
@@ -40,38 +98,33 @@ function resolveMessagingCanonicalAgentId(
40
98
  export function createBncrMessagingExplicitTargetParser(
41
99
  getBridge: () => BncrMessagingRuntimeBridge,
42
100
  ) {
43
- return ({ raw, accountId, cfg }: any) => {
44
- const resolvedAccountId = resolveMessagingAccountId(accountId);
101
+ return ({ raw }: BncrMessagingExplicitTargetArgs) => {
45
102
  const runtimeBridge = getBridge();
46
- const canonicalAgentId = resolveMessagingCanonicalAgentId(
47
- runtimeBridge,
48
- cfg,
49
- resolvedAccountId,
50
- );
51
- return parseExplicitTarget(asString(raw).trim(), { canonicalAgentId });
103
+ const canonicalAgentId = runtimeBridge.canonicalAgentId || 'main';
104
+ const parsed = parseExplicitTarget(asSanitizedString(raw).trim(), { canonicalAgentId });
105
+ if (!parsed) return null;
106
+ const chatType: ChatType = parsed.route?.groupId ? 'group' : 'direct';
107
+ return {
108
+ to: parsed.displayScope,
109
+ displayScope: parsed.displayScope,
110
+ threadId: undefined,
111
+ chatType,
112
+ };
52
113
  };
53
114
  }
54
115
 
55
116
  export function createBncrMessagingSessionTargetResolver(
56
117
  getBridge: () => BncrMessagingRuntimeBridge,
57
118
  ) {
58
- return ({ id, accountId, cfg }: any) => {
59
- const raw = asString(id).trim();
119
+ return ({ id }: BncrMessagingSessionTargetArgs) => {
120
+ const raw = asSanitizedString(id).trim();
60
121
  if (!raw) return undefined;
61
- const resolvedAccountId = resolveMessagingAccountId(accountId);
62
122
  const runtimeBridge = getBridge();
63
- const canonicalAgentId = resolveMessagingCanonicalAgentId(
64
- runtimeBridge,
65
- cfg,
66
- resolvedAccountId,
67
- );
123
+ const canonicalAgentId = runtimeBridge.canonicalAgentId || 'main';
68
124
 
69
- let parsed = parseExplicitTarget(raw, { canonicalAgentId });
125
+ const parsed = parseExplicitTarget(raw, { canonicalAgentId });
70
126
  if (!parsed) {
71
- const route = runtimeBridge.resolveRouteBySession(raw, resolvedAccountId);
72
- if (route) {
73
- parsed = parseExplicitTarget(formatDisplayScope(route), { canonicalAgentId });
74
- }
127
+ return raw || undefined;
75
128
  }
76
129
  return parsed?.displayScope || undefined;
77
130
  };
@@ -80,7 +133,7 @@ export function createBncrMessagingSessionTargetResolver(
80
133
  export function createBncrMessagingOutboundSessionRouteResolver(
81
134
  getBridge: () => BncrMessagingRuntimeBridge,
82
135
  ) {
83
- return (params: any) => {
136
+ return (params: BncrMessagingOutboundSessionRouteArgs) => {
84
137
  const accountId = resolveMessagingAccountId(params?.accountId);
85
138
  const runtimeBridge = getBridge();
86
139
  const canonicalAgentId = resolveMessagingCanonicalAgentId(
@@ -89,7 +142,16 @@ export function createBncrMessagingOutboundSessionRouteResolver(
89
142
  accountId,
90
143
  );
91
144
  return resolveBncrOutboundSessionRoute({
92
- ...params,
145
+ channel: 'bncr',
146
+ cfg: params.cfg,
147
+ agentId: params.agentId,
148
+ accountId: params.accountId ?? undefined,
149
+ target: params.target,
150
+ resolvedTarget: params.resolvedTarget,
151
+ threadId:
152
+ params.threadId === null || params.threadId === undefined
153
+ ? undefined
154
+ : asSanitizedString(params.threadId),
93
155
  canonicalAgentId,
94
156
  resolveRouteBySession: (raw: string, acc: string) =>
95
157
  runtimeBridge.resolveRouteBySession(raw, acc),
@@ -112,12 +174,12 @@ export function createBncrMessagingSurface(getBridge: () => BncrMessagingRuntime
112
174
  export function createBncrMessagingTargetResolver(getBridge: () => BncrMessagingRuntimeBridge) {
113
175
  return {
114
176
  looksLikeId: (raw: string, normalized?: string) => {
115
- return looksLikeBncrExplicitTarget(asString(normalized || raw).trim());
177
+ return looksLikeBncrExplicitTarget(asSanitizedString(normalized || raw).trim());
116
178
  },
117
- resolveTarget: async ({ accountId, input, normalized }: any) => {
179
+ resolveTarget: async ({ accountId, input, normalized }: BncrMessagingResolveTargetArgs) => {
118
180
  const runtimeBridge = getBridge();
119
181
  const resolved = resolveBncrOutboundTarget({
120
- target: asString(normalized || input).trim(),
182
+ target: asSanitizedString(normalized || input).trim(),
121
183
  accountId: resolveMessagingAccountId(accountId),
122
184
  resolveRouteBySession: (raw: string, acc: string) =>
123
185
  runtimeBridge.resolveRouteBySession(raw, acc),
@@ -10,17 +10,59 @@ function asString(v: unknown, fallback = ''): string {
10
10
  return typeof v === 'string' ? v : v == null ? fallback : String(v);
11
11
  }
12
12
 
13
+ type BncrOutboundDeliveryResult = { channel: string; messageId: string; chatId: string };
14
+
15
+ type BncrOutboundSendContext = {
16
+ accountId?: string | null;
17
+ to?: string;
18
+ text?: string;
19
+ mediaUrl?: string;
20
+ type?: string;
21
+ kind?: string;
22
+ replyToId?: string | null;
23
+ replyToMessageId?: string | null;
24
+ asVoice?: boolean;
25
+ audioAsVoice?: boolean;
26
+ mediaLocalRoots?: readonly string[];
27
+ };
28
+
29
+ type BncrOutboundReplyActionContext = {
30
+ accountId?: string | null;
31
+ to?: string;
32
+ text?: string;
33
+ replyToId?: string | null;
34
+ replyToMessageId?: string | null;
35
+ };
36
+
37
+ type BncrOutboundTargetActionContext = {
38
+ accountId?: string | null;
39
+ messageId?: string | null;
40
+ targetMessageId?: string | null;
41
+ };
42
+
43
+ type BncrOutboundReactActionContext = BncrOutboundTargetActionContext & {
44
+ emoji?: string | null;
45
+ };
46
+
47
+ type BncrOutboundEditActionContext = BncrOutboundTargetActionContext & {
48
+ text?: string;
49
+ };
50
+
13
51
  export type BncrOutboundBridge = {
14
- channelSendText: (ctx: any) => unknown | Promise<unknown>;
15
- channelSendMedia: (ctx: any) => unknown | Promise<unknown>;
52
+ channelSendText: (
53
+ ctx: BncrOutboundSendContext,
54
+ ) => BncrOutboundDeliveryResult | Promise<BncrOutboundDeliveryResult>;
55
+ channelSendMedia: (
56
+ ctx: BncrOutboundSendContext,
57
+ ) => BncrOutboundDeliveryResult | Promise<BncrOutboundDeliveryResult>;
16
58
  };
17
59
 
18
60
  export function createBncrOutboundRuntime(getBridge: () => BncrOutboundBridge) {
19
61
  return {
20
62
  deliveryMode: 'gateway' as const,
21
- sendText: async (ctx: any) => getBridge().channelSendText(ctx),
22
- sendMedia: async (ctx: any) => getBridge().channelSendMedia(ctx),
23
- replyAction: async (ctx: any) =>
63
+ sendText: async (ctx: BncrOutboundSendContext) => getBridge().channelSendText(ctx),
64
+ sendMedia: async (ctx: BncrOutboundSendContext) => getBridge().channelSendMedia(ctx),
65
+ replyAction: async (ctx: BncrOutboundReplyActionContext) =>
24
66
  sendBncrReplyAction({
25
67
  accountId: normalizeAccountId(ctx?.accountId),
26
68
  to: asString(ctx?.to || '').trim(),
@@ -30,18 +72,18 @@ export function createBncrOutboundRuntime(getBridge: () => BncrOutboundBridge) {
30
72
  sendText: async ({ accountId, to, text }) =>
31
73
  getBridge().channelSendText({ accountId, to, text }),
32
74
  }),
33
- deleteAction: async (ctx: any) =>
75
+ deleteAction: async (ctx: BncrOutboundTargetActionContext) =>
34
76
  deleteBncrMessageAction({
35
77
  accountId: normalizeAccountId(ctx?.accountId),
36
78
  targetMessageId: asString(ctx?.messageId || ctx?.targetMessageId || '').trim(),
37
79
  }),
38
- reactAction: async (ctx: any) =>
80
+ reactAction: async (ctx: BncrOutboundReactActionContext) =>
39
81
  reactBncrMessageAction({
40
82
  accountId: normalizeAccountId(ctx?.accountId),
41
83
  targetMessageId: asString(ctx?.messageId || ctx?.targetMessageId || '').trim(),
42
84
  emoji: asString(ctx?.emoji || '').trim(),
43
85
  }),
44
- editAction: async (ctx: any) =>
86
+ editAction: async (ctx: BncrOutboundEditActionContext) =>
45
87
  editBncrMessageAction({
46
88
  accountId: normalizeAccountId(ctx?.accountId),
47
89
  targetMessageId: asString(ctx?.messageId || ctx?.targetMessageId || '').trim(),
@@ -0,0 +1,136 @@
1
+ import type { BncrRoute, OutboxEntry } from '../core/types.ts';
2
+ import {
3
+ buildOutboxAckDebugInfo,
4
+ buildOutboxScheduleDebugInfo,
5
+ buildRetryRerouteDebugInfo,
6
+ } from '../messaging/outbound/diagnostics.ts';
7
+ import { computeOutboxRetryWait } from '../messaging/outbound/queue-selectors.ts';
8
+ import { OUTBOUND_SCHEDULE_SOURCE } from '../messaging/outbound/reasons.ts';
9
+ import type { RetryRerouteDecision } from '../messaging/outbound/retry-policy.ts';
10
+
11
+ export type BncrOutboxAckLogsRuntime = {
12
+ bridgeId: string;
13
+ pushEvent: string;
14
+ now: () => number;
15
+ outboxSize: () => number;
16
+ adaptiveAckTimeoutEnabled: boolean;
17
+ formatDisplayScope: (route: BncrRoute) => string;
18
+ isFileTransferEntry: (entry: OutboxEntry) => boolean;
19
+ logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
20
+ };
21
+
22
+ export function createBncrOutboxAckLogs(runtime: BncrOutboxAckLogsRuntime) {
23
+ const logOutboxAckSummary = (
24
+ scope:
25
+ | 'outbox ack ok'
26
+ | 'outbox ack ok late'
27
+ | 'outbox ack retry'
28
+ | 'outbox ack timeout'
29
+ | 'outbox ack fatal',
30
+ args: {
31
+ messageId: string;
32
+ connId?: string;
33
+ clientId?: string;
34
+ err?: string;
35
+ queueMs?: number | null;
36
+ pushMs?: number | null;
37
+ waitMs?: number | null;
38
+ },
39
+ ) => {
40
+ const parts = [`mid=${args.messageId}`, `q=${runtime.outboxSize()}`];
41
+ if (typeof args.queueMs === 'number') parts.push(`queueMs=${args.queueMs}`);
42
+ if (typeof args.pushMs === 'number') parts.push(`pushMs=${args.pushMs}`);
43
+ if (typeof args.waitMs === 'number') parts.push(`waitMs=${args.waitMs}`);
44
+ if (args.err) parts.push(`err=${args.err}`);
45
+ runtime.logInfo(scope, parts.join('|'));
46
+ };
47
+
48
+ const logOutboxAckWait = (args: {
49
+ entry: OutboxEntry;
50
+ requireAck: boolean;
51
+ ackResult: 'acked' | 'timeout';
52
+ onlineNow: boolean;
53
+ recentInboundReachable: boolean;
54
+ ackTimeoutMs?: number | null;
55
+ }) => {
56
+ runtime.logInfo(
57
+ 'outbox',
58
+ `ack ${JSON.stringify(
59
+ buildOutboxAckDebugInfo({
60
+ messageId: args.entry.messageId,
61
+ accountId: args.entry.accountId,
62
+ sessionKey: args.entry.sessionKey,
63
+ to: runtime.formatDisplayScope(args.entry.route),
64
+ kind: runtime.isFileTransferEntry(args.entry) ? 'file-transfer' : undefined,
65
+ requireAck: args.requireAck,
66
+ ackResult: args.ackResult,
67
+ ackStage: 'message',
68
+ ackOutcome: args.ackResult,
69
+ reason: args.ackResult === 'timeout' ? 'push-ack-timeout' : 'message-acked',
70
+ ackTimeoutMs: typeof args.ackTimeoutMs === 'number' ? args.ackTimeoutMs : undefined,
71
+ adaptiveAckTimeoutEnabled: runtime.adaptiveAckTimeoutEnabled,
72
+ onlineNow: args.onlineNow,
73
+ recentInboundReachable: args.recentInboundReachable,
74
+ connIds: args.entry.lastPushConnId ? [args.entry.lastPushConnId] : [],
75
+ ownerConnId: args.entry.lastPushConnId,
76
+ ownerClientId: args.entry.lastPushClientId,
77
+ event: runtime.pushEvent,
78
+ }),
79
+ )}`,
80
+ { debugOnly: true },
81
+ );
82
+ };
83
+
84
+ const logOutboxAckReroute = (args: {
85
+ accountId: string;
86
+ entry: OutboxEntry;
87
+ requireAck: boolean;
88
+ currentConnId: string;
89
+ availableConnIds: string[];
90
+ decision: Extract<RetryRerouteDecision, { kind: 'retry' }>;
91
+ localNextDelay: number | null;
92
+ ackTimeoutMs?: number | null;
93
+ }) => {
94
+ logOutboxAckSummary(args.requireAck ? 'outbox ack timeout' : 'outbox ack retry', {
95
+ messageId: args.entry.messageId,
96
+ connId: args.entry.lastPushConnId,
97
+ clientId: args.entry.lastPushClientId,
98
+ err: args.requireAck ? undefined : args.entry.lastError,
99
+ waitMs: args.requireAck ? args.ackTimeoutMs : undefined,
100
+ });
101
+ runtime.logInfo(
102
+ 'outbox',
103
+ `retry-reroute ${JSON.stringify(
104
+ buildRetryRerouteDebugInfo({
105
+ messageId: args.entry.messageId,
106
+ accountId: args.accountId,
107
+ currentConnId: args.currentConnId,
108
+ decision: args.decision,
109
+ availableConnIds: args.availableConnIds,
110
+ }),
111
+ )}`,
112
+ { debugOnly: true },
113
+ );
114
+
115
+ runtime.logInfo(
116
+ 'outbox',
117
+ `schedule ${JSON.stringify(
118
+ buildOutboxScheduleDebugInfo({
119
+ bridgeId: runtime.bridgeId,
120
+ accountId: args.accountId,
121
+ messageId: args.entry.messageId,
122
+ source: OUTBOUND_SCHEDULE_SOURCE.RETRY_REROUTE_WAIT,
123
+ wait: computeOutboxRetryWait(args.decision.nextAttemptAt, runtime.now()),
124
+ localNextDelay: args.localNextDelay,
125
+ }),
126
+ )}`,
127
+ { debugOnly: true },
128
+ );
129
+ };
130
+
131
+ return {
132
+ logOutboxAckSummary,
133
+ logOutboxAckWait,
134
+ logOutboxAckReroute,
135
+ };
136
+ }
@@ -0,0 +1,128 @@
1
+ import type { OutboxEntry } from '../core/types.ts';
2
+ import {
3
+ buildBncrAckOkTelemetryPatch,
4
+ buildBncrAckRetryEntryPatch,
5
+ } from '../runtime/outbox-transitions.ts';
6
+
7
+ export type BncrOutboxAckOkTelemetryPatch = ReturnType<typeof buildBncrAckOkTelemetryPatch>;
8
+
9
+ export type BncrOutboxAckOutcomeRuntime = {
10
+ now: () => number;
11
+ defaultAckTimeoutMs: number;
12
+ markOutboundCapability: (args: {
13
+ accountId: string;
14
+ connId: string;
15
+ clientId?: string;
16
+ outboundReady: boolean;
17
+ preferredForOutbound: boolean;
18
+ }) => void;
19
+ recordAckOkTelemetry: (args: {
20
+ accountId: string;
21
+ entry: OutboxEntry;
22
+ telemetryPatch: BncrOutboxAckOkTelemetryPatch;
23
+ }) => void;
24
+ deleteOutboxEntry: (messageId: string) => void;
25
+ setOutboxEntry: (messageId: string, entry: OutboxEntry) => void;
26
+ scheduleSave: () => void;
27
+ resolveMessageAck: (messageId: string, result: 'acked' | 'timeout') => void;
28
+ moveToDeadLetter: (entry: OutboxEntry, reason: string) => void;
29
+ logOutboxAckSummary: (
30
+ scope: 'outbox ack ok' | 'outbox ack ok late' | 'outbox ack retry' | 'outbox ack fatal',
31
+ args: {
32
+ messageId: string;
33
+ connId?: string;
34
+ clientId?: string;
35
+ err?: string;
36
+ queueMs?: number | null;
37
+ pushMs?: number | null;
38
+ waitMs?: number | null;
39
+ },
40
+ ) => void;
41
+ };
42
+
43
+ export function createBncrOutboxAckOutcome(runtime: BncrOutboxAckOutcomeRuntime) {
44
+ const handleAckOk = (args: {
45
+ accountId: string;
46
+ messageId: string;
47
+ connId: string;
48
+ clientId?: string;
49
+ stale: boolean;
50
+ entry: OutboxEntry;
51
+ }) => {
52
+ runtime.markOutboundCapability({
53
+ accountId: args.accountId,
54
+ connId: args.connId,
55
+ clientId: args.clientId,
56
+ outboundReady: true,
57
+ preferredForOutbound: true,
58
+ });
59
+ const telemetryPatch = buildBncrAckOkTelemetryPatch({
60
+ entry: args.entry,
61
+ ackAt: runtime.now(),
62
+ defaultAckTimeoutMs: runtime.defaultAckTimeoutMs,
63
+ });
64
+ runtime.recordAckOkTelemetry({
65
+ accountId: args.accountId,
66
+ entry: args.entry,
67
+ telemetryPatch,
68
+ });
69
+ runtime.deleteOutboxEntry(args.messageId);
70
+ runtime.scheduleSave();
71
+ runtime.resolveMessageAck(args.messageId, 'acked');
72
+ runtime.logOutboxAckSummary(
73
+ telemetryPatch.lateAccepted ? 'outbox ack ok late' : 'outbox ack ok',
74
+ {
75
+ messageId: args.messageId,
76
+ connId: args.connId,
77
+ clientId: args.clientId,
78
+ queueMs: telemetryPatch.ackQueueLatencyMs,
79
+ pushMs: telemetryPatch.ackPushLatencyMs,
80
+ err: telemetryPatch.lateAccepted ? 'accepted-after-timeout' : undefined,
81
+ },
82
+ );
83
+ };
84
+
85
+ const handleAckFatal = (args: {
86
+ entry: OutboxEntry;
87
+ messageId: string;
88
+ connId: string;
89
+ clientId?: string;
90
+ error: string;
91
+ }) => {
92
+ runtime.moveToDeadLetter(args.entry, args.error);
93
+ runtime.logOutboxAckSummary('outbox ack fatal', {
94
+ messageId: args.messageId,
95
+ connId: args.connId,
96
+ clientId: args.clientId,
97
+ err: args.error,
98
+ });
99
+ };
100
+
101
+ const handleAckRetry = (args: {
102
+ entry: OutboxEntry;
103
+ messageId: string;
104
+ connId: string;
105
+ clientId?: string;
106
+ error: string;
107
+ }) => {
108
+ const nextEntry = buildBncrAckRetryEntryPatch({
109
+ entry: args.entry,
110
+ error: args.error,
111
+ nextAttemptAt: runtime.now() + 1_000,
112
+ });
113
+ runtime.setOutboxEntry(args.messageId, nextEntry);
114
+ runtime.scheduleSave();
115
+ runtime.logOutboxAckSummary('outbox ack retry', {
116
+ messageId: args.messageId,
117
+ connId: args.connId,
118
+ clientId: args.clientId,
119
+ err: nextEntry.lastError,
120
+ });
121
+ };
122
+
123
+ return {
124
+ handleAckOk,
125
+ handleAckFatal,
126
+ handleAckRetry,
127
+ };
128
+ }