@xmoxmo/bncr 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.js +6 -0
  2. package/index.ts +6 -0
  3. package/package.json +1 -1
  4. package/src/channel.ts +41 -2
  5. package/src/core/targets.ts +106 -17
  6. package/src/messaging/inbound/commands.ts +263 -51
  7. package/src/messaging/inbound/context-facts.ts +126 -14
  8. package/src/messaging/inbound/contracts.ts +24 -0
  9. package/src/messaging/inbound/dispatch-prep.ts +214 -39
  10. package/src/messaging/inbound/dispatch.ts +71 -5
  11. package/src/messaging/inbound/gate.ts +56 -86
  12. package/src/messaging/inbound/group-history.ts +189 -0
  13. package/src/messaging/inbound/native-command-runtime.ts +77 -61
  14. package/src/messaging/inbound/native-command.ts +92 -8
  15. package/src/messaging/inbound/parse.ts +113 -8
  16. package/src/messaging/inbound/reply-dispatch-serial.ts +62 -0
  17. package/src/messaging/inbound/reply-dispatch.ts +252 -77
  18. package/src/messaging/inbound/scene-admin.ts +269 -0
  19. package/src/messaging/inbound/session-label.ts +122 -13
  20. package/src/messaging/inbound/session-meta-task.ts +17 -0
  21. package/src/messaging/inbound/turn-context.ts +184 -71
  22. package/src/openclaw/channel-runtime-contracts.ts +1 -0
  23. package/src/plugin/channel-components.ts +34 -1
  24. package/src/plugin/channel-inbound-helpers.ts +9 -2
  25. package/src/plugin/channel-runtime-builders-delivery.ts +24 -1
  26. package/src/plugin/channel-runtime-types.ts +42 -0
  27. package/src/plugin/file-inbound-init.ts +27 -12
  28. package/src/plugin/file-inbound-runtime.ts +2 -0
  29. package/src/plugin/inbound-acceptance.ts +82 -1
  30. package/src/plugin/inbound-handlers.ts +55 -2
  31. package/src/plugin/inbound-surface-handlers-group.ts +16 -0
  32. package/src/plugin/messaging.ts +22 -5
  33. package/src/plugin/scene-registry.ts +155 -0
  34. package/src/plugin/state-store.ts +133 -0
  35. package/src/plugin/state-transient-runtime-group.ts +5 -0
  36. package/src/plugin/target-runtime.ts +2 -2
package/dist/index.js CHANGED
@@ -840,6 +840,12 @@ var plugin = {
840
840
  owner = adopted.owner;
841
841
  previousOwner = adopted.previousOwner;
842
842
  gatewayRuntime.currentBridge = bridge;
843
+ if (rebuilt) {
844
+ gatewayRuntime.serviceRegistered = false;
845
+ gatewayRuntime.channelRegistered = false;
846
+ gatewayRuntime.serviceOwnerApiInstanceId = void 0;
847
+ gatewayRuntime.channelOwnerApiInstanceId = void 0;
848
+ }
843
849
  } else {
844
850
  runtime2 = loadBncrRuntimeSync();
845
851
  bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
package/index.ts CHANGED
@@ -101,6 +101,12 @@ const plugin = {
101
101
  owner = adopted.owner;
102
102
  previousOwner = adopted.previousOwner;
103
103
  gatewayRuntime.currentBridge = bridge;
104
+ if (rebuilt) {
105
+ gatewayRuntime.serviceRegistered = false;
106
+ gatewayRuntime.channelRegistered = false;
107
+ gatewayRuntime.serviceOwnerApiInstanceId = undefined;
108
+ gatewayRuntime.channelOwnerApiInstanceId = undefined;
109
+ }
104
110
  } else {
105
111
  runtime = loadBncrRuntimeSync();
106
112
  bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/channel.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  import { summarizeOutboxEntry } from './core/outbox-summary.ts';
27
27
  import { normalizePersistedOutboxEntry as normalizePersistedOutboxEntryFromRuntime } from './core/persisted-outbox-entry.ts';
28
28
  import {
29
+ buildCanonicalBncrSessionKey,
29
30
  formatDisplayScope,
30
31
  normalizeStoredSessionKey,
31
32
  parseRouteLike,
@@ -40,6 +41,7 @@ import type {
40
41
  FileSendTransferState,
41
42
  OutboxEntry,
42
43
  } from './core/types.ts';
44
+ import type { BncrGroupHistoryMap } from './messaging/inbound/group-history.ts';
43
45
  import type { parseBncrInboundParams } from './messaging/inbound/parse.ts';
44
46
  import { buildEnqueueFromReplyDebugInfo } from './messaging/outbound/diagnostics.ts';
45
47
  import { buildBncrMediaOutboundFrame } from './messaging/outbound/media.ts';
@@ -149,6 +151,7 @@ import type {
149
151
  BncrBridgeRuntimePaths,
150
152
  BncrChannelConfigRoot,
151
153
  BncrChannelSendContext,
154
+ BncrSceneRecord,
152
155
  FileAckPayloadState,
153
156
  PersistedState,
154
157
  } from './plugin/channel-runtime-types.ts';
@@ -284,6 +287,8 @@ class BncrBridgeRuntime {
284
287
  string,
285
288
  { accountId: string; route: BncrRoute; updatedAt: number }
286
289
  >();
290
+ private sceneRegistry = new Map<string, BncrSceneRecord>();
291
+ private groupHistories: BncrGroupHistoryMap = new Map();
287
292
  private routeAliases = new Map<
288
293
  string,
289
294
  { accountId: string; route: BncrRoute; updatedAt: number }
@@ -972,6 +977,19 @@ class BncrBridgeRuntime {
972
977
  return this.bridgeSupportRuntime.ensureCanonicalAgentId(args);
973
978
  }
974
979
 
980
+ private defaultAdminAgentId(args: {
981
+ cfg: BncrChannelConfigRoot;
982
+ accountId: string;
983
+ peer?: unknown;
984
+ channelId?: string;
985
+ }): string {
986
+ return this.ensureCanonicalAgentId(args);
987
+ }
988
+
989
+ private defaultPublicAgentId(): string {
990
+ return 'public';
991
+ }
992
+
975
993
  private countInvalidOutboxSessionKeys(accountId: string): number {
976
994
  return countInvalidOutboxSessionKeysFromRuntime({
977
995
  accountId,
@@ -1530,6 +1548,9 @@ class BncrBridgeRuntime {
1530
1548
  sessionKey: string;
1531
1549
  inboundText: string;
1532
1550
  hasMedia: boolean;
1551
+ resolvedAgentId: string;
1552
+ shouldDispatch: boolean;
1553
+ shouldAccumulate: boolean;
1533
1554
  }
1534
1555
  | {
1535
1556
  ok: false;
@@ -1542,11 +1563,20 @@ class BncrBridgeRuntime {
1542
1563
  parsed: args.parsed,
1543
1564
  canonicalAgentId: args.canonicalAgentId,
1544
1565
  asString,
1566
+ now,
1545
1567
  getRuntimeConfig: (api) => getOpenClawRuntimeConfig(api as OpenClawChannelRuntimeApiHolder),
1546
1568
  resolveAgentRoute: (params) =>
1547
1569
  resolveOpenClawAgentRoute(this.api as OpenClawChannelRuntimeApiHolder, params),
1548
1570
  buildInboundResponsePayload,
1549
1571
  markInboundDedupSeen: (key) => this.markInboundDedupSeen(key),
1572
+ sceneRegistry: this.sceneRegistry,
1573
+ defaultAdminAgentId: this.defaultAdminAgentId({
1574
+ cfg: getOpenClawRuntimeConfig(this.api as OpenClawChannelRuntimeApiHolder),
1575
+ accountId: args.parsed.accountId,
1576
+ peer: args.parsed.peer,
1577
+ channelId: CHANNEL_ID,
1578
+ }),
1579
+ defaultPublicAgentId: this.defaultPublicAgentId(),
1550
1580
  });
1551
1581
  }
1552
1582
 
@@ -1867,6 +1897,8 @@ class BncrBridgeRuntime {
1867
1897
  maxDeadLetterEntries: MAX_DEAD_LETTER_ENTRIES,
1868
1898
  maxSessionRouteEntries: MAX_SESSION_ROUTE_ENTRIES,
1869
1899
  maxAccountActivityEntries: MAX_ACCOUNT_ACTIVITY_ENTRIES,
1900
+ sceneRegistry: this.sceneRegistry,
1901
+ groupHistories: this.groupHistories,
1870
1902
  outbox: this.outbox,
1871
1903
  getDeadLetter: () => this.deadLetter,
1872
1904
  setDeadLetter: (entries) => {
@@ -2319,9 +2351,10 @@ class BncrBridgeRuntime {
2319
2351
  }
2320
2352
 
2321
2353
  // 严谨目标解析:
2322
- // 1) 标准 to 仅认 Bncr:<platform>:<groupId>:<userId> / Bncr:<platform>:<userId>
2354
+ // 1) 标准 to 仅认 Bncr:<platform>:0:<userId> / Bncr:<platform>:<groupId>:0
2323
2355
  // 2) 仍接受 strict sessionKey 作为内部兼容输入
2324
- // 3) 其他旧格式直接失败,并输出标准格式提示日志
2356
+ // 3) 输入侧额外兼容 Bncr:<platform>:user:<userId> / Bncr:<platform>:group:<groupId>
2357
+ // 4) 其他旧格式直接失败,并输出标准格式提示日志
2325
2358
  resolveVerifiedTarget(
2326
2359
  rawTarget: string,
2327
2360
  accountId: string,
@@ -2850,6 +2883,10 @@ class BncrBridgeRuntime {
2850
2883
  buildInboundAcceptedLifecycleDebugInfo,
2851
2884
  ...inboundActivityRuntime,
2852
2885
  ensureCanonicalAgentId: (args) => this.ensureCanonicalAgentId(args),
2886
+ defaultAdminAgentId: (args) => this.defaultAdminAgentId(args),
2887
+ defaultPublicAgentId: () => this.defaultPublicAgentId(),
2888
+ sceneRegistry: this.sceneRegistry,
2889
+ groupHistories: this.groupHistories,
2853
2890
  prepareInboundAcceptance: (args) => this.prepareInboundAcceptance(args),
2854
2891
  logInboundSummary: (args) => this.logInboundSummary(args),
2855
2892
  flushPushQueueBestEffort: (args) => this.flushPushQueueBestEffort(args),
@@ -2858,6 +2895,8 @@ class BncrBridgeRuntime {
2858
2895
  enqueueFromReply: (args: Parameters<BncrBridgeRuntime['enqueueFromReply']>[0]) =>
2859
2896
  this.enqueueFromReply(args),
2860
2897
  scheduleSave: () => this.scheduleSave(),
2898
+ buildCanonicalSessionKey: (route: BncrRoute) =>
2899
+ buildCanonicalBncrSessionKey(route, this.canonicalAgentId || 'main'),
2861
2900
  fileRecvTransfers: this.fileRecvTransfers,
2862
2901
  inboundFileTransferMaxBytes: INBOUND_FILE_TRANSFER_MAX_BYTES,
2863
2902
  inboundFileTransferMaxChunks: INBOUND_FILE_TRANSFER_MAX_CHUNKS,
@@ -36,6 +36,14 @@ export function asTargetString(value: unknown, fallback = ''): string {
36
36
 
37
37
  export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
38
38
  const parts = asTargetString(scope).trim().split(':');
39
+ if (parts.length === 3) {
40
+ const [platform, kind, id] = parts;
41
+ if (!platform || !kind || !id) return null;
42
+ if (kind === 'User') return { platform, groupId: '0', userId: id };
43
+ if (kind === 'Group') return { platform, groupId: id, userId: '0' };
44
+ if (kind.toLowerCase() === 'user' || kind.toLowerCase() === 'group') return null;
45
+ }
46
+
39
47
  if (parts.length === 2) {
40
48
  const [platform, userId] = parts;
41
49
  if (!platform || !userId) return null;
@@ -45,12 +53,33 @@ export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | n
45
53
  if (parts.length === 3) {
46
54
  const [platform, groupId, userId] = parts;
47
55
  if (!platform || !groupId || !userId) return null;
56
+ if (groupId !== '0' && userId !== '0') {
57
+ // Legacy group routes may still include the triggering userId; collapse them
58
+ // back to the shared group route so old targets remain deliverable.
59
+ return { platform, groupId, userId: '0' };
60
+ }
48
61
  return { platform, groupId, userId };
49
62
  }
50
63
 
51
64
  return null;
52
65
  }
53
66
 
67
+ function parseGroupScope(scope: string): { platform: string; groupId: string } | null {
68
+ const parts = asTargetString(scope).trim().split(':');
69
+ if (parts.length !== 2) return null;
70
+ const [platform, groupId] = parts;
71
+ if (!platform || !groupId) return null;
72
+ return { platform, groupId };
73
+ }
74
+
75
+ function parseDirectScope(scope: string): { platform: string; userId: string } | null {
76
+ const parts = asTargetString(scope).trim().split(':');
77
+ if (parts.length !== 2) return null;
78
+ const [platform, userId] = parts;
79
+ if (!platform || !userId) return null;
80
+ return { platform, userId };
81
+ }
82
+
54
83
  export function normalizeDisplayScopePrefix(scope: string): string {
55
84
  const raw = asTargetString(scope).trim();
56
85
  if (!raw) return '';
@@ -102,11 +131,22 @@ export function routeScopeToHex(route: BncrRoute): string {
102
131
  return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
103
132
  }
104
133
 
134
+ export function groupScopeToHex(route: BncrRoute): string {
135
+ const raw = `${route.platform}:${route.groupId}`;
136
+ return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
137
+ }
138
+
139
+ export function directScopeToHex(route: BncrRoute): string {
140
+ const raw = `${route.platform}:${route.userId}`;
141
+ return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
142
+ }
143
+
105
144
  export function parseRouteFromScope(scope: string): BncrRoute | null {
106
145
  const parts = asTargetString(scope).trim().split(':');
107
146
  if (parts.length < 3) return null;
108
147
  const [platform, groupId, userId] = parts;
109
148
  if (!platform || !groupId || !userId) return null;
149
+ if (groupId !== '0' && userId !== '0') return null;
110
150
  return { platform, groupId, userId };
111
151
  }
112
152
 
@@ -122,6 +162,34 @@ export function parseRouteFromHexScope(scopeHex: string): BncrRoute | null {
122
162
  }
123
163
  }
124
164
 
165
+ export function parseGroupRouteFromHexScope(scopeHex: string): BncrRoute | null {
166
+ const rawHex = asTargetString(scopeHex).trim();
167
+ if (!isLowerHex(rawHex)) return null;
168
+
169
+ try {
170
+ const decoded = Buffer.from(rawHex, 'hex').toString('utf8');
171
+ const parsed = parseGroupScope(decoded);
172
+ if (!parsed) return null;
173
+ return { platform: parsed.platform, groupId: parsed.groupId, userId: '0' };
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ export function parseDirectRouteFromHexScope(scopeHex: string): BncrRoute | null {
180
+ const rawHex = asTargetString(scopeHex).trim();
181
+ if (!isLowerHex(rawHex)) return null;
182
+
183
+ try {
184
+ const decoded = Buffer.from(rawHex, 'hex').toString('utf8');
185
+ const parsed = parseDirectScope(decoded);
186
+ if (!parsed) return null;
187
+ return { platform: parsed.platform, groupId: '0', userId: parsed.userId };
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
125
193
  export function parseStrictBncrSessionKey(input: string): {
126
194
  inputSessionKey: string;
127
195
  inputAgentId: string;
@@ -143,9 +211,24 @@ export function parseStrictBncrSessionKey(input: string): {
143
211
 
144
212
  if (isLowerHex(payload)) {
145
213
  scopeHex = payload.toLowerCase();
146
- route = parseRouteFromHexScope(scopeHex);
214
+ route =
215
+ inputKind === 'group'
216
+ ? parseGroupRouteFromHexScope(scopeHex)
217
+ : parseDirectRouteFromHexScope(scopeHex) || parseRouteFromHexScope(scopeHex);
147
218
  } else {
148
- route = parseRouteFromScope(payload);
219
+ route =
220
+ inputKind === 'group'
221
+ ? (() => {
222
+ const parsed = parseGroupScope(payload);
223
+ return parsed
224
+ ? { platform: parsed.platform, groupId: parsed.groupId, userId: '0' }
225
+ : null;
226
+ })()
227
+ : (() => {
228
+ const direct = parseDirectScope(payload);
229
+ if (direct) return { platform: direct.platform, groupId: '0', userId: direct.userId };
230
+ return parseRouteFromScope(payload);
231
+ })();
149
232
  if (route) scopeHex = routeScopeToHex(route);
150
233
  }
151
234
 
@@ -237,13 +320,17 @@ export function parseRouteFromDisplayScope(scope: string): BncrRoute | null {
237
320
 
238
321
  const payload = raw.match(/^Bncr:(.+)$/)?.[1];
239
322
  if (!payload) return null;
240
- return parseRouteFromStandardDisplayScope(payload);
323
+ const route = parseRouteFromStandardDisplayScope(payload);
324
+ if (!route) return null;
325
+ return route;
241
326
  }
242
327
 
243
328
  export function buildCanonicalBncrSessionKey(route: BncrRoute, canonicalAgentId: string): string {
244
329
  const agentId = asTargetString(canonicalAgentId).trim() || 'main';
245
- const kind = resolveCanonicalSessionKind();
246
- return `agent:${agentId}:bncr:${kind}:${routeScopeToHex(route)}`;
330
+ if (route.groupId !== '0') {
331
+ return `agent:${agentId}:bncr:group:${groupScopeToHex(route)}`;
332
+ }
333
+ return `agent:${agentId}:bncr:direct:${directScopeToHex(route)}`;
247
334
  }
248
335
 
249
336
  export function normalizeStoredSessionKey(
@@ -266,17 +353,17 @@ export function normalizeStoredSessionKey(
266
353
  let route: BncrRoute | null = null;
267
354
  let passthroughAgentId: string | null = null;
268
355
 
269
- const strict = parseStrictBncrSessionKey(base);
270
- if (strict) {
271
- route = strict.route;
272
- passthroughAgentId = strict.inputAgentId;
356
+ const legacy = parseLegacySessionKey(base);
357
+ if (legacy) {
358
+ route = legacy.route;
359
+ passthroughAgentId = legacy.inputAgentId || null;
273
360
  }
274
361
 
275
362
  if (!route) {
276
- const legacy = parseLegacySessionKey(base);
277
- if (legacy) {
278
- route = legacy.route;
279
- passthroughAgentId = legacy.inputAgentId || null;
363
+ const strict = parseStrictBncrSessionKey(base);
364
+ if (strict) {
365
+ route = strict.route;
366
+ passthroughAgentId = strict.inputAgentId;
280
367
  }
281
368
  }
282
369
 
@@ -322,6 +409,7 @@ export function parseRouteLike(input: unknown): BncrRoute | null {
322
409
  const groupId = asTargetString(routeInput?.groupId || '').trim();
323
410
  const userId = asTargetString(routeInput?.userId || '').trim();
324
411
  if (!platform || !groupId || !userId) return null;
412
+ if (groupId !== '0' && userId !== '0') return null;
325
413
  return { platform, groupId, userId };
326
414
  }
327
415
 
@@ -333,10 +421,11 @@ export type BncrExplicitTargetSource =
333
421
  | 'route-scope';
334
422
 
335
423
  export function formatDisplayScope(route: BncrRoute): string {
336
- if (route.groupId === '0' && route.userId !== '0') {
337
- return `Bncr:${route.platform}:${route.userId}`;
338
- }
339
- return `Bncr:${route.platform}:${route.groupId}:${route.userId}`;
424
+ const platform = asTargetString(route?.platform).trim();
425
+ const groupId = asTargetString(route?.groupId || '0').trim() || '0';
426
+ const userId = asTargetString(route?.userId || '0').trim() || '0';
427
+ if (groupId !== '0') return `Bncr:${platform}:${groupId}:0`;
428
+ return `Bncr:${platform}:0:${userId}`;
340
429
  }
341
430
 
342
431
  export function buildDisplayScopeCandidates(route: BncrRoute): string[] {