@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
@@ -5,6 +5,37 @@ import {
5
5
  updateBncrSessionStoreEntry,
6
6
  } from '../../openclaw/inbound-session-runtime.ts';
7
7
 
8
+ function formatBncrSessionLabel(displayTo: string): string {
9
+ const raw = String(displayTo || '').trim();
10
+ const parts = raw.split(':');
11
+ if (parts.length === 4 && parts[0] === 'Bncr') {
12
+ const [, platform, groupId, userId] = parts;
13
+ if (platform && groupId && groupId !== '0') return `Bncr:${platform}:Group:${groupId}`;
14
+ if (platform && userId && userId !== '0') return `Bncr:${platform}:User:${userId}`;
15
+ }
16
+ return raw;
17
+ }
18
+
19
+ function formatBncrSessionRouteTarget(displayTo: string): string {
20
+ const raw = String(displayTo || '').trim();
21
+ const parts = raw.split(':');
22
+ if (parts.length === 4 && parts[0] === 'Bncr') {
23
+ const [, platform, groupId, userId] = parts;
24
+ if (platform && groupId && groupId !== '0') return `Bncr:${platform}:${groupId}:0`;
25
+ if (platform && userId && userId !== '0') return `Bncr:${platform}:0:${userId}`;
26
+ }
27
+ return raw;
28
+ }
29
+
30
+ function formatBncrSessionGroupKey(displayTo: string): string | null {
31
+ const raw = String(displayTo || '').trim();
32
+ const parts = raw.split(':');
33
+ if (parts.length !== 4 || parts[0] !== 'Bncr') return null;
34
+ const [, platform, groupId] = parts;
35
+ if (!platform || !groupId || groupId === '0') return null;
36
+ return `${platform.toLowerCase()}:${groupId}`;
37
+ }
38
+
8
39
  type RecordInboundSessionFn = (args: {
9
40
  storePath?: string;
10
41
  sessionKey?: string;
@@ -21,30 +52,35 @@ export function buildBncrInboundSessionIdentityPatch(args: {
21
52
  senderId: string;
22
53
  }) {
23
54
  const { channelId, accountId, chatType, displayTo, senderId } = args;
55
+ const displayLabel = formatBncrSessionLabel(displayTo);
56
+ const routeTarget = formatBncrSessionRouteTarget(displayTo);
57
+ const groupKey = chatType === 'group' ? formatBncrSessionGroupKey(displayTo) : null;
24
58
  return {
25
- label: displayTo,
59
+ label: displayLabel,
60
+ displayName: displayLabel,
26
61
  channel: channelId,
27
62
  chatType,
63
+ ...(groupKey ? { groupId: groupKey } : {}),
28
64
  origin: {
29
- label: displayTo,
65
+ label: displayLabel,
30
66
  provider: channelId,
31
67
  surface: channelId,
32
68
  chatType,
33
69
  from: senderId,
34
- to: displayTo,
70
+ to: routeTarget,
35
71
  accountId,
36
72
  },
37
73
  deliveryContext: {
38
74
  channel: channelId,
39
- to: displayTo,
75
+ to: routeTarget,
40
76
  accountId,
41
77
  },
42
78
  route: {
43
79
  channel: channelId,
44
80
  accountId,
45
- target: { to: displayTo },
81
+ target: { to: routeTarget },
46
82
  },
47
- lastTo: displayTo,
83
+ lastTo: routeTarget,
48
84
  };
49
85
  }
50
86
 
@@ -56,20 +92,93 @@ function normalizeNonEmptyString(value: unknown): string | null {
56
92
  export async function correctBncrInboundSessionLabel(args: {
57
93
  storePath: string;
58
94
  sessionKey: string;
59
- expectedLabel: string;
95
+ expectedPatch: Record<string, unknown>;
60
96
  }) {
61
97
  const storePath = normalizeNonEmptyString(args.storePath);
62
98
  const sessionKey = normalizeNonEmptyString(args.sessionKey);
63
- const expectedLabel = normalizeNonEmptyString(args.expectedLabel);
64
- if (!storePath || !sessionKey || !expectedLabel) return;
99
+ if (!storePath || !sessionKey) return;
100
+
101
+ const expectedPatch = args.expectedPatch;
102
+ const expectedLabel = normalizeNonEmptyString(expectedPatch.label);
103
+ const expectedDisplayName = normalizeNonEmptyString(expectedPatch.displayName);
104
+ const expectedGroupId = normalizeNonEmptyString(expectedPatch.groupId);
105
+ const expectedOriginTo = normalizeNonEmptyString(
106
+ (expectedPatch.origin as { to?: unknown } | undefined)?.to,
107
+ );
108
+ const expectedDeliveryTo = normalizeNonEmptyString(
109
+ (expectedPatch.deliveryContext as { to?: unknown } | undefined)?.to,
110
+ );
111
+ const expectedRouteTo = normalizeNonEmptyString(
112
+ (
113
+ (expectedPatch.route as { target?: { to?: unknown } } | undefined)?.target as
114
+ | { to?: unknown }
115
+ | undefined
116
+ )?.to,
117
+ );
118
+ const expectedLastTo = normalizeNonEmptyString(expectedPatch.lastTo);
119
+ if (
120
+ !expectedLabel ||
121
+ !expectedDisplayName ||
122
+ !expectedOriginTo ||
123
+ !expectedDeliveryTo ||
124
+ !expectedRouteTo ||
125
+ !expectedLastTo
126
+ )
127
+ return;
65
128
 
66
129
  try {
67
130
  await updateBncrSessionStoreEntry({
68
131
  storePath,
69
132
  sessionKey,
70
133
  update: (entry: SessionStoreEntryLike) => {
71
- if (entry?.label === expectedLabel) return null;
72
- return { label: expectedLabel };
134
+ const origin =
135
+ entry?.origin && typeof entry.origin === 'object'
136
+ ? (entry.origin as Record<string, unknown>)
137
+ : {};
138
+ const deliveryContext =
139
+ entry?.deliveryContext && typeof entry.deliveryContext === 'object'
140
+ ? (entry.deliveryContext as Record<string, unknown>)
141
+ : {};
142
+ const route =
143
+ entry?.route && typeof entry.route === 'object'
144
+ ? (entry.route as Record<string, unknown>)
145
+ : {};
146
+ const routeTarget =
147
+ route.target && typeof route.target === 'object'
148
+ ? (route.target as Record<string, unknown>)
149
+ : {};
150
+
151
+ const unchanged =
152
+ entry?.label === expectedLabel &&
153
+ entry?.displayName === expectedDisplayName &&
154
+ normalizeNonEmptyString(entry?.groupId) === expectedGroupId &&
155
+ normalizeNonEmptyString(origin.to) === expectedOriginTo &&
156
+ normalizeNonEmptyString(deliveryContext.to) === expectedDeliveryTo &&
157
+ normalizeNonEmptyString(routeTarget.to) === expectedRouteTo &&
158
+ normalizeNonEmptyString(entry?.lastTo) === expectedLastTo;
159
+ if (unchanged) return null;
160
+
161
+ return {
162
+ ...(expectedGroupId ? { groupId: expectedGroupId } : {}),
163
+ label: expectedLabel,
164
+ displayName: expectedDisplayName,
165
+ origin: {
166
+ ...origin,
167
+ ...(expectedPatch.origin as Record<string, unknown>),
168
+ },
169
+ deliveryContext: {
170
+ ...deliveryContext,
171
+ ...(expectedPatch.deliveryContext as Record<string, unknown>),
172
+ },
173
+ route: {
174
+ ...route,
175
+ ...(expectedPatch.route as Record<string, unknown>),
176
+ target: {
177
+ to: expectedRouteTo,
178
+ },
179
+ },
180
+ lastTo: expectedLastTo,
181
+ };
73
182
  },
74
183
  });
75
184
  } catch (err) {
@@ -108,7 +217,7 @@ export async function recordAndPatchBncrInboundSessionEntry(args: {
108
217
 
109
218
  export function wrapBncrInboundRecordSessionLabelCorrection(args: {
110
219
  recordInboundSession: RecordInboundSessionFn;
111
- expectedLabel: string;
220
+ expectedPatch: Record<string, unknown>;
112
221
  }): RecordInboundSessionFn {
113
222
  return async (recordArgs) => {
114
223
  const result = await args.recordInboundSession(recordArgs);
@@ -116,7 +225,7 @@ export function wrapBncrInboundRecordSessionLabelCorrection(args: {
116
225
  await correctBncrInboundSessionLabel({
117
226
  storePath: recordArgs.storePath,
118
227
  sessionKey: recordArgs.sessionKey,
119
- expectedLabel: args.expectedLabel,
228
+ expectedPatch: args.expectedPatch,
120
229
  });
121
230
  return result;
122
231
  };
@@ -0,0 +1,17 @@
1
+ export function createBncrSessionMetaTaskBarrier() {
2
+ const pending = new Set<Promise<unknown>>();
3
+
4
+ return {
5
+ track(task: Promise<unknown>) {
6
+ if (!task || typeof task.then !== 'function') return;
7
+ pending.add(task);
8
+ void task.finally(() => {
9
+ pending.delete(task);
10
+ });
11
+ },
12
+ async wait() {
13
+ if (pending.size === 0) return;
14
+ await Promise.allSettled([...pending]);
15
+ },
16
+ };
17
+ }
@@ -1,41 +1,93 @@
1
+ import { readBncrSessionUpdatedAt } from '../../openclaw/inbound-session-runtime.ts';
2
+ import { resolveOpenClawEnvelopeFormatOptions } from '../../openclaw/reply-runtime.ts';
1
3
  import {
2
4
  buildBncrPromptVisibleContextFacts,
3
5
  buildBncrStructuredContextFactsFromInboundParts,
4
6
  } from './context-facts.ts';
5
- import type { BncrInboundApi, BncrInboundContextPayload } from './contracts.ts';
7
+ import type { BncrInboundApi, BncrInboundConfig, BncrInboundContextPayload } from './contracts.ts';
6
8
  import type {
7
9
  BncrInboundConversationResolution,
8
10
  BncrPreparedInboundSessionContext,
9
11
  ParsedInbound,
10
12
  } from './dispatch-prep.ts';
13
+ import {
14
+ type BncrGroupHistoryMap,
15
+ buildBncrInboundHistory,
16
+ buildBncrPendingGroupContext,
17
+ } from './group-history.ts';
11
18
  import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
12
19
 
20
+ function parseSlashCommandName(body: string): string | undefined {
21
+ const raw = String(body || '').trim();
22
+ if (!raw.startsWith('/')) return undefined;
23
+ return raw.slice(1).split(/\s+/, 1)[0]?.split('@', 1)[0] || undefined;
24
+ }
25
+
26
+ function applyBncrLegacyCommandFields(args: {
27
+ ctx: BncrInboundContextPayload;
28
+ channelId: string;
29
+ senderIdForContext: string;
30
+ resolution: BncrInboundConversationResolution;
31
+ rawBody: string;
32
+ ownerAllowFrom?: string[];
33
+ isAuthorizedTextCommand: boolean;
34
+ }) {
35
+ const { ctx, channelId, senderIdForContext, resolution, rawBody, ownerAllowFrom } = args;
36
+ ctx.From = senderIdForContext;
37
+ ctx.To = resolution.canonicalTo;
38
+ ctx.SenderId = senderIdForContext;
39
+ ctx.OriginatingChannel = channelId;
40
+ if (ownerAllowFrom?.length) ctx.OwnerAllowFrom = ownerAllowFrom;
41
+ if (args.isAuthorizedTextCommand) {
42
+ ctx.CommandAuthorized = true;
43
+ ctx.CommandSource = 'text';
44
+ ctx.CommandTurn = {
45
+ kind: 'text-slash',
46
+ source: 'text',
47
+ authorized: true,
48
+ commandName: parseSlashCommandName(rawBody),
49
+ body: rawBody,
50
+ };
51
+ }
52
+ return ctx;
53
+ }
54
+
13
55
  export function buildBncrInboundTurnContext(args: {
14
56
  api: BncrInboundApi;
57
+ cfg: BncrInboundConfig;
15
58
  channelId: string;
16
59
  parsed: ParsedInbound;
17
60
  msgId?: string | null;
18
- mimeType?: string;
19
- mediaPath?: string;
20
61
  peer: ParsedInbound['peer'];
21
62
  senderIdForContext: string;
22
63
  senderDisplayName: string;
64
+ ownerAllowFrom?: string[];
65
+ bridgeSenderId?: string;
66
+ bridgeSenderName?: string;
23
67
  resolution: BncrInboundConversationResolution;
24
68
  prepared: BncrPreparedInboundSessionContext;
69
+ groupHistories: BncrGroupHistoryMap;
70
+ shouldDispatch: boolean;
25
71
  }): BncrInboundContextPayload | Promise<BncrInboundContextPayload> {
26
72
  const {
27
73
  api,
74
+ cfg,
28
75
  channelId,
29
76
  parsed,
30
77
  msgId,
31
- mimeType,
32
- mediaPath,
33
78
  peer,
34
79
  senderIdForContext,
35
80
  senderDisplayName,
81
+ ownerAllowFrom,
82
+ bridgeSenderId,
83
+ bridgeSenderName,
36
84
  resolution,
37
85
  prepared,
86
+ groupHistories,
87
+ shouldDispatch,
38
88
  } = args;
89
+ const senderIsOwner = parsed.isAdmin === true || Boolean(ownerAllowFrom?.length);
90
+ const senderIsAuthorized = senderIsOwner;
39
91
  const structuredContextFacts = buildBncrStructuredContextFactsFromInboundParts({
40
92
  channelId,
41
93
  parsed,
@@ -43,11 +95,14 @@ export function buildBncrInboundTurnContext(args: {
43
95
  prepared: {
44
96
  rawBody: prepared.rawBody,
45
97
  body: prepared.body,
46
- mediaPath,
47
- mediaContentType: mimeType,
98
+ mediaItems: prepared.mediaItems,
48
99
  },
49
100
  senderIdForContext,
50
101
  senderDisplayName,
102
+ bridgeSenderId,
103
+ bridgeSenderName,
104
+ senderIsOwner,
105
+ senderIsAuthorized,
51
106
  });
52
107
  const promptVisibleContextFacts = buildBncrPromptVisibleContextFacts(structuredContextFacts);
53
108
  const supplementalUntrustedContext = Object.keys(promptVisibleContextFacts).length
@@ -60,72 +115,130 @@ export function buildBncrInboundTurnContext(args: {
60
115
  },
61
116
  ]
62
117
  : [];
118
+ const platformWantsReply = parsed.shouldRespond === true;
119
+ const wasMentioned = parsed.isBotMentioned === true || platformWantsReply;
120
+ const isAuthorizedTextCommand = Boolean(ownerAllowFrom?.length);
121
+ const bodyForAgent =
122
+ shouldDispatch && parsed.peer.kind === 'group'
123
+ ? buildBncrPendingGroupContext({
124
+ api,
125
+ historyMap: groupHistories,
126
+ parsed,
127
+ channelLabel: resolution.canonicalTo,
128
+ currentTimestamp: Date.now(),
129
+ previousTimestamp: readBncrSessionUpdatedAt(api, {
130
+ storePath: prepared.storePath,
131
+ sessionKey: resolution.dispatchSessionKey,
132
+ }),
133
+ envelope: resolveOpenClawEnvelopeFormatOptions(api, cfg),
134
+ currentMessage: prepared.body,
135
+ })
136
+ : prepared.body;
137
+ const inboundHistory =
138
+ shouldDispatch && parsed.peer.kind === 'group'
139
+ ? buildBncrInboundHistory({ historyMap: groupHistories, parsed })
140
+ : undefined;
63
141
 
64
- return resolveBncrChannelInboundRuntime(api).buildContext({
65
- channel: channelId,
66
- provider: channelId,
67
- surface: channelId,
68
- accountId: resolution.accountId,
69
- messageId: msgId,
70
- timestamp: Date.now(),
71
- from: senderIdForContext,
72
- sender: {
73
- id: senderIdForContext,
74
- name: senderDisplayName,
75
- username: senderDisplayName,
76
- },
77
- conversation: {
78
- kind: resolution.chatType,
79
- id: peer.id,
80
- label: resolution.canonicalTo,
81
- routePeer: {
82
- kind: peer.kind,
142
+ return Promise.resolve(
143
+ resolveBncrChannelInboundRuntime(api).buildContext({
144
+ channel: channelId,
145
+ provider: channelId,
146
+ surface: channelId,
147
+ accountId: resolution.accountId,
148
+ messageId: msgId,
149
+ timestamp: Date.now(),
150
+ from: senderIdForContext,
151
+ sender: {
152
+ id: senderIdForContext,
153
+ name: senderDisplayName,
154
+ username: parsed.userName || senderDisplayName,
155
+ },
156
+ conversation: {
157
+ kind: resolution.chatType,
83
158
  id: peer.id,
159
+ label: resolution.canonicalTo,
160
+ routePeer: {
161
+ kind: peer.kind,
162
+ id: peer.id,
163
+ },
84
164
  },
85
- },
86
- route: {
87
- agentId: resolution.resolvedRoute.agentId,
88
- accountId: resolution.accountId,
89
- routeSessionKey: resolution.resolvedRoute.sessionKey,
90
- dispatchSessionKey: resolution.dispatchSessionKey,
91
- mainSessionKey: resolution.resolvedRoute.mainSessionKey,
92
- },
93
- reply: {
94
- to: resolution.canonicalTo,
95
- originatingTo: resolution.originatingTo,
96
- },
97
- message: {
98
- inboundEventKind: 'user_request',
99
- body: prepared.body,
165
+ route: {
166
+ agentId: resolution.resolvedRoute.agentId,
167
+ accountId: resolution.accountId,
168
+ routeSessionKey: resolution.resolvedRoute.sessionKey,
169
+ dispatchSessionKey: resolution.dispatchSessionKey,
170
+ mainSessionKey: resolution.resolvedRoute.mainSessionKey,
171
+ },
172
+ reply: {
173
+ to: resolution.canonicalTo,
174
+ originatingTo: resolution.originatingTo,
175
+ },
176
+ message: {
177
+ inboundEventKind: 'user_request',
178
+ body: prepared.body,
179
+ rawBody: prepared.rawBody,
180
+ bodyForAgent,
181
+ inboundHistory,
182
+ commandBody: prepared.rawBody,
183
+ envelopeFrom: resolution.originatingTo,
184
+ senderLabel: senderDisplayName,
185
+ },
186
+ ...(isAuthorizedTextCommand
187
+ ? {
188
+ commandTurn: {
189
+ kind: 'text-slash' as const,
190
+ source: 'text' as const,
191
+ authorized: true,
192
+ commandName: parseSlashCommandName(prepared.rawBody),
193
+ body: prepared.rawBody,
194
+ },
195
+ }
196
+ : {}),
197
+ media:
198
+ prepared.mediaItems.length > 0
199
+ ? prepared.mediaItems.map((item) => ({
200
+ path: item.path,
201
+ contentType: item.contentType,
202
+ kind: item.kind,
203
+ messageId: msgId ?? undefined,
204
+ }))
205
+ : [],
206
+ access: {
207
+ mentions: {
208
+ canDetectMention: true,
209
+ wasMentioned,
210
+ effectiveWasMentioned: wasMentioned,
211
+ },
212
+ ...(isAuthorizedTextCommand
213
+ ? {
214
+ commands: {
215
+ authorized: true,
216
+ allowTextCommands: true,
217
+ useAccessGroups: false,
218
+ authorizers: [],
219
+ },
220
+ }
221
+ : {}),
222
+ },
223
+ supplemental: {
224
+ untrustedContext: supplementalUntrustedContext,
225
+ },
226
+ extra: {
227
+ OriginatingChannel: channelId,
228
+ ...(ownerAllowFrom?.length ? { OwnerAllowFrom: ownerAllowFrom } : {}),
229
+ BncrStructuredContextFacts: structuredContextFacts,
230
+ StructuredContextFacts: structuredContextFacts,
231
+ },
232
+ }),
233
+ ).then((ctx) =>
234
+ applyBncrLegacyCommandFields({
235
+ ctx,
236
+ channelId,
237
+ senderIdForContext,
238
+ resolution,
100
239
  rawBody: prepared.rawBody,
101
- bodyForAgent: prepared.rawBody,
102
- commandBody: prepared.rawBody,
103
- envelopeFrom: resolution.originatingTo,
104
- senderLabel: senderDisplayName,
105
- },
106
- media: mediaPath
107
- ? [
108
- {
109
- path: mediaPath,
110
- contentType: mimeType,
111
- kind: mimeType?.startsWith('image/')
112
- ? 'image'
113
- : mimeType?.startsWith('video/')
114
- ? 'video'
115
- : mimeType?.startsWith('audio/')
116
- ? 'audio'
117
- : 'document',
118
- messageId: msgId ?? undefined,
119
- },
120
- ]
121
- : [],
122
- supplemental: {
123
- untrustedContext: supplementalUntrustedContext,
124
- },
125
- extra: {
126
- OriginatingChannel: channelId,
127
- BncrStructuredContextFacts: structuredContextFacts,
128
- StructuredContextFacts: structuredContextFacts,
129
- },
130
- });
240
+ ownerAllowFrom,
241
+ isAuthorizedTextCommand,
242
+ }),
243
+ );
131
244
  }
@@ -47,6 +47,7 @@ export type OpenClawInboundRuntimeResolvedTurn = {
47
47
  record: {
48
48
  updateLastRoute: unknown;
49
49
  onRecordError: (err: unknown) => void;
50
+ trackSessionMetaTask?: (task: Promise<unknown>) => void;
50
51
  };
51
52
  runDispatch: () => Promise<unknown> | unknown;
52
53
  };
@@ -5,6 +5,7 @@ import { formatDisplayScope, normalizeStoredSessionKey, parseRouteLike } from '.
5
5
  import type { BncrRoute } from '../core/types.ts';
6
6
  import type { BncrInboundParamsInput } from '../messaging/inbound/contracts.ts';
7
7
  import { dispatchBncrInbound } from '../messaging/inbound/dispatch.ts';
8
+ import type { BncrGroupHistoryMap } from '../messaging/inbound/group-history.ts';
8
9
  import { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
9
10
  import { OUTBOUND_FLUSH_REASON, OUTBOUND_FLUSH_TRIGGER } from '../messaging/outbound/reasons.ts';
10
11
  import type { ReplyPayloadInput } from '../messaging/outbound/reply-enqueue.ts';
@@ -150,6 +151,9 @@ export function createBncrFileInboundHandlersComponent(runtime: {
150
151
  typeof createBncrFileInboundHandlers
151
152
  >[0]['refreshAcceptedFileTransferLiveState'];
152
153
  logWarn: Parameters<typeof createBncrFileInboundHandlers>[0]['logWarn'];
154
+ buildCanonicalSessionKey: Parameters<
155
+ typeof createBncrFileInboundHandlers
156
+ >[0]['buildCanonicalSessionKey'];
153
157
  fileRecvTransfers: Parameters<typeof createBncrFileInboundHandlers>[0]['fileRecvTransfers'];
154
158
  inboundFileTransferMaxBytes: number;
155
159
  inboundFileTransferMaxChunks: number;
@@ -166,6 +170,7 @@ export function createBncrFileInboundHandlersComponent(runtime: {
166
170
  logWarn: runtime.logWarn,
167
171
  parseRouteLike,
168
172
  normalizeStoredSessionKey,
173
+ buildCanonicalSessionKey: runtime.buildCanonicalSessionKey,
169
174
  saveInboundMediaBuffer: async ({ buffer, mimeType, fileName }) =>
170
175
  await saveOpenClawChannelMediaBuffer(
171
176
  runtime.getApi(),
@@ -209,6 +214,10 @@ export function createBncrInboundHandlersComponent(runtime: {
209
214
  >[0]['buildActiveConnectionDebugList'];
210
215
  markLastInboundAt: (accountId: string) => void;
211
216
  ensureCanonicalAgentId: Parameters<typeof createBncrInboundHandlers>[0]['ensureCanonicalAgentId'];
217
+ defaultAdminAgentId: Parameters<typeof createBncrInboundHandlers>[0]['defaultAdminAgentId'];
218
+ defaultPublicAgentId: Parameters<typeof createBncrInboundHandlers>[0]['defaultPublicAgentId'];
219
+ sceneRegistry: Parameters<typeof createBncrInboundHandlers>[0]['sceneRegistry'];
220
+ groupHistories: BncrGroupHistoryMap;
212
221
  prepareInboundAcceptance: Parameters<
213
222
  typeof createBncrInboundHandlers
214
223
  >[0]['prepareInboundAcceptance'];
@@ -250,22 +259,47 @@ export function createBncrInboundHandlersComponent(runtime: {
250
259
  markLastInboundAt: runtime.markLastInboundAt,
251
260
  getConfig: () => getOpenClawRuntimeConfig(runtime.getApi()),
252
261
  ensureCanonicalAgentId: runtime.ensureCanonicalAgentId,
262
+ defaultAdminAgentId: runtime.defaultAdminAgentId,
263
+ defaultPublicAgentId: runtime.defaultPublicAgentId,
264
+ sceneRegistry: runtime.sceneRegistry,
265
+ groupHistories: runtime.groupHistories,
253
266
  prepareInboundAcceptance: runtime.prepareInboundAcceptance,
254
267
  formatDisplayScope,
255
268
  logInboundSummary: runtime.logInboundSummary,
269
+ enqueueFromReply: runtime.enqueueFromReply,
256
270
  flushOnInboundAccepted: (accountId) =>
257
271
  runtime.flushPushQueueBestEffort({
258
272
  accountId,
259
273
  trigger: OUTBOUND_FLUSH_TRIGGER.INBOUND,
260
274
  reason: OUTBOUND_FLUSH_REASON.INBOUND_ACCEPTED,
261
275
  }),
262
- dispatchInbound: ({ cfg, parsed, canonicalAgentId }) =>
276
+ dispatchInbound: ({
277
+ cfg,
278
+ parsed,
279
+ canonicalAgentId,
280
+ resolvedAgentId,
281
+ shouldDispatch,
282
+ shouldAccumulate,
283
+ sceneRegistry,
284
+ groupHistories,
285
+ defaultAdminAgentId,
286
+ defaultPublicAgentId,
287
+ now,
288
+ }) =>
263
289
  dispatchBncrInbound({
264
290
  api: runtime.getApi(),
265
291
  channelId: runtime.channelId,
266
292
  cfg,
267
293
  parsed,
268
294
  canonicalAgentId,
295
+ resolvedAgentId,
296
+ shouldDispatch,
297
+ shouldAccumulate,
298
+ sceneRegistry,
299
+ groupHistories,
300
+ defaultAdminAgentId,
301
+ defaultPublicAgentId,
302
+ now,
269
303
  rememberSessionRoute: runtime.rememberSessionRoute,
270
304
  enqueueFromReply: runtime.enqueueFromReply,
271
305
  setInboundActivity: runtime.setInboundActivity,
@@ -55,6 +55,7 @@ export function resolveInboundSessionContext(args: {
55
55
  route: BncrRoute;
56
56
  sessionKeyFromRoute?: string;
57
57
  canonicalAgentId: string;
58
+ resolvedAgentId?: string;
58
59
  taskKey?: string;
59
60
  text: string;
60
61
  extractedText?: string;
@@ -74,15 +75,21 @@ export function resolveInboundSessionContext(args: {
74
75
  peer: args.peer,
75
76
  }),
76
77
  );
78
+ const normalizedAgentId =
79
+ (args.resolvedAgentId || '').trim() || resolvedRoute.agentId || args.canonicalAgentId;
77
80
  const baseSessionKey =
78
81
  normalizeInboundSessionKey(
79
82
  args.sessionKeyFromRoute || '',
80
83
  args.route,
81
- args.canonicalAgentId || '',
84
+ normalizedAgentId || '',
82
85
  ) || resolvedRoute.sessionKey;
83
86
  const taskSessionKey = withTaskSessionKey(baseSessionKey, args.taskKey);
84
87
  return {
85
- resolvedRoute,
88
+ resolvedRoute: {
89
+ ...resolvedRoute,
90
+ agentId: normalizedAgentId || resolvedRoute.agentId,
91
+ sessionKey: baseSessionKey || resolvedRoute.sessionKey,
92
+ },
86
93
  baseSessionKey,
87
94
  taskSessionKey,
88
95
  sessionKey: taskSessionKey || baseSessionKey,