@xmoxmo/bncr 0.4.6 → 0.4.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.
Files changed (35) hide show
  1. package/README.md +8 -0
  2. package/package.json +1 -1
  3. package/src/channel.ts +41 -2
  4. package/src/core/targets.ts +106 -17
  5. package/src/messaging/inbound/commands.ts +421 -51
  6. package/src/messaging/inbound/context-facts.ts +126 -14
  7. package/src/messaging/inbound/contracts.ts +24 -0
  8. package/src/messaging/inbound/dispatch-prep.ts +214 -39
  9. package/src/messaging/inbound/dispatch.ts +71 -5
  10. package/src/messaging/inbound/gate.ts +56 -86
  11. package/src/messaging/inbound/group-history.ts +189 -0
  12. package/src/messaging/inbound/native-command-runtime.ts +77 -61
  13. package/src/messaging/inbound/native-command.ts +192 -8
  14. package/src/messaging/inbound/parse.ts +113 -8
  15. package/src/messaging/inbound/reply-dispatch-serial.ts +62 -0
  16. package/src/messaging/inbound/reply-dispatch.ts +252 -77
  17. package/src/messaging/inbound/scene-admin.ts +300 -0
  18. package/src/messaging/inbound/session-label.ts +122 -13
  19. package/src/messaging/inbound/session-meta-task.ts +17 -0
  20. package/src/messaging/inbound/turn-context.ts +184 -71
  21. package/src/openclaw/channel-runtime-contracts.ts +1 -0
  22. package/src/plugin/channel-components.ts +35 -1
  23. package/src/plugin/channel-inbound-helpers.ts +9 -2
  24. package/src/plugin/channel-runtime-builders-delivery.ts +24 -1
  25. package/src/plugin/channel-runtime-types.ts +42 -0
  26. package/src/plugin/file-inbound-init.ts +27 -12
  27. package/src/plugin/file-inbound-runtime.ts +2 -0
  28. package/src/plugin/inbound-acceptance.ts +82 -1
  29. package/src/plugin/inbound-handlers.ts +97 -2
  30. package/src/plugin/inbound-surface-handlers-group.ts +16 -0
  31. package/src/plugin/messaging.ts +22 -5
  32. package/src/plugin/scene-registry.ts +171 -0
  33. package/src/plugin/state-store.ts +133 -0
  34. package/src/plugin/state-transient-runtime-group.ts +5 -0
  35. package/src/plugin/target-runtime.ts +2 -2
@@ -7,6 +7,7 @@ import type {
7
7
  FileSendTransferState,
8
8
  OutboxEntry,
9
9
  } from '../core/types.ts';
10
+ import type { BncrGroupHistoryMap } from '../messaging/inbound/group-history.ts';
10
11
  import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
11
12
  import type {
12
13
  NormalizedReplyPayload,
@@ -18,7 +19,11 @@ import type {
18
19
  buildInboundAcceptedLifecycleDebugInfo as buildInboundAcceptedLifecycleDebugInfoFromRuntime,
19
20
  buildInboundResponsePayload as buildInboundResponsePayloadFromRuntime,
20
21
  } from './channel-inbound-helpers.ts';
21
- import type { BncrChannelConfigRoot, BncrVerifiedTarget } from './channel-runtime-types.ts';
22
+ import type {
23
+ BncrChannelConfigRoot,
24
+ BncrSceneRecord,
25
+ BncrVerifiedTarget,
26
+ } from './channel-runtime-types.ts';
22
27
  import type { LeaseEventPayload } from './connection-handlers.ts';
23
28
  import type { BncrActiveConnectionDebugEntry } from './connection-state.ts';
24
29
  import type { FileAckPayloadState, FileAckWaiter } from './file-ack-runtime.ts';
@@ -121,6 +126,11 @@ export function buildBncrStateTransientRuntime(deps: {
121
126
  maxDeadLetterEntries: number;
122
127
  maxSessionRouteEntries: number;
123
128
  maxAccountActivityEntries: number;
129
+ sceneRegistry: Map<string, BncrSceneRecord>;
130
+ groupHistories: Map<
131
+ string,
132
+ import('./channel-runtime-types.ts').BncrPersistedGroupHistoryEntry[]
133
+ >;
124
134
  outbox: Map<string, OutboxEntry>;
125
135
  getDeadLetter: () => OutboxEntry[];
126
136
  setDeadLetter: (entries: OutboxEntry[]) => void;
@@ -446,6 +456,15 @@ export function buildBncrInboundSurfaceRuntime(deps: {
446
456
  peer?: unknown;
447
457
  channelId?: string;
448
458
  }) => string;
459
+ defaultAdminAgentId: (args: {
460
+ cfg: BncrChannelConfigRoot;
461
+ accountId: string;
462
+ peer?: unknown;
463
+ channelId?: string;
464
+ }) => string;
465
+ defaultPublicAgentId: () => string;
466
+ sceneRegistry: Map<string, BncrSceneRecord>;
467
+ groupHistories: BncrGroupHistoryMap;
449
468
  prepareInboundAcceptance: (args: {
450
469
  parsed: ReturnType<typeof parseBncrInboundParams>;
451
470
  canonicalAgentId: string;
@@ -456,6 +475,9 @@ export function buildBncrInboundSurfaceRuntime(deps: {
456
475
  sessionKey: string;
457
476
  inboundText: string;
458
477
  hasMedia: boolean;
478
+ resolvedAgentId: string;
479
+ shouldDispatch: boolean;
480
+ shouldAccumulate: boolean;
459
481
  }
460
482
  | {
461
483
  ok: false;
@@ -486,6 +508,7 @@ export function buildBncrInboundSurfaceRuntime(deps: {
486
508
  }) => Promise<void>;
487
509
  setInboundActivity: (accountId: string, at: number) => void;
488
510
  scheduleSave: () => void;
511
+ buildCanonicalSessionKey: (route: BncrRoute) => string;
489
512
  fileRecvTransfers: Parameters<typeof createBncrFileInboundHandlers>[0]['fileRecvTransfers'];
490
513
  inboundFileTransferMaxBytes: number;
491
514
  inboundFileTransferMaxChunks: number;
@@ -44,6 +44,46 @@ export type BncrPersistedLastSession = {
44
44
  updatedAt: number;
45
45
  };
46
46
 
47
+ export type BncrSceneKind = 'direct' | 'group';
48
+
49
+ export type BncrSceneStatus = 'pending' | 'allowed' | 'denied';
50
+
51
+ export type BncrGroupReplyMode = 'admin' | 'mention' | 'hybrid' | 'all';
52
+
53
+ export type BncrSceneRecord = {
54
+ sceneKey: string;
55
+ kind: BncrSceneKind;
56
+ status: BncrSceneStatus;
57
+ platform: string;
58
+ userId?: string;
59
+ userName?: string;
60
+ groupId?: string;
61
+ groupName?: string;
62
+ agentId?: string;
63
+ groupReplyMode?: BncrGroupReplyMode;
64
+ lastSeenAt: number;
65
+ };
66
+
67
+ export type BncrPersistedGroupHistoryEntry = {
68
+ sender: string;
69
+ body: string;
70
+ timestamp?: number;
71
+ messageId?: string;
72
+ media?: BncrPersistedGroupHistoryMediaEntry[];
73
+ };
74
+
75
+ export type BncrPersistedGroupHistoryMediaEntry = {
76
+ path?: string;
77
+ contentType?: string;
78
+ kind?: 'image' | 'video' | 'audio' | 'document' | 'unknown';
79
+ messageId?: string;
80
+ };
81
+
82
+ export type BncrPersistedGroupHistoryBucket = {
83
+ key: string;
84
+ entries: BncrPersistedGroupHistoryEntry[];
85
+ };
86
+
47
87
  export type BncrStatusRuntimeSnapshot = {
48
88
  connected?: boolean;
49
89
  running?: boolean;
@@ -133,6 +173,8 @@ export type PersistedState = {
133
173
  outbox: OutboxEntry[];
134
174
  deadLetter: OutboxEntry[];
135
175
  sessionRoutes: BncrPersistedSessionRoute[];
176
+ sceneRegistry?: BncrSceneRecord[];
177
+ groupHistories?: BncrPersistedGroupHistoryBucket[];
136
178
  lastSessionByAccount?: BncrPersistedLastSession[];
137
179
  lastActivityByAccount?: BncrPersistedAccountTimestamp[];
138
180
  lastInboundByAccount?: BncrPersistedAccountTimestamp[];
@@ -45,34 +45,49 @@ export function createBncrFileInboundInitHandler(runtime: BncrFileInboundRuntime
45
45
  const chunkSize = runtime.finiteNonNegativeNumberOrNull(params?.chunkSize ?? 256 * 1024);
46
46
  const totalChunks = runtime.finiteNonNegativeNumberOrNull(params?.totalChunks);
47
47
  const fileSha256 = runtime.asString(params?.fileSha256 || '').trim();
48
+ const routeFromParams = runtime.parseRouteLike({
49
+ platform: runtime.asString(params?.platform || '').trim(),
50
+ groupId: runtime.asString(params?.groupId || '0').trim() || '0',
51
+ userId: runtime.asString(params?.userId || '0').trim() || '0',
52
+ });
53
+
54
+ const reject = (error: string) => {
55
+ runtime.logWarn(
56
+ 'file',
57
+ `file.init reject accountId=${accountId} connId=${connId} clientId=${clientId || '-'} transferId=${transferId || '-'} error=${error} sessionKey=${sessionKey || '-'} fileSize=${fileSize ?? 'null'} chunkSize=${chunkSize ?? 'null'} totalChunks=${totalChunks ?? 'null'}`,
58
+ );
59
+ respond(false, { error });
60
+ };
48
61
 
49
62
  if (!transferId || !sessionKey || !fileSize || !chunkSize || !totalChunks) {
50
- respond(false, { error: 'transferId/sessionKey/fileSize/chunkSize/totalChunks required' });
63
+ reject('transferId/sessionKey/fileSize/chunkSize/totalChunks required');
51
64
  return;
52
65
  }
53
66
  if (fileSize > runtime.inboundFileTransferMaxBytes) {
54
- respond(false, {
55
- error: `fileSize too large size=${fileSize} max=${runtime.inboundFileTransferMaxBytes}`,
56
- });
67
+ reject(`fileSize too large size=${fileSize} max=${runtime.inboundFileTransferMaxBytes}`);
57
68
  return;
58
69
  }
59
70
  if (totalChunks > runtime.inboundFileTransferMaxChunks) {
60
- respond(false, {
61
- error: `totalChunks too large total=${totalChunks} max=${runtime.inboundFileTransferMaxChunks}`,
62
- });
71
+ reject(
72
+ `totalChunks too large total=${totalChunks} max=${runtime.inboundFileTransferMaxChunks}`,
73
+ );
63
74
  return;
64
75
  }
65
76
  const expectedTotalChunks = Math.ceil(fileSize / chunkSize);
66
77
  if (totalChunks !== expectedTotalChunks) {
67
- respond(false, {
68
- error: `totalChunks mismatch total=${totalChunks} expected=${expectedTotalChunks}`,
69
- });
78
+ reject(`totalChunks mismatch total=${totalChunks} expected=${expectedTotalChunks}`);
70
79
  return;
71
80
  }
72
81
 
73
- const normalized = runtime.normalizeStoredSessionKey(sessionKey);
82
+ let normalized = runtime.normalizeStoredSessionKey(sessionKey);
83
+ if (!normalized && routeFromParams) {
84
+ normalized = {
85
+ sessionKey: runtime.buildCanonicalSessionKey(routeFromParams),
86
+ route: routeFromParams,
87
+ };
88
+ }
74
89
  if (!normalized) {
75
- respond(false, { error: 'invalid sessionKey' });
90
+ reject('invalid sessionKey');
76
91
  return;
77
92
  }
78
93
 
@@ -1,4 +1,5 @@
1
1
  import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
2
+ import type { BncrSessionKind } from '../core/targets.ts';
2
3
  import type { BncrRoute, FileRecvTransferState } from '../core/types.ts';
3
4
 
4
5
  export type BncrFileInboundLeaseEventKind =
@@ -40,6 +41,7 @@ export type BncrFileInboundRuntime = {
40
41
  normalizeStoredSessionKey: (
41
42
  sessionKey: string,
42
43
  ) => { sessionKey: string; route: BncrRoute } | null;
44
+ buildCanonicalSessionKey: (route: BncrRoute, kind?: BncrSessionKind) => string;
43
45
  saveInboundMediaBuffer: (args: {
44
46
  buffer: Buffer;
45
47
  mimeType: string;
@@ -1,13 +1,66 @@
1
+ import { hasControlCommand } from 'openclaw/plugin-sdk/command-auth';
1
2
  import { resolveAccount } from '../core/accounts.ts';
2
3
  import { checkBncrMessageGate } from '../messaging/inbound/gate.ts';
4
+ import { parseBncrNativeCommand } from '../messaging/inbound/native-command.ts';
3
5
  import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
4
6
  import type { OpenClawResolvedAgentRoute } from '../openclaw/routing-runtime.ts';
5
7
  import type { buildInboundResponsePayload } from './channel-inbound-helpers.ts';
6
8
  import { resolveInboundSessionContext } from './channel-inbound-helpers.ts';
7
- import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
9
+ import type { BncrChannelConfigRoot, BncrSceneRecord } from './channel-runtime-types.ts';
10
+ import { decideSceneAdmission } from './scene-registry.ts';
8
11
 
9
12
  type InboundAcceptanceResponsePayload = ReturnType<typeof buildInboundResponsePayload>;
10
13
 
14
+ function shouldDispatchForScene(args: {
15
+ parsed: ReturnType<typeof parseBncrInboundParams>;
16
+ admission: ReturnType<typeof decideSceneAdmission>;
17
+ cfg: BncrChannelConfigRoot;
18
+ }) {
19
+ const { parsed, admission, cfg } = args;
20
+ if (parsed.peer.kind === 'direct') return true;
21
+ if (!admission.allowed) return false;
22
+
23
+ const isBncrNativeCommand =
24
+ parseBncrNativeCommand(parsed.extracted.text, {
25
+ allowBareWhoami: parsed.isAdmin !== true,
26
+ }) !== null;
27
+ const isAdminOpenClawNativeCommand =
28
+ parsed.isAdmin === true &&
29
+ !isBncrNativeCommand &&
30
+ hasControlCommand(parsed.extracted.text, cfg as Parameters<typeof hasControlCommand>[1]);
31
+ if (isAdminOpenClawNativeCommand) return true;
32
+
33
+ const mode = admission.scene.groupReplyMode || 'admin';
34
+ if (mode === 'all') return true;
35
+ if (mode === 'admin') return parsed.isAdmin === true;
36
+ if (mode === 'mention') return parsed.shouldRespond === true;
37
+ return parsed.isAdmin === true || parsed.shouldRespond === true;
38
+ }
39
+
40
+ function shouldAccumulateForScene(args: {
41
+ parsed: ReturnType<typeof parseBncrInboundParams>;
42
+ admission: ReturnType<typeof decideSceneAdmission>;
43
+ cfg: BncrChannelConfigRoot;
44
+ }) {
45
+ const { parsed, admission, cfg } = args;
46
+ if (parsed.peer.kind === 'direct') return true;
47
+ if (!admission.allowed) return false;
48
+
49
+ const isBncrNativeCommand =
50
+ parseBncrNativeCommand(parsed.extracted.text, {
51
+ allowBareWhoami: parsed.isAdmin !== true,
52
+ }) !== null;
53
+ const isAdminOpenClawNativeCommand =
54
+ parsed.isAdmin === true &&
55
+ !isBncrNativeCommand &&
56
+ hasControlCommand(parsed.extracted.text, cfg as Parameters<typeof hasControlCommand>[1]);
57
+ if (isAdminOpenClawNativeCommand) return true;
58
+
59
+ const mode = admission.scene.groupReplyMode || 'admin';
60
+ if (mode === 'admin') return parsed.isAdmin === true;
61
+ return true;
62
+ }
63
+
11
64
  export async function prepareBncrInboundAcceptance(args: {
12
65
  api: unknown;
13
66
  parsed: ReturnType<typeof parseBncrInboundParams>;
@@ -27,6 +80,10 @@ export async function prepareBncrInboundAcceptance(args: {
27
80
  | { kind: 'gate-denied'; accountId: string; msgId?: string | null; reason: string },
28
81
  ) => InboundAcceptanceResponsePayload;
29
82
  markInboundDedupSeen: (key: string) => boolean;
83
+ sceneRegistry: Map<string, BncrSceneRecord>;
84
+ now: () => number;
85
+ defaultAdminAgentId: string;
86
+ defaultPublicAgentId: string;
30
87
  }) {
31
88
  const { parsed, canonicalAgentId } = args;
32
89
  const {
@@ -83,6 +140,26 @@ export async function prepareBncrInboundAcceptance(args: {
83
140
  };
84
141
  }
85
142
 
143
+ const admission = decideSceneAdmission({
144
+ parsed,
145
+ now: args.now(),
146
+ sceneRegistry: args.sceneRegistry,
147
+ defaultAdminAgentId: args.defaultAdminAgentId,
148
+ defaultPublicAgentId: args.defaultPublicAgentId,
149
+ });
150
+ if (!admission.allowed) {
151
+ return {
152
+ ok: false as const,
153
+ status: admission.replyPolicy === 'pending',
154
+ payload: args.buildInboundResponsePayload({
155
+ kind: 'gate-denied',
156
+ accountId,
157
+ msgId: msgId ?? null,
158
+ reason: admission.reason,
159
+ }),
160
+ };
161
+ }
162
+
86
163
  const { sessionKey, inboundText } = resolveInboundSessionContext({
87
164
  cfg,
88
165
  accountId,
@@ -90,6 +167,7 @@ export async function prepareBncrInboundAcceptance(args: {
90
167
  route,
91
168
  sessionKeyFromRoute: sessionKeyfromroute,
92
169
  canonicalAgentId,
170
+ resolvedAgentId: admission.agentId,
93
171
  taskKey: extracted.taskKey ?? undefined,
94
172
  text,
95
173
  extractedText: extracted.text,
@@ -103,5 +181,8 @@ export async function prepareBncrInboundAcceptance(args: {
103
181
  sessionKey,
104
182
  inboundText,
105
183
  hasMedia: Boolean(mediaBase64 || mediaPathFromTransfer),
184
+ resolvedAgentId: admission.agentId,
185
+ shouldDispatch: shouldDispatchForScene({ parsed, admission, cfg }),
186
+ shouldAccumulate: shouldAccumulateForScene({ parsed, admission, cfg }),
106
187
  };
107
188
  }
@@ -1,11 +1,14 @@
1
1
  import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
2
+ import { buildCanonicalBncrSessionKey } from '../core/targets.ts';
2
3
  import type { BncrConnection, BncrRoute } from '../core/types.ts';
4
+ import type { BncrGroupHistoryMap } from '../messaging/inbound/group-history.ts';
3
5
  import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
6
+ import type { ReplyPayloadInput } from '../messaging/outbound/reply-enqueue.ts';
4
7
  import type {
5
8
  buildInboundResponsePayload,
6
9
  resolveInboundSessionContext,
7
10
  } from './channel-inbound-helpers.ts';
8
- import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
11
+ import type { BncrChannelConfigRoot, BncrSceneRecord } from './channel-runtime-types.ts';
9
12
  import { buildBncrGatewayEventContext } from './gateway-event-context.ts';
10
13
 
11
14
  type InboundLifecycleStage = 'accepted';
@@ -39,6 +42,9 @@ type InboundAcceptanceResult =
39
42
  sessionKey: string;
40
43
  inboundText: string;
41
44
  hasMedia: boolean;
45
+ resolvedAgentId: string;
46
+ shouldDispatch: boolean;
47
+ shouldAccumulate: boolean;
42
48
  }
43
49
  | {
44
50
  ok: false;
@@ -46,6 +52,26 @@ type InboundAcceptanceResult =
46
52
  payload: Record<string, unknown>;
47
53
  };
48
54
 
55
+ function buildBncrPendingDirectApprovalNotice(args: {
56
+ parsed: ParsedInboundParams;
57
+ reason: string;
58
+ }): string | null {
59
+ if (args.parsed.peer.kind !== 'direct') return null;
60
+ if (args.reason !== 'scene pending approval') return null;
61
+ return `This private chat is pending approval. Ask the administrator to allow your SceneId: ${args.parsed.platform}:${args.parsed.userId}`;
62
+ }
63
+
64
+ function buildBncrPendingDirectApprovalPayload(args: {
65
+ msgId?: string;
66
+ text: string;
67
+ }): ReplyPayloadInput {
68
+ return {
69
+ text: args.text,
70
+ kind: 'final',
71
+ ...(args.msgId ? { replyToId: args.msgId } : {}),
72
+ };
73
+ }
74
+
49
75
  export type BncrInboundHandlersRuntime = {
50
76
  channelId: string;
51
77
  bridgeId: string;
@@ -98,6 +124,15 @@ export type BncrInboundHandlersRuntime = {
98
124
  peer: EnsureCanonicalAgentIdPeer;
99
125
  channelId: string;
100
126
  }) => string;
127
+ defaultAdminAgentId: (args: {
128
+ cfg: BncrChannelConfigRoot;
129
+ accountId: string;
130
+ peer: EnsureCanonicalAgentIdPeer;
131
+ channelId: string;
132
+ }) => string;
133
+ defaultPublicAgentId: () => string;
134
+ sceneRegistry: Map<string, BncrSceneRecord>;
135
+ groupHistories: BncrGroupHistoryMap;
101
136
  prepareInboundAcceptance: (args: {
102
137
  parsed: ParsedInboundParams;
103
138
  canonicalAgentId: string;
@@ -110,12 +145,27 @@ export type BncrInboundHandlersRuntime = {
110
145
  text: string;
111
146
  hasMedia: boolean;
112
147
  }) => void;
148
+ enqueueFromReply: (args: {
149
+ accountId: string;
150
+ sessionKey: string;
151
+ route: BncrRoute;
152
+ payload: ReplyPayloadInput;
153
+ mediaLocalRoots?: readonly string[];
154
+ }) => Promise<void>;
113
155
  respond: GatewayRequestHandlerOptions['respond'];
114
156
  flushOnInboundAccepted: (accountId: string) => void;
115
157
  dispatchInbound: (args: {
116
158
  cfg: BncrChannelConfigRoot;
117
159
  parsed: ParsedInboundParams;
118
160
  canonicalAgentId: string;
161
+ resolvedAgentId: string;
162
+ shouldDispatch: boolean;
163
+ shouldAccumulate: boolean;
164
+ sceneRegistry: Map<string, BncrSceneRecord>;
165
+ groupHistories: BncrGroupHistoryMap;
166
+ defaultAdminAgentId: string;
167
+ defaultPublicAgentId: string;
168
+ now: () => number;
119
169
  }) => Promise<unknown>;
120
170
  };
121
171
 
@@ -191,13 +241,50 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
191
241
  peer,
192
242
  channelId: runtime.channelId,
193
243
  });
244
+ const defaultAdminAgentId = runtime.defaultAdminAgentId({
245
+ cfg,
246
+ accountId,
247
+ peer,
248
+ channelId: runtime.channelId,
249
+ });
194
250
  const acceptance = await runtime.prepareInboundAcceptance({ parsed, canonicalAgentId });
195
251
  if (!acceptance.ok) {
252
+ const reason = runtime
253
+ .asString((acceptance.payload as { reason?: unknown })?.reason || '')
254
+ .trim();
255
+ runtime.logInfo(
256
+ 'inbound',
257
+ `accept reject|accountId=${accountId}|msgId=${msgId ?? '-'}|scope=${runtime.formatDisplayScope(route)}|chatType=${peer.kind}|msgType=${msgType}|status=${acceptance.status}|reason=${reason || '-'}|isAdmin=${parsed.isAdmin === true}|shouldRespond=${parsed.shouldRespond === true}`,
258
+ );
259
+ const pendingNotice = buildBncrPendingDirectApprovalNotice({ parsed, reason });
260
+ if (pendingNotice) {
261
+ const sessionKey = buildCanonicalBncrSessionKey(route, canonicalAgentId);
262
+ await runtime.enqueueFromReply({
263
+ accountId,
264
+ sessionKey,
265
+ route,
266
+ payload: buildBncrPendingDirectApprovalPayload({
267
+ msgId: msgId ?? undefined,
268
+ text: pendingNotice,
269
+ }),
270
+ });
271
+ }
196
272
  respond(acceptance.status, acceptance.payload);
197
273
  return;
198
274
  }
199
275
 
200
- const { sessionKey, inboundText, hasMedia } = acceptance;
276
+ const {
277
+ sessionKey,
278
+ inboundText,
279
+ hasMedia,
280
+ resolvedAgentId,
281
+ shouldDispatch,
282
+ shouldAccumulate,
283
+ } = acceptance;
284
+ runtime.logInfo(
285
+ 'inbound',
286
+ `accept ok|accountId=${accountId}|msgId=${msgId ?? '-'}|scope=${runtime.formatDisplayScope(route)}|chatType=${peer.kind}|msgType=${msgType}|sessionKey=${sessionKey}|agent=${resolvedAgentId}|dispatch=${shouldDispatch}|accumulate=${shouldAccumulate}|isAdmin=${parsed.isAdmin === true}|shouldRespond=${parsed.shouldRespond === true}`,
287
+ );
201
288
  runtime.logInfo(
202
289
  'inbound',
203
290
  JSON.stringify({
@@ -239,6 +326,14 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
239
326
  cfg,
240
327
  parsed,
241
328
  canonicalAgentId,
329
+ resolvedAgentId,
330
+ shouldDispatch,
331
+ shouldAccumulate,
332
+ sceneRegistry: runtime.sceneRegistry,
333
+ groupHistories: runtime.groupHistories,
334
+ defaultAdminAgentId,
335
+ defaultPublicAgentId: runtime.defaultPublicAgentId(),
336
+ now: runtime.now,
242
337
  })
243
338
  .catch((err) => {
244
339
  runtime.logError('inbound', `process failed: ${String(err)}`, { debugOnly: true });
@@ -53,6 +53,9 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
53
53
  clientId?: string;
54
54
  context: GatewayRequestHandlerOptions['context'];
55
55
  }) => void;
56
+ buildCanonicalSessionKey: Parameters<
57
+ typeof createBncrFileInboundHandlersComponent
58
+ >[0]['buildCanonicalSessionKey'];
56
59
  refreshLiveConnectionState: (args: {
57
60
  accountId: string;
58
61
  connId: string;
@@ -75,6 +78,14 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
75
78
  ensureCanonicalAgentId: Parameters<
76
79
  typeof createBncrInboundHandlersComponent
77
80
  >[0]['ensureCanonicalAgentId'];
81
+ defaultAdminAgentId: Parameters<
82
+ typeof createBncrInboundHandlersComponent
83
+ >[0]['defaultAdminAgentId'];
84
+ defaultPublicAgentId: Parameters<
85
+ typeof createBncrInboundHandlersComponent
86
+ >[0]['defaultPublicAgentId'];
87
+ sceneRegistry: Parameters<typeof createBncrInboundHandlersComponent>[0]['sceneRegistry'];
88
+ groupHistories: Parameters<typeof createBncrInboundHandlersComponent>[0]['groupHistories'];
78
89
  prepareInboundAcceptance: Parameters<
79
90
  typeof createBncrInboundHandlersComponent
80
91
  >[0]['prepareInboundAcceptance'];
@@ -111,6 +122,7 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
111
122
  matchesTransferOwner: runtime.matchesTransferOwner,
112
123
  refreshAcceptedFileTransferLiveState: runtime.refreshAcceptedFileTransferLiveState,
113
124
  logWarn: runtime.logWarn,
125
+ buildCanonicalSessionKey: runtime.buildCanonicalSessionKey,
114
126
  fileRecvTransfers: runtime.fileRecvTransfers,
115
127
  inboundFileTransferMaxBytes: runtime.inboundFileTransferMaxBytes,
116
128
  inboundFileTransferMaxChunks: runtime.inboundFileTransferMaxChunks,
@@ -136,6 +148,10 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
136
148
  buildActiveConnectionDebugList: runtime.buildActiveConnectionDebugList,
137
149
  markLastInboundAt: runtime.markLastInboundAt,
138
150
  ensureCanonicalAgentId: runtime.ensureCanonicalAgentId,
151
+ defaultAdminAgentId: runtime.defaultAdminAgentId,
152
+ defaultPublicAgentId: runtime.defaultPublicAgentId,
153
+ sceneRegistry: runtime.sceneRegistry,
154
+ groupHistories: runtime.groupHistories,
139
155
  prepareInboundAcceptance: runtime.prepareInboundAcceptance,
140
156
  logInboundSummary: runtime.logInboundSummary,
141
157
  flushPushQueueBestEffort: runtime.flushPushQueueBestEffort,
@@ -10,6 +10,15 @@ import {
10
10
  } from '../messaging/outbound/target-resolver.ts';
11
11
  import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
12
12
 
13
+ function formatBncrHumanDisplay(route: BncrRoute): string {
14
+ const platform = asSanitizedString(route?.platform).trim();
15
+ const groupId = asSanitizedString(route?.groupId).trim();
16
+ const userId = asSanitizedString(route?.userId).trim();
17
+ if (platform && groupId && groupId !== '0') return `Bncr:${platform}:Group:${groupId}`;
18
+ if (platform && userId && userId !== '0') return `Bncr:${platform}:User:${userId}`;
19
+ return formatDisplayScope(route);
20
+ }
21
+
13
22
  type BncrMessagingRuntimeBridge = {
14
23
  canonicalAgentId?: string;
15
24
  ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) => string;
@@ -69,16 +78,24 @@ export function normalizeBncrMessagingTarget(raw: string) {
69
78
  }
70
79
 
71
80
  export function formatBncrMessagingTargetDisplay({ target }: BncrMessagingTargetDisplayInput) {
72
- if (typeof target === 'string') return asSanitizedString(target).trim();
81
+ if (typeof target === 'string') {
82
+ const parsed = parseExplicitTarget(asSanitizedString(target).trim());
83
+ return parsed?.route ? formatBncrHumanDisplay(parsed.route) : asSanitizedString(target).trim();
84
+ }
73
85
  if (!isDisplayTarget(target)) return '';
74
86
  const displayScope = asSanitizedString(target?.displayScope || target?.to).trim();
75
- if (displayScope) return displayScope;
87
+ if (displayScope) {
88
+ const parsed = parseExplicitTarget(displayScope);
89
+ if (parsed?.route) return formatBncrHumanDisplay(parsed.route);
90
+ return displayScope;
91
+ }
76
92
  if (target.platform || target.groupId || target.userId) {
77
- return formatDisplayScope({
93
+ const route = {
78
94
  platform: asSanitizedString(target.platform).trim(),
79
95
  groupId: asSanitizedString(target.groupId).trim(),
80
96
  userId: asSanitizedString(target.userId).trim(),
81
- });
97
+ };
98
+ return formatBncrHumanDisplay(route);
82
99
  }
83
100
  return '';
84
101
  }
@@ -188,7 +205,7 @@ export function createBncrMessagingTargetResolver(getBridge: () => BncrMessaging
188
205
  return {
189
206
  to: resolved.displayScope,
190
207
  kind: resolved.kind,
191
- display: resolved.displayScope,
208
+ display: formatBncrHumanDisplay(resolved.route),
192
209
  source: 'normalized' as const,
193
210
  };
194
211
  },