@xmoxmo/bncr 0.6.7 → 0.6.9
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/dist/index.js +1386 -206
- package/index.ts +291 -131
- package/package.json +1 -1
- package/scripts/check-pack.mjs +2 -0
- package/scripts/selfcheck.mjs +2 -0
- package/src/bootstrap/channel-plugin-runtime.ts +28 -7
- package/src/bootstrap/register-lifecycle.ts +492 -0
- package/src/bootstrap/register-runtime-gateway.ts +64 -3
- package/src/bootstrap/register-runtime-helpers.ts +8 -44
- package/src/bootstrap/register-runtime-observation.ts +170 -0
- package/src/bootstrap/register-runtime-singleton.ts +44 -40
- package/src/bootstrap/register-runtime.ts +918 -15
- package/src/channel.ts +32 -2
- package/src/messaging/outbound/session-route.ts +4 -6
- package/src/plugin/bridge-lifecycle.ts +13 -2
- package/src/plugin/channel-plugin-bridge-group.ts +2 -8
- package/src/plugin/channel-plugin-surface-group.ts +2 -3
- package/src/plugin/messaging.ts +9 -32
- package/src/plugin/scene-registry.ts +14 -3
package/src/channel.ts
CHANGED
|
@@ -201,6 +201,7 @@ import {
|
|
|
201
201
|
createBncrOutboxDiagnosticsHelpers,
|
|
202
202
|
createBncrRuntimeAckObservabilityBuilder,
|
|
203
203
|
} from './plugin/runtime-diagnostics-snapshot.ts';
|
|
204
|
+
import { buildRouteSceneKey } from './plugin/scene-registry.ts';
|
|
204
205
|
import { BNCR_SETUP_SURFACE } from './plugin/setup.ts';
|
|
205
206
|
import type { BncrHistoryShardRow } from './plugin/sqlite-state.ts';
|
|
206
207
|
import { createBncrStateTransientRuntimeGroup } from './plugin/state-transient-runtime-group.ts';
|
|
@@ -309,6 +310,12 @@ class BncrBridgeRuntime {
|
|
|
309
310
|
getSceneRegistry(): Map<string, BncrSceneRecord> {
|
|
310
311
|
return this.sceneRegistry;
|
|
311
312
|
}
|
|
313
|
+
resolveOutboundAgentId(route: BncrRoute): string {
|
|
314
|
+
return (
|
|
315
|
+
asString(this.sceneRegistry.get(buildRouteSceneKey(route))?.agentId).trim() ||
|
|
316
|
+
this.defaultPublicAgentId()
|
|
317
|
+
);
|
|
318
|
+
}
|
|
312
319
|
private conversationHistories: BncrConversationHistoryMap = new Map();
|
|
313
320
|
private outboundReplayCache: BncrOutboundReplayCache = new Map();
|
|
314
321
|
private routeAliases = new Map<
|
|
@@ -348,6 +355,7 @@ class BncrBridgeRuntime {
|
|
|
348
355
|
// 内置健康/回归计数(替代独立脚本)
|
|
349
356
|
private startedAt = now();
|
|
350
357
|
private stopped = false;
|
|
358
|
+
private serviceStarted = false;
|
|
351
359
|
private connectEventsByAccount = new Map<string, number>();
|
|
352
360
|
private inboundEventsByAccount = new Map<string, number>();
|
|
353
361
|
private activityEventsByAccount = new Map<string, number>();
|
|
@@ -840,8 +848,21 @@ class BncrBridgeRuntime {
|
|
|
840
848
|
return BNCR_DEBUG_VERBOSE;
|
|
841
849
|
}
|
|
842
850
|
|
|
843
|
-
|
|
844
|
-
|
|
851
|
+
getRuntimeObservation() {
|
|
852
|
+
const activeChannelAccounts = this.channelAccountWorkers.size;
|
|
853
|
+
return {
|
|
854
|
+
serviceRunning: this.serviceStarted,
|
|
855
|
+
channelRunning: activeChannelAccounts > 0,
|
|
856
|
+
activeChannelAccounts,
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
startService = async (
|
|
861
|
+
ctx: OpenClawPluginServiceContext,
|
|
862
|
+
debug?: boolean,
|
|
863
|
+
isStartCurrent?: () => boolean,
|
|
864
|
+
) => {
|
|
865
|
+
const started = await startBncrBridgeService(
|
|
845
866
|
{
|
|
846
867
|
bridgeId: this.bridgeId,
|
|
847
868
|
setStopped: (value) => {
|
|
@@ -865,11 +886,19 @@ class BncrBridgeRuntime {
|
|
|
865
886
|
(((cfg as BncrChannelConfigRoot | null | undefined)?.channels || null)?.[CHANNEL_ID] as
|
|
866
887
|
| Record<string, unknown>
|
|
867
888
|
| undefined) || {},
|
|
889
|
+
resumeOutboxDrain: () => {
|
|
890
|
+
if (this.outbox.size === 0 || this.connections.size === 0) return;
|
|
891
|
+
this.schedulePushDrain(0);
|
|
892
|
+
},
|
|
893
|
+
isStartCurrent,
|
|
868
894
|
},
|
|
869
895
|
ctx,
|
|
870
896
|
debug,
|
|
871
897
|
);
|
|
898
|
+
if (!started || this.stopped || !(isStartCurrent?.() ?? true)) return false;
|
|
872
899
|
this.startHistoryShardWorker();
|
|
900
|
+
this.serviceStarted = true;
|
|
901
|
+
return true;
|
|
873
902
|
};
|
|
874
903
|
|
|
875
904
|
stopService = async () => {
|
|
@@ -887,6 +916,7 @@ class BncrBridgeRuntime {
|
|
|
887
916
|
}
|
|
888
917
|
|
|
889
918
|
private cleanupRuntimeWaitersAndTimers(reason: string) {
|
|
919
|
+
this.serviceStarted = false;
|
|
890
920
|
this.stopHistoryShardWorker(reason);
|
|
891
921
|
this.clientRpcRuntime.shutdown();
|
|
892
922
|
cleanupBncrBridgeRuntime(
|
|
@@ -27,12 +27,11 @@ type BncrOutboundSessionRoute = ChannelOutboundSessionRoute & BncrChannelRouteRe
|
|
|
27
27
|
type ResolveBncrOutboundSessionRouteParams = {
|
|
28
28
|
cfg: BncrChannelConfigRoot;
|
|
29
29
|
channel: string;
|
|
30
|
-
agentId: string;
|
|
31
30
|
accountId?: string;
|
|
32
31
|
target: string;
|
|
33
32
|
resolvedTarget?: { to?: string } | null;
|
|
34
33
|
threadId?: string | number | null;
|
|
35
|
-
|
|
34
|
+
resolveAgentId: (route: BncrRoute) => string;
|
|
36
35
|
resolveRouteBySession?: (raw: string, accountId: string) => BncrRoute | null;
|
|
37
36
|
};
|
|
38
37
|
|
|
@@ -76,15 +75,14 @@ export function resolveBncrOutboundSessionRoute(params: ResolveBncrOutboundSessi
|
|
|
76
75
|
|
|
77
76
|
if (!route) return null;
|
|
78
77
|
|
|
79
|
-
const
|
|
80
|
-
asString(params.canonicalAgentId).trim() || asString(params.agentId).trim() || 'main';
|
|
78
|
+
const agentId = asString(params.resolveAgentId(route)).trim() || 'public';
|
|
81
79
|
const peerId = routeScopeToHex(route);
|
|
82
|
-
const sessionKey = buildCanonicalBncrSessionKey(route,
|
|
80
|
+
const sessionKey = buildCanonicalBncrSessionKey(route, agentId);
|
|
83
81
|
const displayTo = formatDisplayScope(route);
|
|
84
82
|
|
|
85
83
|
const built = buildOpenClawChannelOutboundSessionRoute({
|
|
86
84
|
cfg: params.cfg,
|
|
87
|
-
agentId
|
|
85
|
+
agentId,
|
|
88
86
|
channel: params.channel,
|
|
89
87
|
accountId: params.accountId,
|
|
90
88
|
peer: {
|
|
@@ -59,13 +59,15 @@ export function startBncrBridgeService(
|
|
|
59
59
|
getChannelConfigRoot: (
|
|
60
60
|
cfg: BncrChannelConfigRoot,
|
|
61
61
|
) => BncrChannelPolicyConfig | null | undefined;
|
|
62
|
+
resumeOutboxDrain?: () => void;
|
|
63
|
+
isStartCurrent?: () => boolean;
|
|
62
64
|
},
|
|
63
65
|
ctx: OpenClawPluginServiceContext,
|
|
64
66
|
debug?: boolean,
|
|
65
67
|
) {
|
|
66
|
-
runtime.setStopped(false);
|
|
67
68
|
runtime.setStatePath(path.join(ctx.stateDir, 'bncr-bridge-state.json'));
|
|
68
69
|
return (async () => {
|
|
70
|
+
const isStartCurrent = () => runtime.isStartCurrent?.() ?? true;
|
|
69
71
|
try {
|
|
70
72
|
const cfg = runtime.getRuntimeConfig();
|
|
71
73
|
runtime.initializeCanonicalAgentId(cfg);
|
|
@@ -76,8 +78,10 @@ export function startBncrBridgeService(
|
|
|
76
78
|
// ignore startup canonical agent initialization errors
|
|
77
79
|
}
|
|
78
80
|
await runtime.loadState();
|
|
81
|
+
if (!isStartCurrent()) return false;
|
|
79
82
|
if (process.env.BNCR_SQLITE_CUTOVER === '1' && runtime.cutoverToSqlite) {
|
|
80
83
|
const cutover = await runtime.cutoverToSqlite();
|
|
84
|
+
if (!isStartCurrent()) return false;
|
|
81
85
|
runtime.logInfo(
|
|
82
86
|
'sqlite',
|
|
83
87
|
`cutover completed backup=${cutover.backupPath || 'none'} storeMode=${cutover.storeMode}`,
|
|
@@ -85,6 +89,8 @@ export function startBncrBridgeService(
|
|
|
85
89
|
}
|
|
86
90
|
if (typeof debug === 'boolean') runtime.setDebugFlag(debug);
|
|
87
91
|
await runtime.refreshDebugFlagFromConfig({ forceLog: true });
|
|
92
|
+
if (!isStartCurrent()) return false;
|
|
93
|
+
runtime.setStopped(false);
|
|
88
94
|
const bootDiag = runtime.buildIntegratedDiagnostics(BNCR_DEFAULT_ACCOUNT_ID);
|
|
89
95
|
runtime.logInfo(
|
|
90
96
|
'startup',
|
|
@@ -95,7 +101,12 @@ export function startBncrBridgeService(
|
|
|
95
101
|
`service started bridge=${runtime.bridgeId} diag.ok=${bootDiag.regression.ok} routes=${bootDiag.regression.totalKnownRoutes} pending=${bootDiag.health.pending} dead=${bootDiag.health.deadLetter}`,
|
|
96
102
|
{ debugOnly: true },
|
|
97
103
|
);
|
|
98
|
-
|
|
104
|
+
runtime.resumeOutboxDrain?.();
|
|
105
|
+
return true;
|
|
106
|
+
})().catch((error) => {
|
|
107
|
+
runtime.setStopped(true);
|
|
108
|
+
throw error;
|
|
109
|
+
});
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
export function cleanupBncrBridgeRuntime(
|
|
@@ -34,6 +34,7 @@ export type BncrChannelPluginBridge = {
|
|
|
34
34
|
channelId: string;
|
|
35
35
|
peer: { kind: 'direct'; id: string };
|
|
36
36
|
}) => string;
|
|
37
|
+
resolveOutboundAgentId: (route: BncrRoute) => string;
|
|
37
38
|
resolveRouteBySession: (raw: string, accountId: string) => BncrRoute | null;
|
|
38
39
|
getChannelSummary: (defaultAccountId: string) => Record<string, unknown>;
|
|
39
40
|
getAccountRuntimeSnapshot: (accountId: string) => BncrStatusRuntimeSnapshot;
|
|
@@ -90,14 +91,7 @@ export function createBncrChannelPluginBridgeGroup(runtime: {
|
|
|
90
91
|
const getMessagingBridge = () => {
|
|
91
92
|
const bridge = runtime.getBridge();
|
|
92
93
|
return {
|
|
93
|
-
|
|
94
|
-
ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) =>
|
|
95
|
-
bridge.ensureCanonicalAgentId({
|
|
96
|
-
cfg: params.cfg,
|
|
97
|
-
accountId: params.accountId,
|
|
98
|
-
channelId: runtime.channelId,
|
|
99
|
-
peer: { kind: 'direct', id: params.accountId },
|
|
100
|
-
}),
|
|
94
|
+
resolveOutboundAgentId: (route: BncrRoute) => bridge.resolveOutboundAgentId(route),
|
|
101
95
|
resolveRouteBySession: (raw: string, accountId: string) =>
|
|
102
96
|
bridge.resolveRouteBySession(raw, accountId),
|
|
103
97
|
};
|
|
@@ -3,13 +3,13 @@ import { Type } from 'typebox';
|
|
|
3
3
|
import { listAccountIds, normalizeAccountId, resolveAccount } from '../core/accounts.ts';
|
|
4
4
|
import { BncrConfigSchema } from '../core/config-schema.ts';
|
|
5
5
|
import { resolveBncrChannelPolicy } from '../core/policy.ts';
|
|
6
|
+
import type { BncrRoute } from '../core/types.ts';
|
|
6
7
|
import type { ReplyPayloadInput } from '../messaging/outbound/reply-enqueue.ts';
|
|
7
8
|
import type { OutboundReplyTargetPolicy } from '../messaging/outbound/reply-target-policy.ts';
|
|
8
9
|
import type { OpenClawChannelToolSend, openClawJsonResult } from '../openclaw/sdk-helpers.ts';
|
|
9
10
|
import { readOpenClawStringParam } from '../openclaw/sdk-helpers.ts';
|
|
10
11
|
import type { BncrBridgeCallBridge } from './bridge-call.ts';
|
|
11
12
|
import type {
|
|
12
|
-
BncrChannelConfigRoot,
|
|
13
13
|
BncrChannelSendContext,
|
|
14
14
|
BncrMessageToolSchemaContribution,
|
|
15
15
|
BncrSceneRecord,
|
|
@@ -81,8 +81,7 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
|
|
|
81
81
|
};
|
|
82
82
|
getOutboundBridge: () => BncrOutboundBridge;
|
|
83
83
|
getMessagingBridge: () => {
|
|
84
|
-
|
|
85
|
-
ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) => string;
|
|
84
|
+
resolveOutboundAgentId: (route: BncrRoute) => string;
|
|
86
85
|
resolveRouteBySession: (raw: string, accountId: string) => BncrVerifiedTarget['route'] | null;
|
|
87
86
|
};
|
|
88
87
|
getStatusBridge: () => {
|
package/src/plugin/messaging.ts
CHANGED
|
@@ -15,8 +15,7 @@ function formatBncrHumanDisplay(route: BncrRoute): string {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
type BncrMessagingRuntimeBridge = {
|
|
18
|
-
|
|
19
|
-
ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) => string;
|
|
18
|
+
resolveOutboundAgentId: (route: BncrRoute) => string;
|
|
20
19
|
resolveRouteBySession: (raw: string, accountId: string) => BncrRoute | null;
|
|
21
20
|
};
|
|
22
21
|
|
|
@@ -44,6 +43,7 @@ type BncrMessagingSessionTargetArgs = {
|
|
|
44
43
|
};
|
|
45
44
|
|
|
46
45
|
type BncrMessagingOutboundSessionRouteArgs = {
|
|
46
|
+
/** Host-reported owner. BNCR resolves ownership from its persisted scene registry instead. */
|
|
47
47
|
agentId: string;
|
|
48
48
|
target: string;
|
|
49
49
|
resolvedTarget?: { to?: string } | null;
|
|
@@ -99,21 +99,9 @@ function resolveMessagingAccountId(accountId: unknown) {
|
|
|
99
99
|
return normalizeAccountId(asSanitizedString(accountId || BNCR_DEFAULT_ACCOUNT_ID));
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function
|
|
103
|
-
runtimeBridge: BncrMessagingRuntimeBridge,
|
|
104
|
-
cfg: BncrChannelConfigRoot,
|
|
105
|
-
accountId: string,
|
|
106
|
-
) {
|
|
107
|
-
return runtimeBridge.canonicalAgentId || runtimeBridge.ensureCanonicalAgentId({ cfg, accountId });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function createBncrMessagingExplicitTargetParser(
|
|
111
|
-
getBridge: () => BncrMessagingRuntimeBridge,
|
|
112
|
-
) {
|
|
102
|
+
export function createBncrMessagingExplicitTargetParser() {
|
|
113
103
|
return ({ raw }: BncrMessagingExplicitTargetArgs) => {
|
|
114
|
-
const
|
|
115
|
-
const canonicalAgentId = runtimeBridge.canonicalAgentId || 'main';
|
|
116
|
-
const parsed = parseExplicitTarget(asSanitizedString(raw).trim(), { canonicalAgentId });
|
|
104
|
+
const parsed = parseExplicitTarget(asSanitizedString(raw).trim());
|
|
117
105
|
if (!parsed) return null;
|
|
118
106
|
const chatType: ChatType = parsed.route?.groupId ? 'group' : 'direct';
|
|
119
107
|
return {
|
|
@@ -125,16 +113,12 @@ export function createBncrMessagingExplicitTargetParser(
|
|
|
125
113
|
};
|
|
126
114
|
}
|
|
127
115
|
|
|
128
|
-
export function createBncrMessagingSessionTargetResolver(
|
|
129
|
-
getBridge: () => BncrMessagingRuntimeBridge,
|
|
130
|
-
) {
|
|
116
|
+
export function createBncrMessagingSessionTargetResolver() {
|
|
131
117
|
return ({ id }: BncrMessagingSessionTargetArgs) => {
|
|
132
118
|
const raw = asSanitizedString(id).trim();
|
|
133
119
|
if (!raw) return undefined;
|
|
134
|
-
const runtimeBridge = getBridge();
|
|
135
|
-
const canonicalAgentId = runtimeBridge.canonicalAgentId || 'main';
|
|
136
120
|
|
|
137
|
-
const parsed = parseExplicitTarget(raw
|
|
121
|
+
const parsed = parseExplicitTarget(raw);
|
|
138
122
|
if (!parsed) {
|
|
139
123
|
return raw || undefined;
|
|
140
124
|
}
|
|
@@ -146,17 +130,10 @@ export function createBncrMessagingOutboundSessionRouteResolver(
|
|
|
146
130
|
getBridge: () => BncrMessagingRuntimeBridge,
|
|
147
131
|
) {
|
|
148
132
|
return (params: BncrMessagingOutboundSessionRouteArgs) => {
|
|
149
|
-
const accountId = resolveMessagingAccountId(params?.accountId);
|
|
150
133
|
const runtimeBridge = getBridge();
|
|
151
|
-
const canonicalAgentId = resolveMessagingCanonicalAgentId(
|
|
152
|
-
runtimeBridge,
|
|
153
|
-
params?.cfg,
|
|
154
|
-
accountId,
|
|
155
|
-
);
|
|
156
134
|
return resolveBncrOutboundSessionRoute({
|
|
157
135
|
channel: 'bncr',
|
|
158
136
|
cfg: params.cfg,
|
|
159
|
-
agentId: params.agentId,
|
|
160
137
|
accountId: params.accountId ?? undefined,
|
|
161
138
|
target: params.target,
|
|
162
139
|
resolvedTarget: params.resolvedTarget,
|
|
@@ -164,7 +141,7 @@ export function createBncrMessagingOutboundSessionRouteResolver(
|
|
|
164
141
|
params.threadId === null || params.threadId === undefined
|
|
165
142
|
? undefined
|
|
166
143
|
: asSanitizedString(params.threadId),
|
|
167
|
-
|
|
144
|
+
resolveAgentId: (route) => runtimeBridge.resolveOutboundAgentId(route),
|
|
168
145
|
resolveRouteBySession: (raw: string, acc: string) =>
|
|
169
146
|
runtimeBridge.resolveRouteBySession(raw, acc),
|
|
170
147
|
});
|
|
@@ -175,9 +152,9 @@ export function createBncrMessagingSurface(getBridge: () => BncrMessagingRuntime
|
|
|
175
152
|
return {
|
|
176
153
|
// 接收任意标签输入;不在 normalize 阶段做格式门槛,统一下沉到发送前验证。
|
|
177
154
|
normalizeTarget: normalizeBncrMessagingTarget,
|
|
178
|
-
parseExplicitTarget: createBncrMessagingExplicitTargetParser(
|
|
155
|
+
parseExplicitTarget: createBncrMessagingExplicitTargetParser(),
|
|
179
156
|
formatTargetDisplay: formatBncrMessagingTargetDisplay,
|
|
180
|
-
resolveSessionTarget: createBncrMessagingSessionTargetResolver(
|
|
157
|
+
resolveSessionTarget: createBncrMessagingSessionTargetResolver(),
|
|
181
158
|
resolveOutboundSessionRoute: createBncrMessagingOutboundSessionRouteResolver(getBridge),
|
|
182
159
|
targetResolver: createBncrMessagingTargetResolver(getBridge),
|
|
183
160
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { BncrRoute } from '../core/types.ts';
|
|
2
|
+
import { asString } from '../core/value-sanitize.ts';
|
|
1
3
|
import type {
|
|
2
4
|
BncrGroupReplyMode,
|
|
3
5
|
BncrSceneKind,
|
|
@@ -30,10 +32,19 @@ function defaultGroupReplyMode(kind: BncrSceneKind): BncrGroupReplyMode | undefi
|
|
|
30
32
|
return kind === 'group' ? 'admin' : undefined;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
export function buildRouteSceneKey(route: BncrRoute): string {
|
|
36
|
+
const platform = asString(route.platform).trim();
|
|
37
|
+
const groupId = asString(route.groupId).trim() || '0';
|
|
38
|
+
const userId = asString(route.userId).trim() || '0';
|
|
39
|
+
return groupId !== '0' ? `${platform}:${groupId}` : `${platform}:${userId}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function buildSceneKey(parsed: ParsedInbound): string {
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
:
|
|
43
|
+
return buildRouteSceneKey({
|
|
44
|
+
platform: parsed.platform,
|
|
45
|
+
groupId: parsed.groupId || '0',
|
|
46
|
+
userId: parsed.userId || '0',
|
|
47
|
+
});
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
function buildSceneRecord(args: {
|