@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
@@ -1,4 +1,5 @@
1
1
  import { emitBncrLogLine } from '../../core/logging.ts';
2
+ import type { BncrSceneRecord } from '../../plugin/channel-runtime-types.ts';
2
3
  import { handleBncrNativeCommand } from './commands.ts';
3
4
  import type {
4
5
  BncrEnqueueFromReply,
@@ -15,6 +16,12 @@ import {
15
16
  prepareBncrInboundDispatch,
16
17
  resolveBncrInboundConversation,
17
18
  } from './dispatch-prep.ts';
19
+ import {
20
+ type BncrGroupHistoryMap,
21
+ clearBncrPendingGroupHistory,
22
+ recordBncrPendingGroupMedia,
23
+ recordBncrPendingGroupText,
24
+ } from './group-history.ts';
18
25
  import { runBncrInboundReplyDispatch } from './reply-dispatch.ts';
19
26
  import { buildBncrInboundTurnContext } from './turn-context.ts';
20
27
 
@@ -31,6 +38,14 @@ export async function dispatchBncrInbound(params: {
31
38
  cfg: BncrInboundConfig;
32
39
  parsed: ParsedInbound;
33
40
  canonicalAgentId: string;
41
+ shouldDispatch?: boolean;
42
+ shouldAccumulate?: boolean;
43
+ resolvedAgentId?: string;
44
+ sceneRegistry: Map<string, BncrSceneRecord>;
45
+ groupHistories: BncrGroupHistoryMap;
46
+ defaultAdminAgentId: string;
47
+ defaultPublicAgentId: string;
48
+ now: () => number;
34
49
  rememberSessionRoute: BncrRememberSessionRoute;
35
50
  enqueueFromReply: BncrEnqueueFromReply;
36
51
  setInboundActivity: (accountId: string, at: number) => void;
@@ -43,6 +58,14 @@ export async function dispatchBncrInbound(params: {
43
58
  cfg,
44
59
  parsed,
45
60
  canonicalAgentId,
61
+ shouldDispatch = true,
62
+ shouldAccumulate = shouldDispatch,
63
+ resolvedAgentId,
64
+ sceneRegistry,
65
+ groupHistories = new Map(),
66
+ defaultAdminAgentId,
67
+ defaultPublicAgentId,
68
+ now,
46
69
  rememberSessionRoute,
47
70
  enqueueFromReply,
48
71
  setInboundActivity,
@@ -57,6 +80,11 @@ export async function dispatchBncrInbound(params: {
57
80
  cfg,
58
81
  parsed,
59
82
  canonicalAgentId,
83
+ resolvedAgentId,
84
+ sceneRegistry,
85
+ defaultAdminAgentId,
86
+ defaultPublicAgentId,
87
+ now,
60
88
  rememberSessionRoute,
61
89
  enqueueFromReply,
62
90
  logger,
@@ -79,12 +107,23 @@ export async function dispatchBncrInbound(params: {
79
107
  cfg,
80
108
  parsed,
81
109
  canonicalAgentId,
110
+ resolvedAgentId,
82
111
  rememberSessionRoute,
83
112
  });
84
- const { resolution, prepared, replyRouteFact, senderIdForContext, senderDisplayName } =
85
- preparedDispatch;
113
+ const {
114
+ resolution,
115
+ prepared,
116
+ replyRouteFact,
117
+ senderIdForContext,
118
+ senderDisplayName,
119
+ ownerAllowFrom,
120
+ bridgeSenderId,
121
+ bridgeSenderName,
122
+ } = preparedDispatch;
86
123
  const { dispatchSessionKey: sessionKey } = resolution;
87
- const { storePath, mediaPath, mediaContentType, rawBody } = prepared;
124
+ const { storePath, mediaItems, rawBody } = prepared;
125
+ const primaryMedia = mediaItems[0];
126
+ const mediaContentType = primaryMedia?.contentType;
88
127
  if (!clientId) {
89
128
  emitBncrLogLine(
90
129
  'warn',
@@ -93,18 +132,39 @@ export async function dispatchBncrInbound(params: {
93
132
  }
94
133
  const ctxPayload = await buildBncrInboundTurnContext({
95
134
  api,
135
+ cfg,
96
136
  channelId,
97
137
  parsed,
98
138
  msgId,
99
- mimeType: mediaContentType || mimeType,
100
- mediaPath,
101
139
  peer,
102
140
  senderIdForContext,
103
141
  senderDisplayName,
142
+ ownerAllowFrom,
143
+ bridgeSenderId,
144
+ bridgeSenderName,
104
145
  resolution,
105
146
  prepared,
147
+ groupHistories,
148
+ shouldDispatch,
106
149
  });
107
150
 
151
+ if (!shouldDispatch && shouldAccumulate) {
152
+ recordBncrPendingGroupText({
153
+ historyMap: groupHistories,
154
+ parsed,
155
+ senderDisplayName,
156
+ bodyText: rawBody,
157
+ });
158
+ await recordBncrPendingGroupMedia({
159
+ historyMap: groupHistories,
160
+ parsed,
161
+ senderDisplayName,
162
+ bodyText: rawBody,
163
+ mediaItems,
164
+ mediaContentType: mediaContentType || mimeType,
165
+ });
166
+ }
167
+
108
168
  await runBncrInboundReplyDispatch({
109
169
  api,
110
170
  channelId,
@@ -118,11 +178,17 @@ export async function dispatchBncrInbound(params: {
118
178
  resolution,
119
179
  replyRouteFact,
120
180
  senderIdForContext,
181
+ senderDisplayName,
182
+ shouldDispatch,
121
183
  setInboundActivity,
122
184
  scheduleSave,
123
185
  enqueueFromReply,
124
186
  });
125
187
 
188
+ if (shouldDispatch) {
189
+ clearBncrPendingGroupHistory({ historyMap: groupHistories, parsed });
190
+ }
191
+
126
192
  return {
127
193
  accountId,
128
194
  sessionKey,
@@ -1,59 +1,46 @@
1
1
  import { normalizeAccountId } from '../../core/accounts.ts';
2
- import { resolveBncrChannelPolicy } from '../../core/policy.ts';
3
- import { buildDisplayScopeCandidates } from '../../core/targets.ts';
4
- import type { BncrRoute } from '../../core/types.ts';
5
- import {
6
- defineOpenClawStableChannelIngressIdentity,
7
- resolveOpenClawChannelMessageIngress,
8
- } from '../../openclaw/ingress-runtime.ts';
9
2
  import type { BncrInboundConfig, BncrInboundParamsInput } from './contracts.ts';
10
3
 
11
- type RouteLike = Partial<Pick<BncrRoute, 'groupId' | 'userId' | 'platform'>>;
12
- type AccessGroupsLike = Parameters<typeof resolveOpenClawChannelMessageIngress>[0]['accessGroups'];
13
-
14
4
  export type BncrGateResult = { allowed: true } | { allowed: false; reason: string };
15
5
 
6
+ const REQUIRED_PROTOCOL_VERSION = 'scene-routing-v1';
7
+ const REQUIRED_CAPABILITY = 'scene-routing-v1';
8
+
16
9
  function asString(v: unknown, fallback = ''): string {
17
10
  if (typeof v === 'string') return v;
18
11
  if (v == null) return fallback;
19
12
  return String(v);
20
13
  }
21
14
 
22
- const bncrIngressIdentity = defineOpenClawStableChannelIngressIdentity({
23
- key: 'displayScope',
24
- kind: 'plugin:bncr-display-scope',
25
- normalize: (value: string) => asString(value).trim() || null,
26
- sensitivity: 'pii',
27
- entryIdPrefix: 'bncr-allow',
28
- aliases: [
29
- {
30
- key: 'routeKey',
31
- kind: 'plugin:bncr-route-key',
32
- normalize: (value: string) => asString(value).trim() || null,
33
- sensitivity: 'pii',
34
- },
35
- ],
36
- });
15
+ function asBoolean(v: unknown, fallback = false): boolean {
16
+ if (typeof v === 'boolean') return v;
17
+ if (typeof v === 'number') return v !== 0;
18
+ if (typeof v === 'string') {
19
+ const raw = v.trim().toLowerCase();
20
+ if (!raw) return fallback;
21
+ if (['true', '1', 'yes', 'y', 'on'].includes(raw)) return true;
22
+ if (['false', '0', 'no', 'n', 'off'].includes(raw)) return false;
23
+ }
24
+ return fallback;
25
+ }
37
26
 
38
- function gateReasonFromIngress(reasonCode?: string): string {
39
- switch (reasonCode) {
40
- case 'dm_policy_disabled':
41
- return 'dm disabled';
42
- case 'dm_policy_not_allowlisted':
43
- case 'dm_policy_pairing_required':
44
- return 'dm allowlist blocked';
45
- case 'group_policy_disabled':
46
- return 'group disabled';
47
- case 'group_policy_not_allowlisted':
48
- case 'group_policy_empty_allowlist':
49
- return 'group allowlist blocked';
50
- default:
51
- return reasonCode || 'ingress blocked';
27
+ function asCapabilities(v: unknown): string[] {
28
+ if (Array.isArray(v)) {
29
+ return v.map((item) => asString(item).trim()).filter(Boolean);
30
+ }
31
+ if (typeof v === 'string') {
32
+ return v
33
+ .split(',')
34
+ .map((item) => item.trim())
35
+ .filter(Boolean);
52
36
  }
37
+ return [];
53
38
  }
54
39
 
55
40
  export async function checkBncrMessageGate(params: {
56
- parsed: BncrInboundParamsInput & { route?: RouteLike };
41
+ parsed: BncrInboundParamsInput & {
42
+ route?: Partial<{ groupId: string; userId: string; platform: string }>;
43
+ };
57
44
  cfg: BncrInboundConfig;
58
45
  account: { accountId: string; enabled?: boolean };
59
46
  }): Promise<BncrGateResult> {
@@ -61,57 +48,40 @@ export async function checkBncrMessageGate(params: {
61
48
  const accountId = normalizeAccountId(account?.accountId);
62
49
  const channelCfg = cfg?.channels?.bncr || {};
63
50
  const accountCfg = channelCfg?.accounts?.[accountId] || {};
64
- const policy = resolveBncrChannelPolicy(channelCfg);
65
-
66
- if (policy.enabled === false || account?.enabled === false || accountCfg?.enabled === false) {
51
+ if (
52
+ channelCfg?.enabled === false ||
53
+ account?.enabled === false ||
54
+ accountCfg?.enabled === false
55
+ ) {
67
56
  return { allowed: false, reason: 'account disabled' };
68
57
  }
69
58
 
70
- const route = parsed?.route;
71
- if (!route?.platform || !route?.groupId || !route?.userId) {
72
- return { allowed: false, reason: 'invalid route' };
59
+ const protocolVersion = asString(parsed?.protocolVersion || '').trim();
60
+ const capabilities = asCapabilities(parsed?.capabilities);
61
+ if (
62
+ protocolVersion !== REQUIRED_PROTOCOL_VERSION ||
63
+ !capabilities.includes(REQUIRED_CAPABILITY)
64
+ ) {
65
+ return { allowed: false, reason: 'client protocol outdated' };
73
66
  }
74
- const resolvedRoute: BncrRoute = {
75
- platform: route.platform,
76
- groupId: route.groupId,
77
- userId: route.userId,
78
- };
79
- const isGroup = asString(route?.groupId || '0') !== '0';
80
-
81
- const candidates = buildDisplayScopeCandidates(resolvedRoute);
82
- const displayScope = candidates[0] || '';
83
- const routeKey = candidates.find((candidate) => candidate !== displayScope) || displayScope;
84
67
 
85
- // requireMention 默认值为 false。
86
- // 设计目标:当它未来真正生效时,含义是“群消息只有在明确提到机器人时才允许进入处理链”。
87
- // 但当前 parse 层尚未稳定提取 mentions,上游客户端也未统一透传 mention 信号,
88
- // 因此现阶段即使配置为 true,也仍不做实际拦截,避免出现半实现状态。
89
- const resolved = await resolveOpenClawChannelMessageIngress({
90
- channelId: 'bncr',
91
- accountId,
92
- identity: bncrIngressIdentity,
93
- subject: {
94
- stableId: displayScope,
95
- aliases: { routeKey },
96
- },
97
- conversation: {
98
- kind: isGroup ? 'group' : 'direct',
99
- id: isGroup ? asString(route?.groupId) : asString(route?.userId || displayScope),
100
- },
101
- event: { kind: 'message', authMode: 'inbound', mayPair: !isGroup },
102
- policy: {
103
- dmPolicy: policy.dmPolicy,
104
- groupPolicy: policy.groupPolicy,
105
- groupAllowFromFallbackToAllowFrom: false,
106
- },
107
- allowFrom: policy.dmPolicy === 'open' ? ['*', ...policy.allowFrom] : policy.allowFrom,
108
- groupAllowFrom: policy.groupAllowFrom,
109
- accessGroups: cfg?.accessGroups as AccessGroupsLike,
110
- });
111
-
112
- if (resolved.ingress.admission === 'dispatch' || resolved.ingress.admission === 'observe') {
113
- return { allowed: true };
68
+ const platform = asString(parsed?.platform || '').trim();
69
+ const userId = asString(parsed?.userId || '').trim();
70
+ const clientId = asString(parsed?.clientId || '').trim();
71
+ const groupId = asString(parsed?.groupId || '').trim();
72
+ const isGroupProvided = parsed && Object.hasOwn(parsed, 'isGroup');
73
+ const isAdminProvided = parsed && Object.hasOwn(parsed, 'isAdmin');
74
+ const isGroup = asBoolean(parsed?.isGroup, false);
75
+ if (!platform || !userId || !clientId || !isGroupProvided || !isAdminProvided) {
76
+ return { allowed: false, reason: 'inbound schema incomplete' };
77
+ }
78
+ if (isGroup && !groupId) {
79
+ return { allowed: false, reason: 'inbound schema incomplete' };
114
80
  }
115
81
 
116
- return { allowed: false, reason: gateReasonFromIngress(resolved.ingress.reasonCode) };
82
+ const route = parsed?.route;
83
+ if (!route?.platform || !route?.groupId || !route?.userId) {
84
+ return { allowed: false, reason: 'invalid route' };
85
+ }
86
+ return { allowed: true };
117
87
  }
@@ -0,0 +1,189 @@
1
+ import {
2
+ createChannelHistoryWindow,
3
+ DEFAULT_GROUP_HISTORY_LIMIT,
4
+ type HistoryEntry,
5
+ type HistoryMediaEntry,
6
+ } from 'openclaw/plugin-sdk/reply-history';
7
+ import { formatOpenClawAgentEnvelope } from '../../openclaw/reply-runtime.ts';
8
+ import type { BncrInboundApi } from './contracts.ts';
9
+ import type { ParsedInbound } from './dispatch-prep.ts';
10
+
11
+ export type BncrGroupHistoryMap = Map<string, HistoryEntry[]>;
12
+
13
+ type BncrHistoryMediaKind = NonNullable<HistoryMediaEntry['kind']>;
14
+
15
+ function normalizeTextBody(value: string): string {
16
+ return String(value || '')
17
+ .replace(/\s+/g, ' ')
18
+ .trim();
19
+ }
20
+
21
+ export function buildBncrGroupHistoryKey(parsed: ParsedInbound): string | null {
22
+ if (parsed.peer.kind !== 'group') return null;
23
+ const platform = String(parsed.platform || '').trim();
24
+ const groupId = String(parsed.groupId || '').trim();
25
+ if (!platform || !groupId || groupId === '0') return null;
26
+ return `${platform}:${groupId}`;
27
+ }
28
+
29
+ export function recordBncrPendingGroupText(args: {
30
+ historyMap: BncrGroupHistoryMap;
31
+ parsed: ParsedInbound;
32
+ senderDisplayName: string;
33
+ bodyText: string;
34
+ }) {
35
+ const historyKey = buildBncrGroupHistoryKey(args.parsed);
36
+ const body = normalizeTextBody(args.bodyText);
37
+ if (!historyKey || !body || args.parsed.msgType !== 'text') return;
38
+ createChannelHistoryWindow({ historyMap: args.historyMap }).record({
39
+ historyKey,
40
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
41
+ entry: {
42
+ sender: args.senderDisplayName,
43
+ body,
44
+ timestamp: Date.now(),
45
+ messageId: args.parsed.msgId,
46
+ },
47
+ });
48
+ }
49
+
50
+ function inferBncrHistoryMediaKind(args: {
51
+ msgType?: string;
52
+ mediaContentType?: string;
53
+ }): BncrHistoryMediaKind {
54
+ const msgType = String(args.msgType || '')
55
+ .trim()
56
+ .toLowerCase();
57
+ const contentType = String(args.mediaContentType || '')
58
+ .trim()
59
+ .toLowerCase();
60
+ if (msgType === 'image' || contentType.startsWith('image/')) return 'image';
61
+ if (msgType === 'video' || contentType.startsWith('video/')) return 'video';
62
+ if (msgType === 'audio' || msgType === 'voice' || contentType.startsWith('audio/'))
63
+ return 'audio';
64
+ if (msgType === 'file' || msgType === 'document') return 'document';
65
+ if (contentType) return 'document';
66
+ return 'unknown';
67
+ }
68
+
69
+ function buildBncrHistoryMediaBody(kind: BncrHistoryMediaKind): string {
70
+ return `<media:${kind}>`;
71
+ }
72
+
73
+ export async function recordBncrPendingGroupMedia(args: {
74
+ historyMap: BncrGroupHistoryMap;
75
+ parsed: ParsedInbound;
76
+ senderDisplayName: string;
77
+ bodyText: string;
78
+ mediaItems?: Array<{
79
+ path: string;
80
+ contentType?: string;
81
+ kind?: BncrHistoryMediaKind;
82
+ }>;
83
+ mediaContentType?: string;
84
+ }) {
85
+ const historyKey = buildBncrGroupHistoryKey(args.parsed);
86
+ if (!historyKey || args.parsed.msgType === 'text') return;
87
+ const normalizedMediaItems = Array.isArray(args.mediaItems) ? args.mediaItems : [];
88
+ const itemKinds = normalizedMediaItems
89
+ .map((item) =>
90
+ String(item?.kind || '')
91
+ .trim()
92
+ .toLowerCase(),
93
+ )
94
+ .filter(Boolean);
95
+ const kind = itemKinds[0]
96
+ ? itemKinds.every((candidate) => candidate === itemKinds[0])
97
+ ? (itemKinds[0] as BncrHistoryMediaKind)
98
+ : 'document'
99
+ : inferBncrHistoryMediaKind({
100
+ msgType: args.parsed.msgType,
101
+ mediaContentType: args.mediaContentType,
102
+ });
103
+ const body = normalizeTextBody(args.bodyText) || buildBncrHistoryMediaBody(kind);
104
+ if (normalizedMediaItems.length === 0 || kind !== 'image') {
105
+ createChannelHistoryWindow({ historyMap: args.historyMap }).record({
106
+ historyKey,
107
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
108
+ entry: {
109
+ sender: args.senderDisplayName,
110
+ body,
111
+ timestamp: Date.now(),
112
+ messageId: args.parsed.msgId,
113
+ },
114
+ });
115
+ return;
116
+ }
117
+ await createChannelHistoryWindow({ historyMap: args.historyMap }).recordWithMedia({
118
+ historyKey,
119
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
120
+ entry: {
121
+ sender: args.senderDisplayName,
122
+ body,
123
+ timestamp: Date.now(),
124
+ messageId: args.parsed.msgId,
125
+ },
126
+ messageId: args.parsed.msgId,
127
+ media: normalizedMediaItems.map(
128
+ (item) =>
129
+ ({
130
+ path: item.path,
131
+ contentType: item.contentType || args.mediaContentType || 'image/*',
132
+ kind: 'image',
133
+ messageId: args.parsed.msgId,
134
+ }) satisfies HistoryMediaEntry,
135
+ ),
136
+ });
137
+ }
138
+
139
+ export function buildBncrPendingGroupContext(args: {
140
+ api: BncrInboundApi;
141
+ historyMap: BncrGroupHistoryMap;
142
+ parsed: ParsedInbound;
143
+ channelLabel: string;
144
+ currentTimestamp: number;
145
+ previousTimestamp?: unknown;
146
+ envelope?: unknown;
147
+ currentMessage: string;
148
+ }) {
149
+ const historyKey = buildBncrGroupHistoryKey(args.parsed);
150
+ if (!historyKey) return args.currentMessage;
151
+ return createChannelHistoryWindow({ historyMap: args.historyMap }).buildPendingContext({
152
+ historyKey,
153
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
154
+ currentMessage: args.currentMessage,
155
+ formatEntry: (entry) =>
156
+ formatOpenClawAgentEnvelope(args.api, {
157
+ channel: 'Bncr',
158
+ from: args.channelLabel,
159
+ timestamp: entry.timestamp || args.currentTimestamp,
160
+ previousTimestamp: args.previousTimestamp,
161
+ envelope: args.envelope,
162
+ body: entry.body,
163
+ }),
164
+ });
165
+ }
166
+
167
+ export function buildBncrInboundHistory(args: {
168
+ historyMap: BncrGroupHistoryMap;
169
+ parsed: ParsedInbound;
170
+ }) {
171
+ const historyKey = buildBncrGroupHistoryKey(args.parsed);
172
+ if (!historyKey) return undefined;
173
+ return createChannelHistoryWindow({ historyMap: args.historyMap }).buildInboundHistory({
174
+ historyKey,
175
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
176
+ });
177
+ }
178
+
179
+ export function clearBncrPendingGroupHistory(args: {
180
+ historyMap: BncrGroupHistoryMap;
181
+ parsed: ParsedInbound;
182
+ }) {
183
+ const historyKey = buildBncrGroupHistoryKey(args.parsed);
184
+ if (!historyKey) return;
185
+ createChannelHistoryWindow({ historyMap: args.historyMap }).clear({
186
+ historyKey,
187
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
188
+ });
189
+ }
@@ -53,12 +53,12 @@ export function buildNativeCommandHandledResult(args: {
53
53
 
54
54
  export function buildBncrNativeCommandSessionState(args: {
55
55
  parsed: ParsedInbound;
56
- canonicalAgentId: string;
56
+ sessionAgentId: string;
57
57
  resolvedRoute: { sessionKey: string };
58
58
  }) {
59
- const { parsed, canonicalAgentId, resolvedRoute } = args;
59
+ const { parsed, sessionAgentId, resolvedRoute } = args;
60
60
  const baseSessionKey =
61
- normalizeInboundSessionKey(parsed.sessionKeyfromroute, parsed.route, canonicalAgentId) ||
61
+ normalizeInboundSessionKey(parsed.sessionKeyfromroute, parsed.route, sessionAgentId) ||
62
62
  resolvedRoute.sessionKey;
63
63
  const taskSessionKey = withTaskSessionKey(baseSessionKey, parsed.extracted.taskKey);
64
64
  const sessionKey = taskSessionKey || baseSessionKey;
@@ -87,71 +87,87 @@ export function createNativeCommandTurnContext(args: {
87
87
  senderDisplayName: string;
88
88
  body: string;
89
89
  }): BncrInboundContextPayload | Promise<BncrInboundContextPayload> {
90
- return resolveBncrChannelInboundRuntime(args.api).buildContext({
91
- channel: args.channelId,
92
- provider: args.channelId,
93
- surface: args.channelId,
94
- accountId: args.accountId,
95
- messageId: args.msgId,
96
- timestamp: Date.now(),
97
- from: args.senderIdForContext,
98
- sender: {
99
- id: args.senderIdForContext,
100
- name: args.senderDisplayName,
101
- username: args.senderDisplayName,
102
- },
103
- conversation: {
104
- kind: args.peer.kind,
105
- id: args.peer.id,
106
- label: args.displayTo,
107
- routePeer: {
90
+ return Promise.resolve(
91
+ resolveBncrChannelInboundRuntime(args.api).buildContext({
92
+ channel: args.channelId,
93
+ provider: args.channelId,
94
+ surface: args.channelId,
95
+ accountId: args.accountId,
96
+ messageId: args.msgId,
97
+ timestamp: Date.now(),
98
+ from: args.senderIdForContext,
99
+ sender: {
100
+ id: args.senderIdForContext,
101
+ name: args.senderDisplayName,
102
+ username: args.senderDisplayName,
103
+ },
104
+ conversation: {
108
105
  kind: args.peer.kind,
109
106
  id: args.peer.id,
107
+ label: args.displayTo,
108
+ routePeer: {
109
+ kind: args.peer.kind,
110
+ id: args.peer.id,
111
+ },
110
112
  },
111
- },
112
- route: {
113
- agentId: args.resolvedRoute.agentId,
114
- accountId: args.accountId,
115
- routeSessionKey: args.resolvedRoute.sessionKey,
116
- dispatchSessionKey: args.sessionKey,
117
- mainSessionKey: args.resolvedRoute.mainSessionKey,
118
- },
119
- reply: {
120
- to: args.displayTo,
121
- originatingTo: args.originatingTo,
122
- replyToId: args.msgId,
123
- },
124
- message: {
125
- inboundEventKind: 'user_request',
126
- body: args.body,
127
- rawBody: args.body,
128
- bodyForAgent: args.body,
129
- commandBody: args.body,
130
- envelopeFrom: args.originatingTo,
131
- senderLabel: args.senderDisplayName,
132
- },
133
- commandTurn: {
113
+ route: {
114
+ agentId: args.resolvedRoute.agentId,
115
+ accountId: args.accountId,
116
+ routeSessionKey: args.resolvedRoute.sessionKey,
117
+ dispatchSessionKey: args.sessionKey,
118
+ mainSessionKey: args.resolvedRoute.mainSessionKey,
119
+ },
120
+ reply: {
121
+ to: args.displayTo,
122
+ originatingTo: args.originatingTo,
123
+ replyToId: args.msgId,
124
+ },
125
+ message: {
126
+ inboundEventKind: 'user_request',
127
+ body: args.body,
128
+ rawBody: args.body,
129
+ bodyForAgent: args.body,
130
+ commandBody: args.body,
131
+ envelopeFrom: args.originatingTo,
132
+ senderLabel: args.senderDisplayName,
133
+ },
134
+ commandTurn: {
135
+ kind: 'native',
136
+ source: 'native',
137
+ authorized: true,
138
+ body: args.body,
139
+ },
140
+ access: {
141
+ mentions: {
142
+ canDetectMention: true,
143
+ wasMentioned: true,
144
+ effectiveWasMentioned: true,
145
+ },
146
+ commands: {
147
+ authorized: true,
148
+ allowTextCommands: true,
149
+ useAccessGroups: false,
150
+ authorizers: [],
151
+ },
152
+ },
153
+ extra: {
154
+ OriginatingChannel: args.channelId,
155
+ },
156
+ }),
157
+ ).then((ctx) => {
158
+ ctx.From = args.senderIdForContext;
159
+ ctx.To = args.displayTo;
160
+ ctx.SenderId = args.senderIdForContext;
161
+ ctx.OriginatingChannel = args.channelId;
162
+ ctx.CommandAuthorized = true;
163
+ ctx.CommandSource = 'native';
164
+ ctx.CommandTurn = {
134
165
  kind: 'native',
135
166
  source: 'native',
136
167
  authorized: true,
137
168
  body: args.body,
138
- },
139
- access: {
140
- mentions: {
141
- canDetectMention: true,
142
- wasMentioned: true,
143
- effectiveWasMentioned: true,
144
- },
145
- commands: {
146
- authorized: true,
147
- allowTextCommands: true,
148
- useAccessGroups: false,
149
- authorizers: [],
150
- },
151
- },
152
- extra: {
153
- OriginatingChannel: args.channelId,
154
- },
169
+ };
170
+ return ctx;
155
171
  });
156
172
  }
157
173