@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCanonicalBncrSessionKey,
|
|
3
|
+
formatDisplayScope,
|
|
4
|
+
parseRouteFromDisplayScope,
|
|
5
|
+
parseStrictBncrSessionKey,
|
|
6
|
+
routeKey,
|
|
7
|
+
} from '../core/targets.ts';
|
|
8
|
+
import type { BncrRoute } from '../core/types.ts';
|
|
9
|
+
import { getOpenClawRuntimeConfigOrDefault } from '../openclaw/config-runtime.ts';
|
|
10
|
+
import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
|
|
11
|
+
|
|
12
|
+
function asString(v: unknown, fallback = ''): string {
|
|
13
|
+
if (typeof v === 'string') return v;
|
|
14
|
+
if (v == null) return fallback;
|
|
15
|
+
return String(v);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createBncrTargetRuntime(runtime: {
|
|
19
|
+
api: Parameters<typeof getOpenClawRuntimeConfigOrDefault>[0];
|
|
20
|
+
channelId: string;
|
|
21
|
+
canonicalAgentId: string | null;
|
|
22
|
+
now: () => number;
|
|
23
|
+
normalizeAccountId: (accountId: string) => string;
|
|
24
|
+
sessionRoutes: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
|
|
25
|
+
routeAliases: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
|
|
26
|
+
lastSessionByAccount: Map<string, { sessionKey: string; scope: string; updatedAt: number }>;
|
|
27
|
+
markActivity: (accountId: string, at?: number) => void;
|
|
28
|
+
scheduleSave: () => void;
|
|
29
|
+
logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
|
|
30
|
+
logWarn: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
|
|
31
|
+
ensureCanonicalAgentId: (args: {
|
|
32
|
+
cfg: BncrChannelConfigRoot;
|
|
33
|
+
accountId: string;
|
|
34
|
+
channelId: string;
|
|
35
|
+
peer: { kind: 'direct'; id: string };
|
|
36
|
+
}) => string;
|
|
37
|
+
}) {
|
|
38
|
+
function rememberSessionRoute(sessionKey: string, accountId: string, route: BncrRoute) {
|
|
39
|
+
const key = asString(sessionKey).trim();
|
|
40
|
+
if (!key) return;
|
|
41
|
+
|
|
42
|
+
const acc = runtime.normalizeAccountId(accountId);
|
|
43
|
+
const t = runtime.now();
|
|
44
|
+
const info = { accountId: acc, route, updatedAt: t };
|
|
45
|
+
|
|
46
|
+
runtime.sessionRoutes.set(key, info);
|
|
47
|
+
runtime.routeAliases.set(routeKey(acc, route), info);
|
|
48
|
+
runtime.lastSessionByAccount.set(acc, {
|
|
49
|
+
sessionKey: key,
|
|
50
|
+
scope: formatDisplayScope(route),
|
|
51
|
+
updatedAt: t,
|
|
52
|
+
});
|
|
53
|
+
runtime.markActivity(acc, t);
|
|
54
|
+
runtime.scheduleSave();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveRouteBySession(sessionKey: string, accountId: string): BncrRoute | null {
|
|
58
|
+
const key = asString(sessionKey).trim();
|
|
59
|
+
const hit = runtime.sessionRoutes.get(key);
|
|
60
|
+
if (
|
|
61
|
+
hit &&
|
|
62
|
+
runtime.normalizeAccountId(accountId) === runtime.normalizeAccountId(hit.accountId)
|
|
63
|
+
) {
|
|
64
|
+
return hit.route;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const parsed = parseStrictBncrSessionKey(key);
|
|
68
|
+
if (!parsed) return null;
|
|
69
|
+
|
|
70
|
+
const alias = runtime.routeAliases.get(
|
|
71
|
+
routeKey(runtime.normalizeAccountId(accountId), parsed.route),
|
|
72
|
+
);
|
|
73
|
+
return alias?.route || parsed.route;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveVerifiedTarget(
|
|
77
|
+
rawTarget: string,
|
|
78
|
+
accountId: string,
|
|
79
|
+
): { sessionKey: string; route: BncrRoute; displayScope: string } {
|
|
80
|
+
const acc = runtime.normalizeAccountId(accountId);
|
|
81
|
+
const raw = asString(rawTarget).trim();
|
|
82
|
+
if (!raw) throw new Error('bncr invalid target(empty)');
|
|
83
|
+
|
|
84
|
+
runtime.logInfo('target', `incoming raw=${raw} accountId=${acc}`, { debugOnly: true });
|
|
85
|
+
|
|
86
|
+
let route: BncrRoute | null = null;
|
|
87
|
+
|
|
88
|
+
const strict = parseStrictBncrSessionKey(raw);
|
|
89
|
+
if (strict) {
|
|
90
|
+
route = strict.route;
|
|
91
|
+
} else {
|
|
92
|
+
route = parseRouteFromDisplayScope(raw) || resolveRouteBySession(raw, acc);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!route) {
|
|
96
|
+
runtime.logWarn(
|
|
97
|
+
'target',
|
|
98
|
+
`invalid raw=${raw} accountId=${acc} reason=unparseable-or-unknown standardTo=Bncr:<platform>:<groupId>:<userId>|Bncr:<platform>:<userId> standardSessionKey=agent:<agentId>:bncr:direct:<hex(scope)>`,
|
|
99
|
+
{ debugOnly: true },
|
|
100
|
+
);
|
|
101
|
+
throw new Error(
|
|
102
|
+
`bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>): ${raw}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const canonicalAgentId =
|
|
107
|
+
runtime.canonicalAgentId ||
|
|
108
|
+
runtime.ensureCanonicalAgentId({
|
|
109
|
+
cfg: getOpenClawRuntimeConfigOrDefault<BncrChannelConfigRoot>(runtime.api, {}),
|
|
110
|
+
accountId: acc,
|
|
111
|
+
channelId: runtime.channelId,
|
|
112
|
+
peer: { kind: 'direct', id: route.groupId === '0' ? route.userId : route.groupId },
|
|
113
|
+
});
|
|
114
|
+
const verified = {
|
|
115
|
+
sessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId),
|
|
116
|
+
route,
|
|
117
|
+
displayScope: formatDisplayScope(route),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
runtime.logInfo(
|
|
121
|
+
'target',
|
|
122
|
+
`canonical raw=${raw} accountId=${acc} verified=${JSON.stringify(verified)}`,
|
|
123
|
+
{ debugOnly: true },
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
runtime.lastSessionByAccount.set(acc, {
|
|
127
|
+
sessionKey: verified.sessionKey,
|
|
128
|
+
scope: verified.displayScope,
|
|
129
|
+
updatedAt: runtime.now(),
|
|
130
|
+
});
|
|
131
|
+
runtime.scheduleSave();
|
|
132
|
+
|
|
133
|
+
return verified;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
rememberSessionRoute,
|
|
138
|
+
resolveRouteBySession,
|
|
139
|
+
resolveVerifiedTarget,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BncrAckObservability,
|
|
3
|
+
BncrAckStrategy,
|
|
4
|
+
BncrRoute,
|
|
5
|
+
BncrRuntimeLastSession,
|
|
6
|
+
OutboxEntry,
|
|
7
|
+
} from '../core/types.ts';
|
|
8
|
+
import type { MediaDedupeCacheEntry } from '../messaging/outbound/media-dedupe.ts';
|
|
9
|
+
import type { getOpenClawRuntimeConfigOrDefault } from '../openclaw/config-runtime.ts';
|
|
10
|
+
import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
|
|
11
|
+
import { createBncrMediaDedupeRuntime } from './media-dedupe-runtime.ts';
|
|
12
|
+
import { createBncrStatusRuntime } from './status-runtime.ts';
|
|
13
|
+
import { createBncrTargetRuntime } from './target-runtime.ts';
|
|
14
|
+
|
|
15
|
+
export function createBncrTargetStatusRuntimeGroup(runtime: {
|
|
16
|
+
api: Parameters<typeof getOpenClawRuntimeConfigOrDefault>[0];
|
|
17
|
+
channelId: string;
|
|
18
|
+
canonicalAgentId: string | null;
|
|
19
|
+
getPluginRoot: () => string | null;
|
|
20
|
+
startedAt: number;
|
|
21
|
+
debugVerbose: boolean;
|
|
22
|
+
adaptiveAckTimeoutEnabled: boolean;
|
|
23
|
+
defaultMessageAckTimeoutMs: number;
|
|
24
|
+
fileAckTimeoutMs: number;
|
|
25
|
+
maxAckTimeoutMs: number;
|
|
26
|
+
now: () => number;
|
|
27
|
+
normalizeAccountId: (accountId: string) => string;
|
|
28
|
+
sessionRoutes: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
|
|
29
|
+
routeAliases: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
|
|
30
|
+
lastSessionByAccount: Map<string, BncrRuntimeLastSession>;
|
|
31
|
+
markActivity: (accountId: string, at?: number) => void;
|
|
32
|
+
scheduleSave: () => void;
|
|
33
|
+
logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
|
|
34
|
+
logWarn: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
|
|
35
|
+
ensureCanonicalAgentId: (args: {
|
|
36
|
+
cfg: BncrChannelConfigRoot;
|
|
37
|
+
accountId: string;
|
|
38
|
+
channelId: string;
|
|
39
|
+
peer: { kind: 'direct'; id: string };
|
|
40
|
+
}) => string;
|
|
41
|
+
recentMediaDedupeBySession: Map<string, Map<string, MediaDedupeCacheEntry>>;
|
|
42
|
+
resolveMessageAckTimeoutMs: (accountId?: string) => number;
|
|
43
|
+
isOnline: (accountId: string) => boolean;
|
|
44
|
+
outboxValues: () => Iterable<OutboxEntry>;
|
|
45
|
+
deadLetterEntries: () => OutboxEntry[];
|
|
46
|
+
sessionRouteValues: () => Iterable<{ accountId: string }>;
|
|
47
|
+
countInvalidOutboxSessionKeys: (accountId: string) => number;
|
|
48
|
+
countLegacyAccountResidue: (accountId: string) => number;
|
|
49
|
+
connectEventsByAccount: Map<string, number>;
|
|
50
|
+
inboundEventsByAccount: Map<string, number>;
|
|
51
|
+
activityEventsByAccount: Map<string, number>;
|
|
52
|
+
ackEventsByAccount: Map<string, number>;
|
|
53
|
+
activeConnectionCount: (accountId: string) => number;
|
|
54
|
+
lastActivityByAccount: Map<string, number>;
|
|
55
|
+
lastInboundByAccount: Map<string, number>;
|
|
56
|
+
lastOutboundByAccount: Map<string, number>;
|
|
57
|
+
buildRuntimeAckObservability: (accountId: string) => BncrAckObservability;
|
|
58
|
+
buildRuntimeAckStrategy: (ackObservability: BncrAckObservability) => BncrAckStrategy;
|
|
59
|
+
lastAckOkByAccount: Map<string, number>;
|
|
60
|
+
lastAckTimeoutByAccount: Map<string, number>;
|
|
61
|
+
getAckTimeoutCount: (accountId: string) => number;
|
|
62
|
+
getAccountPendingOutboxEntries: (accountId: string) => OutboxEntry[];
|
|
63
|
+
getAccountDeadLetterEntries: (accountId: string) => OutboxEntry[];
|
|
64
|
+
connectionsValues: () => Iterable<{ lastSeenAt: number }>;
|
|
65
|
+
connectTtlMs: number;
|
|
66
|
+
}) {
|
|
67
|
+
const targetRuntime = createBncrTargetRuntime({
|
|
68
|
+
api: runtime.api,
|
|
69
|
+
channelId: runtime.channelId,
|
|
70
|
+
canonicalAgentId: runtime.canonicalAgentId,
|
|
71
|
+
now: runtime.now,
|
|
72
|
+
normalizeAccountId: runtime.normalizeAccountId,
|
|
73
|
+
sessionRoutes: runtime.sessionRoutes,
|
|
74
|
+
routeAliases: runtime.routeAliases,
|
|
75
|
+
lastSessionByAccount: runtime.lastSessionByAccount,
|
|
76
|
+
markActivity: runtime.markActivity,
|
|
77
|
+
scheduleSave: runtime.scheduleSave,
|
|
78
|
+
logInfo: runtime.logInfo,
|
|
79
|
+
logWarn: runtime.logWarn,
|
|
80
|
+
ensureCanonicalAgentId: runtime.ensureCanonicalAgentId,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const mediaDedupeRuntime = createBncrMediaDedupeRuntime({
|
|
84
|
+
now: runtime.now,
|
|
85
|
+
recentMediaDedupeBySession: runtime.recentMediaDedupeBySession,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const statusRuntime = createBncrStatusRuntime({
|
|
89
|
+
api: runtime.api,
|
|
90
|
+
getPluginRoot: runtime.getPluginRoot,
|
|
91
|
+
startedAt: runtime.startedAt,
|
|
92
|
+
debugVerbose: runtime.debugVerbose,
|
|
93
|
+
adaptiveAckTimeoutEnabled: runtime.adaptiveAckTimeoutEnabled,
|
|
94
|
+
defaultMessageAckTimeoutMs: runtime.defaultMessageAckTimeoutMs,
|
|
95
|
+
fileAckTimeoutMs: runtime.fileAckTimeoutMs,
|
|
96
|
+
maxAckTimeoutMs: runtime.maxAckTimeoutMs,
|
|
97
|
+
now: runtime.now,
|
|
98
|
+
resolveMessageAckTimeoutMs: runtime.resolveMessageAckTimeoutMs,
|
|
99
|
+
isOnline: runtime.isOnline,
|
|
100
|
+
outboxValues: runtime.outboxValues,
|
|
101
|
+
deadLetterEntries: runtime.deadLetterEntries,
|
|
102
|
+
sessionRouteValues: runtime.sessionRouteValues,
|
|
103
|
+
countInvalidOutboxSessionKeys: runtime.countInvalidOutboxSessionKeys,
|
|
104
|
+
countLegacyAccountResidue: runtime.countLegacyAccountResidue,
|
|
105
|
+
connectEventsByAccount: runtime.connectEventsByAccount,
|
|
106
|
+
inboundEventsByAccount: runtime.inboundEventsByAccount,
|
|
107
|
+
activityEventsByAccount: runtime.activityEventsByAccount,
|
|
108
|
+
ackEventsByAccount: runtime.ackEventsByAccount,
|
|
109
|
+
activeConnectionCount: runtime.activeConnectionCount,
|
|
110
|
+
lastSessionByAccount: runtime.lastSessionByAccount,
|
|
111
|
+
lastActivityByAccount: runtime.lastActivityByAccount,
|
|
112
|
+
lastInboundByAccount: runtime.lastInboundByAccount,
|
|
113
|
+
lastOutboundByAccount: runtime.lastOutboundByAccount,
|
|
114
|
+
buildRuntimeAckObservability: runtime.buildRuntimeAckObservability,
|
|
115
|
+
buildRuntimeAckStrategy: runtime.buildRuntimeAckStrategy,
|
|
116
|
+
lastAckOkByAccount: runtime.lastAckOkByAccount,
|
|
117
|
+
lastAckTimeoutByAccount: runtime.lastAckTimeoutByAccount,
|
|
118
|
+
getAckTimeoutCount: runtime.getAckTimeoutCount,
|
|
119
|
+
getAccountPendingOutboxEntries: runtime.getAccountPendingOutboxEntries,
|
|
120
|
+
getAccountDeadLetterEntries: runtime.getAccountDeadLetterEntries,
|
|
121
|
+
connectionsValues: runtime.connectionsValues,
|
|
122
|
+
connectTtlMs: runtime.connectTtlMs,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
targetRuntime,
|
|
127
|
+
mediaDedupeRuntime,
|
|
128
|
+
statusRuntime,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { BncrConnection } from '../core/types.ts';
|
|
2
|
+
|
|
3
|
+
export function createBncrTransientStateRuntime(runtime: {
|
|
4
|
+
now: () => number;
|
|
5
|
+
connectTtlMs: number;
|
|
6
|
+
fileTransferKeepMs: number;
|
|
7
|
+
fileTransferTerminalKeepMs: number;
|
|
8
|
+
fileTransferAckTtlMs: number;
|
|
9
|
+
connections: Map<string, BncrConnection>;
|
|
10
|
+
activeConnectionByAccount: Map<string, string>;
|
|
11
|
+
recentInbound: Map<string, number>;
|
|
12
|
+
fileSendTransfers: Map<string, { status: string; startedAt: number; terminalAt?: number }>;
|
|
13
|
+
fileRecvTransfers: Map<string, { status: string; startedAt: number; terminalAt?: number }>;
|
|
14
|
+
earlyFileAcks: Map<string, { at: number }>;
|
|
15
|
+
logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
|
|
16
|
+
bridgeId: string;
|
|
17
|
+
}) {
|
|
18
|
+
function cleanupFileTransfers() {
|
|
19
|
+
const t = runtime.now();
|
|
20
|
+
const keepMsForTransfer = (st: { status: string; startedAt: number; terminalAt?: number }) => {
|
|
21
|
+
const startedAt = Number.isFinite(st.startedAt) ? st.startedAt : t;
|
|
22
|
+
if (st.status === 'completed' || st.status === 'aborted') {
|
|
23
|
+
return {
|
|
24
|
+
since: Number.isFinite(st.terminalAt) ? (st.terminalAt as number) : startedAt,
|
|
25
|
+
keepMs: runtime.fileTransferTerminalKeepMs,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { since: startedAt, keepMs: runtime.fileTransferKeepMs };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
for (const [id, st] of runtime.fileSendTransfers.entries()) {
|
|
32
|
+
const keep = keepMsForTransfer(st);
|
|
33
|
+
if (t - keep.since > keep.keepMs) runtime.fileSendTransfers.delete(id);
|
|
34
|
+
}
|
|
35
|
+
for (const [id, st] of runtime.fileRecvTransfers.entries()) {
|
|
36
|
+
const keep = keepMsForTransfer(st);
|
|
37
|
+
if (t - keep.since > keep.keepMs) runtime.fileRecvTransfers.delete(id);
|
|
38
|
+
}
|
|
39
|
+
for (const [key, ack] of runtime.earlyFileAcks.entries()) {
|
|
40
|
+
if (t - ack.at > runtime.fileTransferAckTtlMs) runtime.earlyFileAcks.delete(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function gcTransientState() {
|
|
45
|
+
const t = runtime.now();
|
|
46
|
+
const staleBefore = t - runtime.connectTtlMs * 2;
|
|
47
|
+
|
|
48
|
+
for (const [key, c] of runtime.connections.entries()) {
|
|
49
|
+
if (c.lastSeenAt < staleBefore) {
|
|
50
|
+
runtime.logInfo(
|
|
51
|
+
'connection',
|
|
52
|
+
`gc ${JSON.stringify({
|
|
53
|
+
bridge: runtime.bridgeId,
|
|
54
|
+
key,
|
|
55
|
+
accountId: c.accountId,
|
|
56
|
+
connId: c.connId,
|
|
57
|
+
clientId: c.clientId,
|
|
58
|
+
lastSeenAt: c.lastSeenAt,
|
|
59
|
+
staleBefore,
|
|
60
|
+
})}`,
|
|
61
|
+
{ debugOnly: true },
|
|
62
|
+
);
|
|
63
|
+
runtime.connections.delete(key);
|
|
64
|
+
if (runtime.activeConnectionByAccount.get(c.accountId) === key) {
|
|
65
|
+
runtime.activeConnectionByAccount.delete(c.accountId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const dedupWindowMs = 90_000;
|
|
71
|
+
for (const [key, ts] of runtime.recentInbound.entries()) {
|
|
72
|
+
if (t - ts > dedupWindowMs) runtime.recentInbound.delete(key);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cleanupFileTransfers();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
gcTransientState,
|
|
80
|
+
cleanupFileTransfers,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { BncrAckObservability, BncrAckStrategy } from '../core/types.ts';
|
|
2
|
+
|
|
1
3
|
type ComputeBncrRecommendedAckTimeoutArgs = {
|
|
2
4
|
lateAckOkCount: number;
|
|
3
5
|
recentAckTimeoutCount: number;
|
|
@@ -83,7 +85,7 @@ function asString(value: unknown, fallback = '') {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
export function buildBncrRuntimeAckStrategy(args: {
|
|
86
|
-
ackObservability:
|
|
88
|
+
ackObservability: BncrAckObservability;
|
|
87
89
|
defaultAckTimeoutMs: number;
|
|
88
90
|
maxAckTimeoutMs: number;
|
|
89
91
|
}) {
|
|
@@ -101,7 +103,7 @@ export function buildBncrRuntimeAckStrategy(args: {
|
|
|
101
103
|
lastLateAckAgeMs: ackObservability.lastLateAckAgeMs ?? null,
|
|
102
104
|
lateAckObservationTtlMs: ackObservability.lateAckObservationTtlMs ?? null,
|
|
103
105
|
recovered: ackObservability.adaptiveAckRecovered === true,
|
|
104
|
-
};
|
|
106
|
+
} satisfies BncrAckStrategy;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
export function buildBncrRuntimeAckObservability(args: {
|
|
@@ -165,5 +167,5 @@ export function buildBncrRuntimeAckObservability(args: {
|
|
|
165
167
|
currentAckTimeoutMs: args.currentAckTimeoutMs,
|
|
166
168
|
recommendedAckTimeoutMs: ackTimeoutDecision.timeoutMs,
|
|
167
169
|
recommendedAckTimeoutReason: ackTimeoutDecision.reason,
|
|
168
|
-
};
|
|
170
|
+
} satisfies BncrAckObservability;
|
|
169
171
|
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { BNCR_DEFAULT_ACCOUNT_ID, CHANNEL_ID, normalizeAccountId } from '../core/accounts.ts';
|
|
2
2
|
import { getOpenClawRuntimeConfig } from '../openclaw/config-runtime.ts';
|
|
3
|
+
import type {
|
|
4
|
+
BncrChannelConfigRoot,
|
|
5
|
+
BncrChannelConfigSection,
|
|
6
|
+
BncrStatusRuntimeSnapshot,
|
|
7
|
+
} from '../plugin/channel-runtime-types.ts';
|
|
3
8
|
|
|
4
9
|
type RuntimeApiHolder = { api: unknown };
|
|
10
|
+
type BncrAckConfigAccount = { outboundRequireAck?: boolean };
|
|
11
|
+
type BncrAckConfigChannel = BncrChannelConfigSection & {
|
|
12
|
+
accounts?: Record<string, BncrAckConfigAccount | undefined>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type OpenClawConfigApi = Parameters<typeof getOpenClawRuntimeConfig>[0];
|
|
16
|
+
|
|
17
|
+
function resolveBncrAckConfigChannel(api: unknown): BncrAckConfigChannel | undefined {
|
|
18
|
+
const cfg = getOpenClawRuntimeConfig(api as OpenClawConfigApi) as BncrChannelConfigRoot;
|
|
19
|
+
return cfg?.channels?.[CHANNEL_ID] as BncrAckConfigChannel | undefined;
|
|
20
|
+
}
|
|
5
21
|
|
|
6
22
|
type ResolveOutboundAckRequiredArgs = RuntimeApiHolder & {
|
|
7
23
|
accountId?: string;
|
|
@@ -9,11 +25,10 @@ type ResolveOutboundAckRequiredArgs = RuntimeApiHolder & {
|
|
|
9
25
|
|
|
10
26
|
export function resolveBncrOutboundAckRequired(args: ResolveOutboundAckRequiredArgs) {
|
|
11
27
|
try {
|
|
12
|
-
const
|
|
13
|
-
const channelCfg = (cfg as any)?.channels?.[CHANNEL_ID];
|
|
28
|
+
const channelCfg = resolveBncrAckConfigChannel(args.api);
|
|
14
29
|
const accountCfg =
|
|
15
30
|
args.accountId && channelCfg?.accounts && typeof channelCfg.accounts === 'object'
|
|
16
|
-
?
|
|
31
|
+
? channelCfg.accounts[normalizeAccountId(args.accountId)]
|
|
17
32
|
: null;
|
|
18
33
|
const scoped = accountCfg?.outboundRequireAck;
|
|
19
34
|
const global = channelCfg?.outboundRequireAck;
|
|
@@ -37,9 +52,9 @@ type BuildBncrRuntimeFlagsArgs = RuntimeApiHolder & {
|
|
|
37
52
|
export function buildBncrRuntimeStatusInput(args: {
|
|
38
53
|
accountId: string;
|
|
39
54
|
connected: boolean;
|
|
40
|
-
queueSnapshot:
|
|
41
|
-
eventCounters:
|
|
42
|
-
activitySnapshot:
|
|
55
|
+
queueSnapshot: BncrStatusRuntimeSnapshot;
|
|
56
|
+
eventCounters: BncrStatusRuntimeSnapshot;
|
|
57
|
+
activitySnapshot: BncrStatusRuntimeSnapshot;
|
|
43
58
|
startedAt: number | null;
|
|
44
59
|
running?: boolean;
|
|
45
60
|
channelRoot: string;
|
|
@@ -59,8 +74,7 @@ export function buildBncrRuntimeStatusInput(args: {
|
|
|
59
74
|
export function buildBncrRuntimeFlags(args: BuildBncrRuntimeFlagsArgs) {
|
|
60
75
|
let ackPolicySource: 'channel' | 'default' = 'default';
|
|
61
76
|
try {
|
|
62
|
-
const
|
|
63
|
-
const global = (cfg as any)?.channels?.[CHANNEL_ID]?.outboundRequireAck;
|
|
77
|
+
const global = resolveBncrAckConfigChannel(args.api)?.outboundRequireAck;
|
|
64
78
|
if (typeof global === 'boolean') ackPolicySource = 'channel';
|
|
65
79
|
} catch {
|
|
66
80
|
// keep default source
|
|
@@ -79,3 +93,5 @@ export function buildBncrRuntimeFlags(args: BuildBncrRuntimeFlagsArgs) {
|
|
|
79
93
|
debugVerbose: args.debugVerbose,
|
|
80
94
|
};
|
|
81
95
|
}
|
|
96
|
+
|
|
97
|
+
export type BncrRuntimeFlags = ReturnType<typeof buildBncrRuntimeFlags>;
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { normalizeAccountId } from '../core/accounts.ts';
|
|
2
|
-
import type { OutboxEntry } from '../core/types.ts';
|
|
2
|
+
import type { BncrRuntimeLastSession, OutboxEntry } from '../core/types.ts';
|
|
3
|
+
|
|
4
|
+
export type RuntimeQueueSnapshot = {
|
|
5
|
+
pending: number;
|
|
6
|
+
deadLetter: number;
|
|
7
|
+
sessionRoutesCount: number;
|
|
8
|
+
invalidOutboxSessionKeys: number;
|
|
9
|
+
legacyAccountResidue: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type RuntimeEventCounters = {
|
|
13
|
+
connectEvents: number;
|
|
14
|
+
inboundEvents: number;
|
|
15
|
+
activityEvents: number;
|
|
16
|
+
ackEvents: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type RuntimeActivitySnapshot = {
|
|
20
|
+
activeConnections: number;
|
|
21
|
+
lastSession: BncrRuntimeLastSession | null;
|
|
22
|
+
lastActivityAt: number | null;
|
|
23
|
+
lastInboundAt: number | null;
|
|
24
|
+
lastOutboundAt: number | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type RuntimeStatusSnapshots = {
|
|
28
|
+
queueSnapshot: RuntimeQueueSnapshot;
|
|
29
|
+
eventCounters: RuntimeEventCounters;
|
|
30
|
+
activitySnapshot: RuntimeActivitySnapshot;
|
|
31
|
+
};
|
|
3
32
|
|
|
4
33
|
function getCounter(map: Map<string, number>, accountId: string): number {
|
|
5
34
|
return map.get(normalizeAccountId(accountId)) || 0;
|
|
@@ -12,7 +41,7 @@ export function buildRuntimeQueueSnapshot(args: {
|
|
|
12
41
|
sessionRouteEntries: Iterable<{ accountId: string }>;
|
|
13
42
|
countInvalidOutboxSessionKeys: (accountId: string) => number;
|
|
14
43
|
countLegacyAccountResidue: (accountId: string) => number;
|
|
15
|
-
}) {
|
|
44
|
+
}): RuntimeQueueSnapshot {
|
|
16
45
|
const accountId = normalizeAccountId(args.accountId);
|
|
17
46
|
const pending = Array.from(args.outboxEntries).filter((v) => v.accountId === accountId).length;
|
|
18
47
|
const deadLetter = Array.from(args.deadLetterEntries).filter(
|
|
@@ -36,7 +65,7 @@ export function buildRuntimeEventCounters(args: {
|
|
|
36
65
|
inboundEventsByAccount: Map<string, number>;
|
|
37
66
|
activityEventsByAccount: Map<string, number>;
|
|
38
67
|
ackEventsByAccount: Map<string, number>;
|
|
39
|
-
}) {
|
|
68
|
+
}): RuntimeEventCounters {
|
|
40
69
|
const accountId = normalizeAccountId(args.accountId);
|
|
41
70
|
return {
|
|
42
71
|
connectEvents: getCounter(args.connectEventsByAccount, accountId),
|
|
@@ -53,11 +82,11 @@ function nullableMapNumber(map: Map<string, number>, key: string): number | null
|
|
|
53
82
|
export function buildRuntimeActivitySnapshot(args: {
|
|
54
83
|
accountId: string;
|
|
55
84
|
activeConnectionCount: (accountId: string) => number;
|
|
56
|
-
lastSessionByAccount: Map<string,
|
|
85
|
+
lastSessionByAccount: Map<string, BncrRuntimeLastSession>;
|
|
57
86
|
lastActivityByAccount: Map<string, number>;
|
|
58
87
|
lastInboundByAccount: Map<string, number>;
|
|
59
88
|
lastOutboundByAccount: Map<string, number>;
|
|
60
|
-
}) {
|
|
89
|
+
}): RuntimeActivitySnapshot {
|
|
61
90
|
const accountId = normalizeAccountId(args.accountId);
|
|
62
91
|
return {
|
|
63
92
|
activeConnections: args.activeConnectionCount(accountId),
|
|
@@ -80,11 +109,11 @@ export function buildRuntimeStatusSnapshots(args: {
|
|
|
80
109
|
activityEventsByAccount: Map<string, number>;
|
|
81
110
|
ackEventsByAccount: Map<string, number>;
|
|
82
111
|
activeConnectionCount: (accountId: string) => number;
|
|
83
|
-
lastSessionByAccount: Map<string,
|
|
112
|
+
lastSessionByAccount: Map<string, BncrRuntimeLastSession>;
|
|
84
113
|
lastActivityByAccount: Map<string, number>;
|
|
85
114
|
lastInboundByAccount: Map<string, number>;
|
|
86
115
|
lastOutboundByAccount: Map<string, number>;
|
|
87
|
-
}) {
|
|
116
|
+
}): RuntimeStatusSnapshots {
|
|
88
117
|
const accountId = normalizeAccountId(args.accountId);
|
|
89
118
|
return {
|
|
90
119
|
queueSnapshot: buildRuntimeQueueSnapshot({
|
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
import { normalizeAccountId } from '../core/accounts.ts';
|
|
2
|
+
import type { BncrDiagnosticsSummary } from '../core/types.ts';
|
|
3
|
+
|
|
4
|
+
type StatusRuntimeMeta = {
|
|
5
|
+
pending?: number;
|
|
6
|
+
pendingAdmissionsCount?: number;
|
|
7
|
+
pendingAdmissions?: unknown[];
|
|
8
|
+
deadLetter?: number;
|
|
9
|
+
lastSessionScope?: string | null;
|
|
10
|
+
lastSessionAt?: number | null;
|
|
11
|
+
lastSessionAgo?: string | null;
|
|
12
|
+
lastActivityAt?: number | null;
|
|
13
|
+
lastActivityAgo?: string | null;
|
|
14
|
+
lastInboundAt?: number | null;
|
|
15
|
+
lastInboundAgo?: string | null;
|
|
16
|
+
lastOutboundAt?: number | null;
|
|
17
|
+
lastOutboundAgo?: string | null;
|
|
18
|
+
diagnostics?: BncrDiagnosticsSummary | Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type StatusRuntimeState = {
|
|
22
|
+
accountId?: string;
|
|
23
|
+
running?: boolean;
|
|
24
|
+
connected?: boolean;
|
|
25
|
+
restartPending?: boolean;
|
|
26
|
+
lastEventAt?: number | null;
|
|
27
|
+
lastStopAt?: number | null;
|
|
28
|
+
mode?: 'linked' | 'configured' | string;
|
|
29
|
+
lastError?: string | null;
|
|
30
|
+
meta?: StatusRuntimeMeta;
|
|
31
|
+
};
|
|
2
32
|
|
|
3
33
|
type StatusWorkerContext = {
|
|
4
34
|
accountId: string;
|
|
5
|
-
getStatus?: () =>
|
|
6
|
-
setStatus?: (status:
|
|
35
|
+
getStatus?: () => StatusRuntimeState;
|
|
36
|
+
setStatus?: (status: StatusRuntimeState) => void;
|
|
7
37
|
abortSignal?: {
|
|
8
38
|
aborted?: boolean;
|
|
9
39
|
addEventListener?: (event: 'abort', listener: () => void, options?: { once?: boolean }) => void;
|
|
@@ -70,10 +100,10 @@ export function updateHealthStatusLogState(args: {
|
|
|
70
100
|
type StatusWorkerHooks = {
|
|
71
101
|
isOnline: (accountId: string) => boolean;
|
|
72
102
|
hasRecentInboundReachability: (accountId: string) => boolean;
|
|
73
|
-
getLastActivityAt: (accountId: string, previous:
|
|
103
|
+
getLastActivityAt: (accountId: string, previous: StatusRuntimeState) => number | null;
|
|
74
104
|
getActiveConnectionKey: (accountId: string) => string | null;
|
|
75
105
|
getActiveConnections: (accountId: string) => Array<Record<string, unknown>>;
|
|
76
|
-
buildStatusMeta: (accountId: string) =>
|
|
106
|
+
buildStatusMeta: (accountId: string) => StatusRuntimeMeta;
|
|
77
107
|
logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
|
|
78
108
|
logInfoDedup: (
|
|
79
109
|
scope: string | undefined,
|