@xmoxmo/bncr 0.3.6 → 0.3.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.
- package/README.md +5 -0
- package/dist/index.js +28 -5
- package/index.ts +55 -721
- package/openclaw.plugin.json +1 -0
- package/package.json +8 -4
- package/scripts/check-pack.mjs +93 -18
- package/scripts/check-register-drift.mjs +35 -13
- package/scripts/selfcheck.mjs +80 -11
- package/src/bootstrap/channel-plugin-runtime.ts +81 -0
- package/src/bootstrap/cli.ts +97 -0
- package/src/bootstrap/register-runtime-gateway.ts +129 -0
- package/src/bootstrap/register-runtime-helpers.ts +140 -0
- package/src/bootstrap/register-runtime-singleton.ts +137 -0
- package/src/bootstrap/register-runtime.ts +201 -0
- package/src/bootstrap/runtime-discovery.ts +187 -0
- package/src/bootstrap/runtime-loader.ts +54 -0
- package/src/channel.ts +1590 -4967
- package/src/core/accounts.ts +23 -4
- package/src/core/dead-letter-diagnostics.ts +37 -5
- package/src/core/diagnostics.ts +31 -15
- package/src/core/downlink-health.ts +3 -11
- package/src/core/extended-diagnostics.ts +78 -36
- package/src/core/file-transfer-payloads.ts +1 -1
- package/src/core/logging.ts +1 -0
- package/src/core/outbox-enqueue.ts +13 -2
- package/src/core/outbox-entry-builders.ts +2 -0
- package/src/core/outbox-summary.ts +75 -3
- package/src/core/permissions.ts +15 -2
- package/src/core/persisted-outbox-entry.ts +21 -6
- package/src/core/policy.ts +45 -4
- package/src/core/probe.ts +3 -15
- package/src/core/register-trace.ts +3 -3
- package/src/core/status.ts +43 -4
- package/src/core/targets.ts +216 -205
- package/src/core/types.ts +221 -0
- package/src/core/value-sanitize.ts +29 -0
- package/src/messaging/inbound/commands.ts +147 -172
- package/src/messaging/inbound/context-facts.ts +4 -2
- package/src/messaging/inbound/contracts.ts +70 -0
- package/src/messaging/inbound/dispatch-prep.ts +303 -0
- package/src/messaging/inbound/dispatch.ts +49 -462
- package/src/messaging/inbound/gate.ts +18 -5
- package/src/messaging/inbound/last-route.ts +10 -4
- package/src/messaging/inbound/media-url-download.ts +109 -0
- package/src/messaging/inbound/native-command-runtime.ts +225 -0
- package/src/messaging/inbound/parse.ts +2 -1
- package/src/messaging/inbound/remote-media.ts +49 -0
- package/src/messaging/inbound/reply-config.ts +16 -4
- package/src/messaging/inbound/reply-dispatch.ts +162 -0
- package/src/messaging/inbound/runtime-compat.ts +31 -10
- package/src/messaging/inbound/session-label.ts +15 -7
- package/src/messaging/inbound/turn-context.ts +131 -0
- package/src/messaging/outbound/actions.ts +24 -10
- package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
- package/src/messaging/outbound/diagnostics.ts +31 -355
- package/src/messaging/outbound/durable-message-adapter.ts +20 -16
- package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
- package/src/messaging/outbound/media.ts +24 -13
- package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
- package/src/messaging/outbound/reply-enqueue.ts +46 -155
- package/src/messaging/outbound/send-params.ts +3 -0
- package/src/messaging/outbound/send.ts +19 -10
- package/src/messaging/outbound/session-route.ts +18 -3
- package/src/openclaw/channel-runtime-contracts.ts +76 -0
- package/src/openclaw/config-runtime.ts +13 -7
- package/src/openclaw/inbound-session-runtime.ts +7 -3
- package/src/openclaw/ingress-runtime.ts +17 -27
- package/src/openclaw/reply-runtime.ts +54 -59
- package/src/openclaw/routing-runtime.ts +35 -18
- package/src/openclaw/runtime-surface.ts +156 -12
- package/src/openclaw/sdk-helpers.ts +8 -1
- package/src/openclaw/session-route-runtime.ts +12 -12
- package/src/plugin/ack-outbox-runtime-group.ts +264 -0
- package/src/plugin/bridge-ack-facade.ts +137 -0
- package/src/plugin/bridge-connection-facade.ts +111 -0
- package/src/plugin/bridge-diagnostics-facade.ts +23 -0
- package/src/plugin/bridge-drain-facade.ts +98 -0
- package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
- package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
- package/src/plugin/bridge-lifecycle.ts +156 -0
- package/src/plugin/bridge-media-facade.ts +241 -0
- package/src/plugin/bridge-outbox-facade.ts +182 -0
- package/src/plugin/bridge-runtime-helpers.ts +266 -0
- package/src/plugin/bridge-runtime-snapshots.ts +104 -0
- package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
- package/src/plugin/bridge-status-facade.ts +76 -0
- package/src/plugin/bridge-status-worker-facade.ts +72 -0
- package/src/plugin/bridge-support-runtime.ts +137 -0
- package/src/plugin/bridge-surface-handlers-group.ts +242 -0
- package/src/plugin/bridge-surface-helpers.ts +28 -0
- package/src/plugin/capabilities.ts +1 -3
- package/src/plugin/channel-components.ts +289 -0
- package/src/plugin/channel-inbound-helpers.ts +149 -0
- package/src/plugin/channel-plugin-bridge-group.ts +129 -0
- package/src/plugin/channel-plugin-surface-group.ts +202 -0
- package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
- package/src/plugin/channel-runtime-builders-status.ts +331 -0
- package/src/plugin/channel-runtime-builders.ts +25 -0
- package/src/plugin/channel-runtime-constants.ts +40 -0
- package/src/plugin/channel-runtime-types.ts +146 -0
- package/src/plugin/channel-send-runtime-group.ts +37 -0
- package/src/plugin/channel-send.ts +226 -0
- package/src/plugin/channel-utils.ts +102 -0
- package/src/plugin/config.ts +24 -3
- package/src/plugin/connection-handlers-helpers.ts +254 -0
- package/src/plugin/connection-handlers.ts +440 -0
- package/src/plugin/connection-state-helpers.ts +159 -0
- package/src/plugin/connection-state-runtime-group.ts +51 -0
- package/src/plugin/connection-state.ts +527 -0
- package/src/plugin/diagnostics-handlers.ts +211 -0
- package/src/plugin/error-message.ts +15 -0
- package/src/plugin/file-ack-runtime.ts +284 -0
- package/src/plugin/file-inbound-abort.ts +112 -0
- package/src/plugin/file-inbound-chunk.ts +146 -0
- package/src/plugin/file-inbound-complete.ts +153 -0
- package/src/plugin/file-inbound-handlers.ts +19 -0
- package/src/plugin/file-inbound-init.ts +122 -0
- package/src/plugin/file-inbound-runtime.ts +51 -0
- package/src/plugin/file-inbound-state.ts +62 -0
- package/src/plugin/file-transfer-logs.ts +227 -0
- package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
- package/src/plugin/file-transfer-orchestrator.ts +304 -0
- package/src/plugin/file-transfer-runtime-group.ts +102 -0
- package/src/plugin/file-transfer-send.ts +89 -0
- package/src/plugin/file-transfer-setup.ts +206 -0
- package/src/plugin/gateway-event-context.ts +41 -0
- package/src/plugin/gateway-runtime.ts +17 -4
- package/src/plugin/inbound-acceptance.ts +107 -0
- package/src/plugin/inbound-handlers.ts +248 -0
- package/src/plugin/inbound-surface-handlers-group.ts +152 -0
- package/src/plugin/media-dedupe-runtime.ts +90 -0
- package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
- package/src/plugin/message-ack-runtime.ts +284 -0
- package/src/plugin/message-send.ts +16 -6
- package/src/plugin/messaging.ts +98 -36
- package/src/plugin/outbound.ts +50 -8
- package/src/plugin/outbox-ack-logs.ts +136 -0
- package/src/plugin/outbox-ack-outcome.ts +128 -0
- package/src/plugin/outbox-drain-ack.ts +145 -0
- package/src/plugin/outbox-drain-failure.ts +84 -0
- package/src/plugin/outbox-drain-loop.ts +554 -0
- package/src/plugin/outbox-drain-post-push.ts +159 -0
- package/src/plugin/outbox-drain-runtime.ts +141 -0
- package/src/plugin/outbox-drain-schedule.ts +116 -0
- package/src/plugin/outbox-file-push-flow.ts +69 -0
- package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
- package/src/plugin/outbox-push.ts +267 -0
- package/src/plugin/outbox-route.ts +181 -0
- package/src/plugin/outbox-text-push-flow.ts +90 -0
- package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
- package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
- package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
- package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
- package/src/plugin/setup.ts +33 -6
- package/src/plugin/state-store.ts +249 -0
- package/src/plugin/state-transient-runtime-group.ts +105 -0
- package/src/plugin/status-runtime.ts +251 -0
- package/src/plugin/status.ts +33 -7
- package/src/plugin/target-runtime.ts +141 -0
- package/src/plugin/target-status-runtime-group.ts +130 -0
- package/src/plugin/transient-state-runtime.ts +82 -0
- package/src/runtime/outbound-ack-timeout.ts +5 -3
- package/src/runtime/outbound-flags.ts +24 -8
- package/src/runtime/status-snapshots.ts +36 -7
- package/src/runtime/status-worker.ts +34 -4
package/src/plugin/messaging.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
12
|
-
resolveRouteBySession: (raw: string, accountId: string) =>
|
|
15
|
+
ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) => string;
|
|
16
|
+
resolveRouteBySession: (raw: string, accountId: string) => BncrRoute | null;
|
|
13
17
|
};
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
67
|
+
const input = asSanitizedString(raw).trim();
|
|
21
68
|
return input || undefined;
|
|
22
69
|
}
|
|
23
70
|
|
|
24
|
-
export function formatBncrMessagingTargetDisplay({ target }:
|
|
25
|
-
return
|
|
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(
|
|
87
|
+
return normalizeAccountId(asSanitizedString(accountId || BNCR_DEFAULT_ACCOUNT_ID));
|
|
30
88
|
}
|
|
31
89
|
|
|
32
90
|
function resolveMessagingCanonicalAgentId(
|
|
33
91
|
runtimeBridge: BncrMessagingRuntimeBridge,
|
|
34
|
-
cfg:
|
|
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
|
|
44
|
-
const resolvedAccountId = resolveMessagingAccountId(accountId);
|
|
101
|
+
return ({ raw }: BncrMessagingExplicitTargetArgs) => {
|
|
45
102
|
const runtimeBridge = getBridge();
|
|
46
|
-
const canonicalAgentId =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
59
|
-
const raw =
|
|
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 =
|
|
64
|
-
runtimeBridge,
|
|
65
|
-
cfg,
|
|
66
|
-
resolvedAccountId,
|
|
67
|
-
);
|
|
123
|
+
const canonicalAgentId = runtimeBridge.canonicalAgentId || 'main';
|
|
68
124
|
|
|
69
|
-
|
|
125
|
+
const parsed = parseExplicitTarget(raw, { canonicalAgentId });
|
|
70
126
|
if (!parsed) {
|
|
71
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
177
|
+
return looksLikeBncrExplicitTarget(asSanitizedString(normalized || raw).trim());
|
|
116
178
|
},
|
|
117
|
-
resolveTarget: async ({ accountId, input, normalized }:
|
|
179
|
+
resolveTarget: async ({ accountId, input, normalized }: BncrMessagingResolveTargetArgs) => {
|
|
118
180
|
const runtimeBridge = getBridge();
|
|
119
181
|
const resolved = resolveBncrOutboundTarget({
|
|
120
|
-
target:
|
|
182
|
+
target: asSanitizedString(normalized || input).trim(),
|
|
121
183
|
accountId: resolveMessagingAccountId(accountId),
|
|
122
184
|
resolveRouteBySession: (raw: string, acc: string) =>
|
|
123
185
|
runtimeBridge.resolveRouteBySession(raw, acc),
|
package/src/plugin/outbound.ts
CHANGED
|
@@ -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: (
|
|
15
|
-
|
|
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:
|
|
22
|
-
sendMedia: async (ctx:
|
|
23
|
-
replyAction: async (ctx:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
+
}
|