@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/core/accounts.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BncrAccountConfig,
|
|
3
|
+
BncrChannelConfigRoot,
|
|
4
|
+
BncrChannelConfigSection,
|
|
5
|
+
} from '../plugin/channel-runtime-types.ts';
|
|
6
|
+
|
|
1
7
|
const CHANNEL_ID = 'bncr';
|
|
2
8
|
const BNCR_DEFAULT_ACCOUNT_ID = 'Primary';
|
|
3
9
|
|
|
@@ -29,8 +35,21 @@ export function resolveDefaultDisplayName(rawName: unknown, accountId: string):
|
|
|
29
35
|
return raw;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
function getChannelConfig(cfg: BncrChannelConfigRoot | null | undefined): BncrChannelConfigSection {
|
|
39
|
+
return cfg?.channels?.[CHANNEL_ID] || {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getAccountsConfig(
|
|
43
|
+
cfg: BncrChannelConfigRoot | null | undefined,
|
|
44
|
+
): Record<string, BncrAccountConfig | undefined> {
|
|
45
|
+
return getChannelConfig(cfg).accounts || {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveAccount(
|
|
49
|
+
cfg: BncrChannelConfigRoot | null | undefined,
|
|
50
|
+
accountId?: string | null,
|
|
51
|
+
) {
|
|
52
|
+
const accounts = getAccountsConfig(cfg);
|
|
34
53
|
let key = normalizeAccountId(accountId);
|
|
35
54
|
|
|
36
55
|
if (!accounts[key]) {
|
|
@@ -48,8 +67,8 @@ export function resolveAccount(cfg: any, accountId?: string | null) {
|
|
|
48
67
|
};
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
export function listAccountIds(cfg:
|
|
52
|
-
const ids = Object.keys(cfg
|
|
70
|
+
export function listAccountIds(cfg: BncrChannelConfigRoot | null | undefined): string[] {
|
|
71
|
+
const ids = Object.keys(getAccountsConfig(cfg));
|
|
53
72
|
return ids.length ? ids : [BNCR_DEFAULT_ACCOUNT_ID];
|
|
54
73
|
}
|
|
55
74
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { summarizeBncrTextPreview } from './logging.ts';
|
|
2
2
|
import { formatDisplayScope } from './targets.ts';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
BncrDeadLetterDiagnosticsSummary,
|
|
5
|
+
BncrDeadLetterEntrySummary,
|
|
6
|
+
OutboxEntry,
|
|
7
|
+
} from './types.ts';
|
|
4
8
|
|
|
5
9
|
export type DeadLetterTopReason = { reason: string; count: number };
|
|
6
10
|
|
|
@@ -17,6 +21,15 @@ function asString(value: unknown, fallback = ''): string {
|
|
|
17
21
|
return String(value);
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
function asPayloadMessage(payload: OutboxEntry['payload']): {
|
|
25
|
+
msg?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
} {
|
|
29
|
+
const raw = payload.message;
|
|
30
|
+
return raw && typeof raw === 'object' ? (raw as { msg?: string; type?: string }) : {};
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
export function buildDeadLetterDiagnostics(options: BuildDeadLetterDiagnosticsOptions) {
|
|
21
34
|
const reasonCounts = new Map<string, number>();
|
|
22
35
|
let oldestAt: number | null = null;
|
|
@@ -43,7 +56,7 @@ export function buildDeadLetterDiagnostics(options: BuildDeadLetterDiagnosticsOp
|
|
|
43
56
|
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
44
57
|
.slice(0, 5)
|
|
45
58
|
.map(([reason, count]) => ({ reason, count })),
|
|
46
|
-
};
|
|
59
|
+
} satisfies BncrDeadLetterDiagnosticsSummary;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
export function formatDeadLetterTopReasons(topReasons: DeadLetterTopReason[]): string {
|
|
@@ -75,17 +88,36 @@ export function parseDeadLetterOlderThan(raw: unknown): number | null {
|
|
|
75
88
|
|
|
76
89
|
export function summarizeDeadLetterEntry(entry: OutboxEntry) {
|
|
77
90
|
const meta = entry.payload?._meta || {};
|
|
78
|
-
const msg = (entry.payload
|
|
91
|
+
const msg = asPayloadMessage(entry.payload);
|
|
79
92
|
const text = asString(meta.text || msg.msg || '');
|
|
80
93
|
return {
|
|
81
94
|
messageId: entry.messageId,
|
|
82
95
|
accountId: entry.accountId,
|
|
83
96
|
sessionKey: entry.sessionKey,
|
|
84
97
|
route: formatDisplayScope(entry.route),
|
|
85
|
-
kind: asString(meta.kind ||
|
|
98
|
+
kind: asString(meta.kind || msg.type || 'message'),
|
|
86
99
|
createdAt: Number.isFinite(Number(entry.createdAt)) ? Number(entry.createdAt) : null,
|
|
87
100
|
retryCount: Number.isFinite(Number(entry.retryCount)) ? Number(entry.retryCount) : 0,
|
|
88
101
|
lastError: entry.lastError || null,
|
|
89
102
|
textPreview: summarizeBncrTextPreview(text, 24),
|
|
90
|
-
};
|
|
103
|
+
} satisfies BncrDeadLetterEntrySummary;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function filterDeadLetterEntries(args: {
|
|
107
|
+
accountId: string;
|
|
108
|
+
entries: OutboxEntry[];
|
|
109
|
+
reason?: string | null;
|
|
110
|
+
olderThan?: number | null;
|
|
111
|
+
}) {
|
|
112
|
+
const normalizedAccountId = asString(args.accountId).trim().toLowerCase();
|
|
113
|
+
const reason = asString(args.reason || '').trim();
|
|
114
|
+
return args.entries.filter((entry) => {
|
|
115
|
+
if (asString(entry.accountId).trim().toLowerCase() !== normalizedAccountId) return false;
|
|
116
|
+
if (reason && entry.lastError !== reason) return false;
|
|
117
|
+
if (typeof args.olderThan === 'number') {
|
|
118
|
+
const createdAt = Number(entry.createdAt);
|
|
119
|
+
if (!Number.isFinite(createdAt) || createdAt >= args.olderThan) return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
91
123
|
}
|
package/src/core/diagnostics.ts
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
|
+
import type { BncrRuntimeFlags } from '../runtime/outbound-flags.ts';
|
|
2
|
+
import type { BncrExtendedDiagnostics } from './extended-diagnostics.ts';
|
|
3
|
+
import type { BncrPermissionSummary } from './permissions.ts';
|
|
1
4
|
import { buildBncrPermissionSummary } from './permissions.ts';
|
|
5
|
+
import type { BncrAccountProbe } from './probe.ts';
|
|
2
6
|
import { probeBncrAccount } from './probe.ts';
|
|
7
|
+
import type { BncrAccountRuntimeSnapshot } from './status.ts';
|
|
8
|
+
import type { BncrDownlinkHealthSummary } from './types.ts';
|
|
9
|
+
import { nonNegativeFiniteNumberOr } from './value-sanitize.ts';
|
|
3
10
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function nonNegativeFiniteNumberOr(value: unknown, fallback: number): number {
|
|
10
|
-
return Math.max(0, finiteNumberOr(value, fallback));
|
|
11
|
-
}
|
|
11
|
+
export type BncrDiagnosticsWaiters = {
|
|
12
|
+
messageAck: number;
|
|
13
|
+
fileAck: number;
|
|
14
|
+
};
|
|
12
15
|
|
|
13
16
|
type DiagnosticsPayloadArgs = {
|
|
14
|
-
cfg:
|
|
17
|
+
cfg: unknown;
|
|
15
18
|
channelId: string;
|
|
16
19
|
accountId: string;
|
|
17
|
-
runtime:
|
|
18
|
-
diagnostics:
|
|
19
|
-
downlinkHealth:
|
|
20
|
-
runtimeFlags:
|
|
21
|
-
waiters:
|
|
20
|
+
runtime: BncrAccountRuntimeSnapshot;
|
|
21
|
+
diagnostics: BncrExtendedDiagnostics;
|
|
22
|
+
downlinkHealth: BncrDownlinkHealthSummary;
|
|
23
|
+
runtimeFlags: BncrRuntimeFlags;
|
|
24
|
+
waiters: BncrDiagnosticsWaiters;
|
|
22
25
|
activeConnections: number;
|
|
23
26
|
invalidOutboxSessionKeys: number;
|
|
24
27
|
legacyAccountResidue: number;
|
|
25
28
|
now: number;
|
|
26
29
|
};
|
|
27
30
|
|
|
28
|
-
export
|
|
31
|
+
export type BncrDiagnosticsPayload = {
|
|
32
|
+
channel: string;
|
|
33
|
+
accountId: string;
|
|
34
|
+
runtime: BncrAccountRuntimeSnapshot;
|
|
35
|
+
diagnostics: BncrExtendedDiagnostics;
|
|
36
|
+
downlinkHealth: BncrDownlinkHealthSummary;
|
|
37
|
+
runtimeFlags: BncrRuntimeFlags;
|
|
38
|
+
waiters: BncrDiagnosticsWaiters;
|
|
39
|
+
permissions: BncrPermissionSummary;
|
|
40
|
+
probe: BncrAccountProbe;
|
|
41
|
+
now: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function buildDiagnosticsPayload(args: DiagnosticsPayloadArgs): BncrDiagnosticsPayload {
|
|
29
45
|
const permissions = buildBncrPermissionSummary(args.cfg ?? {});
|
|
30
46
|
const probe = probeBncrAccount({
|
|
31
47
|
accountId: args.accountId,
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import type { OutboxEntry } from './types.ts';
|
|
2
|
-
|
|
3
|
-
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
4
|
-
const n = Number(value);
|
|
5
|
-
return Number.isFinite(n) ? n : fallback;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function nonNegativeFiniteNumberOr(value: unknown, fallback: number): number {
|
|
9
|
-
return Math.max(0, finiteNumberOr(value, fallback));
|
|
10
|
-
}
|
|
1
|
+
import type { BncrDownlinkHealthSummary, OutboxEntry } from './types.ts';
|
|
2
|
+
import { finiteNumberOr, nonNegativeFiniteNumberOr } from './value-sanitize.ts';
|
|
11
3
|
|
|
12
4
|
type DownlinkHealthInput = {
|
|
13
5
|
accountId: string;
|
|
@@ -62,5 +54,5 @@ export function buildDownlinkHealth(input: DownlinkHealthInput) {
|
|
|
62
54
|
recommendReason: ackStalled
|
|
63
55
|
? 'single-conn pending outbox with recent ack timeout and no recent ack-ok while inbound/activity is still alive'
|
|
64
56
|
: '',
|
|
65
|
-
};
|
|
57
|
+
} satisfies BncrDownlinkHealthSummary;
|
|
66
58
|
}
|
|
@@ -1,11 +1,46 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpenClawChannelRuntimeSurfaceDiagnostics } from '../openclaw/runtime-surface.ts';
|
|
2
|
+
import type {
|
|
3
|
+
RegisterDriftSnapshot,
|
|
4
|
+
RegisterTraceEntry,
|
|
5
|
+
RegisterTraceSummary,
|
|
6
|
+
} from './register-trace.ts';
|
|
7
|
+
import type {
|
|
8
|
+
BncrDeadLetterDiagnosticsSummary,
|
|
9
|
+
BncrDiagnosticsSummary,
|
|
10
|
+
BncrExtendedOutboundDiagnostics,
|
|
11
|
+
BncrStaleCounterSummary,
|
|
12
|
+
} from './types.ts';
|
|
2
13
|
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
type ExtendedConnectionDiagnostics = {
|
|
15
|
+
active: number;
|
|
16
|
+
hasGatewayContext?: boolean;
|
|
17
|
+
lastGatewayContextAt?: number | null;
|
|
18
|
+
primaryLeaseId: string | null;
|
|
19
|
+
primaryEpoch: number | null;
|
|
20
|
+
acceptedConnections: number;
|
|
21
|
+
lastConnectAt: number | null;
|
|
22
|
+
lastDisconnectAt: number | null;
|
|
23
|
+
lastActivityAt: number | null;
|
|
24
|
+
lastInboundAt: number | null;
|
|
25
|
+
lastAckAt: number | null;
|
|
26
|
+
recent: Array<{
|
|
27
|
+
leaseId: string;
|
|
28
|
+
epoch: number;
|
|
29
|
+
connectedAt: number;
|
|
30
|
+
lastActivityAt: number | null;
|
|
31
|
+
isPrimary: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ExtendedProtocolDiagnostics = {
|
|
36
|
+
bridgeVersion: number;
|
|
37
|
+
protocolVersion: number;
|
|
38
|
+
minClientProtocol: number;
|
|
39
|
+
features: Record<string, boolean>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type BncrExtendedDiagnostics = BncrDiagnosticsSummary & {
|
|
43
|
+
runtimeSurface?: OpenClawChannelRuntimeSurfaceDiagnostics;
|
|
9
44
|
register: {
|
|
10
45
|
bridgeId: string;
|
|
11
46
|
gatewayPid: number;
|
|
@@ -19,44 +54,51 @@ type ExtendedDiagnosticsInput = {
|
|
|
19
54
|
lastApiRebindAt: number | null;
|
|
20
55
|
apiGeneration: number;
|
|
21
56
|
traceRecent: RegisterTraceEntry[];
|
|
22
|
-
traceSummary:
|
|
23
|
-
lastDriftSnapshot:
|
|
57
|
+
traceSummary: RegisterTraceSummary;
|
|
58
|
+
lastDriftSnapshot: RegisterDriftSnapshot | null;
|
|
24
59
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
connection: ExtendedConnectionDiagnostics;
|
|
61
|
+
outbound?: BncrExtendedOutboundDiagnostics;
|
|
62
|
+
deadLetterSummary?: BncrDeadLetterDiagnosticsSummary;
|
|
63
|
+
protocol: ExtendedProtocolDiagnostics;
|
|
64
|
+
stale: BncrStaleCounterSummary;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type ExtendedDiagnosticsInput = {
|
|
68
|
+
diagnostics: BncrDiagnosticsSummary;
|
|
69
|
+
runtimeSurface?: OpenClawChannelRuntimeSurfaceDiagnostics;
|
|
70
|
+
register: {
|
|
71
|
+
bridgeId: string;
|
|
72
|
+
gatewayPid: number;
|
|
73
|
+
pluginVersion: string | null;
|
|
74
|
+
source: string | null;
|
|
75
|
+
apiInstanceId: string | null;
|
|
76
|
+
registryFingerprint: string | null;
|
|
77
|
+
registerCount: number;
|
|
78
|
+
firstRegisterAt: number | null;
|
|
79
|
+
lastRegisterAt: number | null;
|
|
80
|
+
lastApiRebindAt: number | null;
|
|
81
|
+
apiGeneration: number;
|
|
82
|
+
traceRecent: RegisterTraceEntry[];
|
|
83
|
+
traceSummary: RegisterTraceSummary;
|
|
84
|
+
lastDriftSnapshot: RegisterDriftSnapshot | null;
|
|
50
85
|
};
|
|
51
|
-
|
|
86
|
+
outbound?: BncrExtendedOutboundDiagnostics;
|
|
87
|
+
deadLetterSummary?: BncrDeadLetterDiagnosticsSummary;
|
|
88
|
+
connection: ExtendedConnectionDiagnostics;
|
|
89
|
+
protocol: ExtendedProtocolDiagnostics;
|
|
90
|
+
stale: BncrStaleCounterSummary;
|
|
52
91
|
};
|
|
53
92
|
|
|
54
|
-
export function buildExtendedDiagnostics(input: ExtendedDiagnosticsInput) {
|
|
93
|
+
export function buildExtendedDiagnostics(input: ExtendedDiagnosticsInput): BncrExtendedDiagnostics {
|
|
55
94
|
return {
|
|
56
95
|
...input.diagnostics,
|
|
57
96
|
runtimeSurface: input.runtimeSurface
|
|
58
97
|
? {
|
|
98
|
+
runtime: { ...input.runtimeSurface.runtime },
|
|
59
99
|
channel: { ...input.runtimeSurface.channel },
|
|
100
|
+
channelMedia: { ...input.runtimeSurface.channelMedia },
|
|
101
|
+
contract: { ...input.runtimeSurface.contract },
|
|
60
102
|
missing: input.runtimeSurface.missing.slice(),
|
|
61
103
|
}
|
|
62
104
|
: undefined,
|
package/src/core/logging.ts
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import type { OutboxEntry } from './types.ts';
|
|
2
2
|
|
|
3
|
+
type OutboxEnqueueMessage = {
|
|
4
|
+
type?: unknown;
|
|
5
|
+
msg?: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type OutboxEnqueuePayload = {
|
|
9
|
+
type?: unknown;
|
|
10
|
+
message?: OutboxEnqueueMessage;
|
|
11
|
+
};
|
|
12
|
+
|
|
3
13
|
export function buildOutboxEnqueueDebugInfo(args: {
|
|
4
14
|
bridgeId: string;
|
|
5
15
|
entry: OutboxEntry;
|
|
6
16
|
asString: (value: unknown) => string;
|
|
7
17
|
formatDisplayScope: (route: OutboxEntry['route']) => string;
|
|
8
18
|
}) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
19
|
+
const payload = args.entry.payload as OutboxEnqueuePayload;
|
|
20
|
+
const msg = payload?.message || {};
|
|
21
|
+
const type = args.asString(msg.type || payload?.type || 'unknown');
|
|
11
22
|
const text = args.asString(msg.msg || '');
|
|
12
23
|
return {
|
|
13
24
|
bridge: args.bridgeId,
|
|
@@ -15,6 +15,7 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
15
15
|
text: string;
|
|
16
16
|
asVoice?: boolean;
|
|
17
17
|
audioAsVoice?: boolean;
|
|
18
|
+
type?: string;
|
|
18
19
|
kind?: 'tool' | 'block' | 'final';
|
|
19
20
|
replyToId?: string;
|
|
20
21
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
@@ -36,6 +37,7 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
36
37
|
text: args.text,
|
|
37
38
|
asVoice: args.asVoice === true,
|
|
38
39
|
audioAsVoice: args.audioAsVoice === true,
|
|
40
|
+
type: args.type,
|
|
39
41
|
finalEvent: args.pushEvent,
|
|
40
42
|
replyToId:
|
|
41
43
|
normalizeOutboundReplyToId({
|
|
@@ -1,14 +1,86 @@
|
|
|
1
1
|
import type { OutboxEntry } from './types.ts';
|
|
2
2
|
|
|
3
|
+
type OutboxSummaryMeta = {
|
|
4
|
+
kind?: string;
|
|
5
|
+
asVoice?: boolean;
|
|
6
|
+
audioAsVoice?: boolean;
|
|
7
|
+
type?: unknown;
|
|
8
|
+
mediaUrl?: unknown;
|
|
9
|
+
text?: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type OutboxSummaryMessage = {
|
|
13
|
+
type?: unknown;
|
|
14
|
+
msg?: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type OutboxSummaryPayload = {
|
|
18
|
+
type?: unknown;
|
|
19
|
+
message?: OutboxSummaryMessage;
|
|
20
|
+
_meta?: OutboxSummaryMeta;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function inferMediaTypeFromUrl(raw: string): 'image' | 'video' | 'audio' | 'file' {
|
|
24
|
+
const clean = String(raw || '').split(/[?#]/, 1)[0] || '';
|
|
25
|
+
const ext = clean.includes('.') ? clean.slice(clean.lastIndexOf('.') + 1).toLowerCase() : '';
|
|
26
|
+
if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'heic', 'heif'].includes(ext)) return 'image';
|
|
27
|
+
if (['mp4', 'mov', 'mkv', 'webm', 'avi', 'm4v'].includes(ext)) return 'video';
|
|
28
|
+
if (['mp3', 'wav', 'ogg', 'oga', 'opus', 'm4a', 'aac', 'flac'].includes(ext)) return 'audio';
|
|
29
|
+
return 'file';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function filenameFromUrl(raw: string): string {
|
|
33
|
+
const clean = String(raw || '').split(/[?#]/, 1)[0] || '';
|
|
34
|
+
const name = clean.split(/[\\/]/).filter(Boolean).pop() || '';
|
|
35
|
+
if (!name) return '';
|
|
36
|
+
try {
|
|
37
|
+
return decodeURIComponent(name);
|
|
38
|
+
} catch {
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function summarizeOutboxType(
|
|
44
|
+
payload: OutboxSummaryPayload,
|
|
45
|
+
msg: OutboxSummaryMessage,
|
|
46
|
+
asString: (value: unknown) => string,
|
|
47
|
+
) {
|
|
48
|
+
const directType = asString(msg.type || '');
|
|
49
|
+
if (directType) return directType;
|
|
50
|
+
|
|
51
|
+
const meta = payload?._meta || {};
|
|
52
|
+
if (meta.kind === 'file-transfer') {
|
|
53
|
+
if (meta.asVoice === true || meta.audioAsVoice === true) return 'voice';
|
|
54
|
+
const hintedType = asString(meta.type || '').trim();
|
|
55
|
+
if (hintedType) return hintedType;
|
|
56
|
+
return inferMediaTypeFromUrl(asString(meta.mediaUrl || ''));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return asString(payload?.type || 'unknown');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function summarizeOutboxText(
|
|
63
|
+
payload: OutboxSummaryPayload,
|
|
64
|
+
msg: OutboxSummaryMessage,
|
|
65
|
+
asString: (value: unknown) => string,
|
|
66
|
+
) {
|
|
67
|
+
const text = asString(msg.msg || payload?._meta?.text || '').trim();
|
|
68
|
+
if (text) return text;
|
|
69
|
+
const meta = payload?._meta || {};
|
|
70
|
+
if (meta.kind === 'file-transfer') return filenameFromUrl(asString(meta.mediaUrl || ''));
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
3
74
|
export function summarizeOutboxEntry(args: {
|
|
4
75
|
entry: OutboxEntry;
|
|
5
76
|
asString: (value: unknown) => string;
|
|
6
77
|
formatDisplayScope: (route: OutboxEntry['route']) => string;
|
|
7
78
|
summarizeTextPreview: (raw: string, limit?: number) => string;
|
|
8
79
|
}) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
80
|
+
const payload = args.entry.payload as OutboxSummaryPayload;
|
|
81
|
+
const msg = payload?.message || {};
|
|
82
|
+
const type = summarizeOutboxType(payload, msg, args.asString);
|
|
83
|
+
const text = summarizeOutboxText(payload, msg, args.asString);
|
|
12
84
|
const preview = args.summarizeTextPreview(text);
|
|
13
85
|
return [type, args.formatDisplayScope(args.entry.route), preview].join('|');
|
|
14
86
|
}
|
package/src/core/permissions.ts
CHANGED
|
@@ -4,7 +4,18 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
4
4
|
return String(v);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type BncrElevatedConfigRoot = {
|
|
8
|
+
tools?: {
|
|
9
|
+
elevated?: {
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
allowFrom?: {
|
|
12
|
+
bncr?: unknown;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function getBncrElevatedConfig(rootCfg: BncrElevatedConfigRoot | null | undefined) {
|
|
8
19
|
const elevated = rootCfg?.tools?.elevated || {};
|
|
9
20
|
const allowFrom = elevated?.allowFrom || {};
|
|
10
21
|
const bncrRules = Array.isArray(allowFrom?.bncr)
|
|
@@ -18,7 +29,7 @@ export function getBncrElevatedConfig(rootCfg: any) {
|
|
|
18
29
|
};
|
|
19
30
|
}
|
|
20
31
|
|
|
21
|
-
export function buildBncrPermissionSummary(rootCfg:
|
|
32
|
+
export function buildBncrPermissionSummary(rootCfg: BncrElevatedConfigRoot | null | undefined) {
|
|
22
33
|
const elevated = getBncrElevatedConfig(rootCfg);
|
|
23
34
|
return {
|
|
24
35
|
elevatedEnabled: elevated.enabled,
|
|
@@ -29,3 +40,5 @@ export function buildBncrPermissionSummary(rootCfg: any) {
|
|
|
29
40
|
: 'bncr elevated not explicitly allowed',
|
|
30
41
|
};
|
|
31
42
|
}
|
|
43
|
+
|
|
44
|
+
export type BncrPermissionSummary = ReturnType<typeof buildBncrPermissionSummary>;
|
|
@@ -2,6 +2,10 @@ import { normalizeAccountId } from './accounts.ts';
|
|
|
2
2
|
import { normalizeStoredSessionKey, parseRouteLike } from './targets.ts';
|
|
3
3
|
import type { OutboxEntry } from './types.ts';
|
|
4
4
|
|
|
5
|
+
type PersistedOutboxEntryInput = Partial<OutboxEntry> & {
|
|
6
|
+
payload?: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
|
|
5
9
|
function asString(v: unknown, fallback = ''): string {
|
|
6
10
|
if (typeof v === 'string') return v;
|
|
7
11
|
if (v == null) return fallback;
|
|
@@ -20,7 +24,7 @@ function optionalFiniteNumber(value: unknown): number | undefined {
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function normalizePersistedOutboxEntry(args: {
|
|
23
|
-
entry:
|
|
27
|
+
entry: PersistedOutboxEntryInput | null | undefined;
|
|
24
28
|
canonicalAgentId: string;
|
|
25
29
|
now: () => number;
|
|
26
30
|
}): OutboxEntry | null {
|
|
@@ -32,14 +36,16 @@ export function normalizePersistedOutboxEntry(args: {
|
|
|
32
36
|
if (!normalized) return null;
|
|
33
37
|
|
|
34
38
|
const route = parseRouteLike(entry.route) || normalized.route;
|
|
35
|
-
const payload
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
const payload: Record<string, unknown> =
|
|
40
|
+
entry.payload && typeof entry.payload === 'object' ? { ...entry.payload } : {};
|
|
41
|
+
payload.sessionKey = normalized.sessionKey;
|
|
42
|
+
payload.platform = route.platform;
|
|
43
|
+
payload.groupId = route.groupId;
|
|
44
|
+
payload.userId = route.userId;
|
|
40
45
|
|
|
41
46
|
return {
|
|
42
47
|
...entry,
|
|
48
|
+
messageId: asString(entry.messageId).trim(),
|
|
43
49
|
accountId,
|
|
44
50
|
sessionKey: normalized.sessionKey,
|
|
45
51
|
route,
|
|
@@ -49,5 +55,14 @@ export function normalizePersistedOutboxEntry(args: {
|
|
|
49
55
|
nextAttemptAt: finiteNumberOr(entry.nextAttemptAt, args.now()),
|
|
50
56
|
lastAttemptAt: optionalFiniteNumber(entry.lastAttemptAt),
|
|
51
57
|
lastError: entry.lastError ? asString(entry.lastError) : undefined,
|
|
58
|
+
lastPushAt: optionalFiniteNumber(entry.lastPushAt),
|
|
59
|
+
lastPushConnId: entry.lastPushConnId ? asString(entry.lastPushConnId) : undefined,
|
|
60
|
+
lastPushClientId: entry.lastPushClientId ? asString(entry.lastPushClientId) : undefined,
|
|
61
|
+
routeAttemptConnIds: Array.isArray(entry.routeAttemptConnIds)
|
|
62
|
+
? entry.routeAttemptConnIds.map((value) => asString(value)).filter(Boolean)
|
|
63
|
+
: undefined,
|
|
64
|
+
routeAttemptRound: optionalFiniteNumber(entry.routeAttemptRound),
|
|
65
|
+
fastReroutePending: entry.fastReroutePending === true,
|
|
66
|
+
awaitingRetryPush: entry.awaitingRetryPush === true,
|
|
52
67
|
};
|
|
53
68
|
}
|
package/src/core/policy.ts
CHANGED
|
@@ -4,6 +4,18 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
4
4
|
return String(v);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export type BncrChannelPolicyConfig = {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
dmPolicy?: unknown;
|
|
10
|
+
groupPolicy?: unknown;
|
|
11
|
+
allowFrom?: unknown;
|
|
12
|
+
groupAllowFrom?: unknown;
|
|
13
|
+
requireMention?: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type BncrDmPolicy = 'open' | 'allowlist' | 'disabled' | 'pairing';
|
|
17
|
+
export type BncrGroupPolicy = 'open' | 'allowlist' | 'disabled';
|
|
18
|
+
|
|
7
19
|
function asList(v: unknown): string[] {
|
|
8
20
|
if (!Array.isArray(v)) return [];
|
|
9
21
|
return v.map((x) => asString(x).trim()).filter(Boolean);
|
|
@@ -15,18 +27,47 @@ function asBoolean(v: unknown, fallback = false): boolean {
|
|
|
15
27
|
return String(v).trim().toLowerCase() === 'true';
|
|
16
28
|
}
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
function normalizeDmPolicy(value: unknown): BncrDmPolicy {
|
|
31
|
+
const normalized = asString(value || 'open')
|
|
32
|
+
.trim()
|
|
33
|
+
.toLowerCase();
|
|
34
|
+
switch (normalized) {
|
|
35
|
+
case 'allowlist':
|
|
36
|
+
case 'disabled':
|
|
37
|
+
case 'pairing':
|
|
38
|
+
return normalized;
|
|
39
|
+
default:
|
|
40
|
+
return 'open';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeGroupPolicy(value: unknown): BncrGroupPolicy {
|
|
45
|
+
const normalized = asString(value || 'open')
|
|
46
|
+
.trim()
|
|
47
|
+
.toLowerCase();
|
|
48
|
+
switch (normalized) {
|
|
49
|
+
case 'allowlist':
|
|
50
|
+
case 'disabled':
|
|
51
|
+
return normalized;
|
|
52
|
+
default:
|
|
53
|
+
return 'open';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function resolveBncrChannelPolicy(channelCfg: BncrChannelPolicyConfig | null | undefined) {
|
|
19
58
|
return {
|
|
20
59
|
enabled: channelCfg?.enabled !== false,
|
|
21
|
-
dmPolicy:
|
|
22
|
-
groupPolicy:
|
|
60
|
+
dmPolicy: normalizeDmPolicy(channelCfg?.dmPolicy),
|
|
61
|
+
groupPolicy: normalizeGroupPolicy(channelCfg?.groupPolicy),
|
|
23
62
|
allowFrom: asList(channelCfg?.allowFrom),
|
|
24
63
|
groupAllowFrom: asList(channelCfg?.groupAllowFrom),
|
|
25
64
|
requireMention: asBoolean(channelCfg?.requireMention, false),
|
|
26
65
|
};
|
|
27
66
|
}
|
|
28
67
|
|
|
29
|
-
export function resolveBncrConfigWarnings(
|
|
68
|
+
export function resolveBncrConfigWarnings(
|
|
69
|
+
channelCfg: BncrChannelPolicyConfig | null | undefined,
|
|
70
|
+
): string[] {
|
|
30
71
|
const policy = resolveBncrChannelPolicy(channelCfg || {});
|
|
31
72
|
const warnings: string[] = [];
|
|
32
73
|
if (policy.requireMention) {
|