@xmoxmo/bncr 0.2.6 → 0.2.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.
- package/README.md +7 -1
- package/index.ts +30 -15
- package/package.json +4 -3
- package/scripts/check-pack.mjs +61 -0
- package/scripts/selfcheck.mjs +10 -0
- package/src/channel.ts +130 -46
- package/src/core/targets.ts +10 -1
- package/src/messaging/inbound/commands.ts +20 -10
- package/src/messaging/inbound/context-facts.ts +200 -0
- package/src/messaging/inbound/dispatch.ts +66 -14
- package/src/messaging/inbound/gate.ts +66 -26
- package/src/messaging/inbound/runtime-compat.ts +39 -0
- package/src/messaging/inbound/session-label.ts +7 -7
- package/src/messaging/outbound/durable-message-adapter.ts +107 -0
- package/src/messaging/outbound/durable-queue-adapter.ts +157 -0
- package/src/messaging/outbound/session-route.ts +2 -2
- package/src/openclaw/config-runtime.ts +52 -0
- package/src/openclaw/inbound-session-runtime.ts +94 -0
- package/src/openclaw/ingress-runtime.ts +35 -0
- package/src/openclaw/media-runtime.ts +73 -0
- package/src/openclaw/reply-runtime.ts +104 -0
- package/src/openclaw/routing-runtime.ts +48 -0
- package/src/openclaw/sdk-helpers.ts +20 -0
- package/src/openclaw/session-route-runtime.ts +15 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { resolvePinnedMainDmOwnerFromAllowlist } from 'openclaw/plugin-sdk/conversation-runtime';
|
|
2
|
-
import { resolveInboundLastRouteSessionKey } from 'openclaw/plugin-sdk/routing';
|
|
3
1
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
4
2
|
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
5
3
|
import {
|
|
@@ -8,11 +6,22 @@ import {
|
|
|
8
6
|
withTaskSessionKey,
|
|
9
7
|
} from '../../core/targets.ts';
|
|
10
8
|
import { buildBncrReplyConfig } from './reply-config.ts';
|
|
9
|
+
import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
|
|
11
10
|
import {
|
|
12
11
|
buildBncrInboundSessionIdentityPatch,
|
|
13
12
|
recordAndPatchBncrInboundSessionEntry,
|
|
14
13
|
wrapBncrInboundRecordSessionLabelCorrection,
|
|
15
14
|
} from './session-label.ts';
|
|
15
|
+
import { dispatchOpenClawReplyWithBufferedBlockDispatcher } from '../../openclaw/reply-runtime.ts';
|
|
16
|
+
import {
|
|
17
|
+
resolveOpenClawAgentRoute,
|
|
18
|
+
resolveOpenClawInboundLastRouteSessionKey,
|
|
19
|
+
} from '../../openclaw/routing-runtime.ts';
|
|
20
|
+
import {
|
|
21
|
+
recordBncrInboundSession,
|
|
22
|
+
resolveBncrInboundSessionStorePath,
|
|
23
|
+
resolveBncrPinnedMainDmOwnerFromAllowlist,
|
|
24
|
+
} from '../../openclaw/inbound-session-runtime.ts';
|
|
16
25
|
|
|
17
26
|
type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
|
|
18
27
|
|
|
@@ -110,7 +119,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
110
119
|
{ debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
|
|
111
120
|
);
|
|
112
121
|
|
|
113
|
-
const resolvedRoute = api
|
|
122
|
+
const resolvedRoute = resolveOpenClawAgentRoute(api, {
|
|
114
123
|
cfg,
|
|
115
124
|
channel: channelId,
|
|
116
125
|
accountId,
|
|
@@ -137,11 +146,12 @@ export async function handleBncrNativeCommand(params: {
|
|
|
137
146
|
}
|
|
138
147
|
const senderIdForContext = clientId || displayTo;
|
|
139
148
|
const senderDisplayName = clientId ? 'bncr-client' : displayTo;
|
|
140
|
-
const storePath =
|
|
149
|
+
const storePath = resolveBncrInboundSessionStorePath({
|
|
150
|
+
storeConfig: cfg?.session?.store,
|
|
141
151
|
agentId: resolvedRoute.agentId,
|
|
142
152
|
});
|
|
143
153
|
|
|
144
|
-
const ctxPayload = api.
|
|
154
|
+
const ctxPayload = resolveBncrChannelInboundRuntime(api).buildContext({
|
|
145
155
|
channel: channelId,
|
|
146
156
|
provider: channelId,
|
|
147
157
|
surface: channelId,
|
|
@@ -259,13 +269,13 @@ export async function handleBncrNativeCommand(params: {
|
|
|
259
269
|
const channelPolicy = resolveBncrChannelPolicy(cfg?.channels?.bncr || {});
|
|
260
270
|
const pinnedMainDmOwner =
|
|
261
271
|
peer.kind === 'direct'
|
|
262
|
-
?
|
|
272
|
+
? resolveBncrPinnedMainDmOwnerFromAllowlist({
|
|
263
273
|
dmScope: cfg?.session?.dmScope,
|
|
264
274
|
allowFrom: channelPolicy.allowFrom,
|
|
265
275
|
normalizeEntry: (entry: string) => String(entry || '').trim(),
|
|
266
276
|
})
|
|
267
277
|
: null;
|
|
268
|
-
const inboundLastRouteSessionKey =
|
|
278
|
+
const inboundLastRouteSessionKey = resolveOpenClawInboundLastRouteSessionKey({
|
|
269
279
|
route: resolvedRoute,
|
|
270
280
|
sessionKey,
|
|
271
281
|
});
|
|
@@ -282,7 +292,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
282
292
|
},
|
|
283
293
|
{ debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
|
|
284
294
|
);
|
|
285
|
-
await api.
|
|
295
|
+
await resolveBncrChannelInboundRuntime(api).run({
|
|
286
296
|
channel: channelId,
|
|
287
297
|
accountId,
|
|
288
298
|
raw: parsed,
|
|
@@ -302,7 +312,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
302
312
|
storePath,
|
|
303
313
|
ctxPayload,
|
|
304
314
|
recordInboundSession: wrapBncrInboundRecordSessionLabelCorrection({
|
|
305
|
-
recordInboundSession:
|
|
315
|
+
recordInboundSession: recordBncrInboundSession,
|
|
306
316
|
expectedLabel: displayTo,
|
|
307
317
|
}),
|
|
308
318
|
record: {
|
|
@@ -330,7 +340,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
330
340
|
},
|
|
331
341
|
},
|
|
332
342
|
runDispatch: () =>
|
|
333
|
-
api
|
|
343
|
+
dispatchOpenClawReplyWithBufferedBlockDispatcher(api, {
|
|
334
344
|
ctx: ctxPayload,
|
|
335
345
|
cfg: effectiveReply.replyCfg,
|
|
336
346
|
dispatcherOptions: {
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
export type BncrStructuredContextFactsInput = {
|
|
2
|
+
channelId: string;
|
|
3
|
+
accountId: string;
|
|
4
|
+
route: {
|
|
5
|
+
agentId?: string;
|
|
6
|
+
routeSessionKey?: string;
|
|
7
|
+
dispatchSessionKey?: string;
|
|
8
|
+
mainSessionKey?: string;
|
|
9
|
+
};
|
|
10
|
+
conversation: {
|
|
11
|
+
kind: string;
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
};
|
|
15
|
+
reply: {
|
|
16
|
+
to: string;
|
|
17
|
+
originatingTo: string;
|
|
18
|
+
};
|
|
19
|
+
sender: {
|
|
20
|
+
id: string;
|
|
21
|
+
displayName?: string;
|
|
22
|
+
};
|
|
23
|
+
message: {
|
|
24
|
+
id?: string | null;
|
|
25
|
+
rawBody: string;
|
|
26
|
+
bodyForAgent?: string;
|
|
27
|
+
commandBody?: string;
|
|
28
|
+
envelopeBody?: string;
|
|
29
|
+
};
|
|
30
|
+
media?: Array<{
|
|
31
|
+
path: string;
|
|
32
|
+
contentType?: string;
|
|
33
|
+
kind?: string;
|
|
34
|
+
messageId?: string;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function buildBncrStructuredContextFacts(input: BncrStructuredContextFactsInput) {
|
|
39
|
+
const rawBody = input.message.rawBody;
|
|
40
|
+
return {
|
|
41
|
+
channel: {
|
|
42
|
+
id: input.channelId,
|
|
43
|
+
accountId: input.accountId,
|
|
44
|
+
},
|
|
45
|
+
route: {
|
|
46
|
+
agentId: input.route.agentId,
|
|
47
|
+
routeSessionKey: input.route.routeSessionKey,
|
|
48
|
+
dispatchSessionKey: input.route.dispatchSessionKey,
|
|
49
|
+
mainSessionKey: input.route.mainSessionKey,
|
|
50
|
+
},
|
|
51
|
+
conversation: {
|
|
52
|
+
kind: input.conversation.kind,
|
|
53
|
+
id: input.conversation.id,
|
|
54
|
+
label: input.conversation.label,
|
|
55
|
+
},
|
|
56
|
+
reply: {
|
|
57
|
+
to: input.reply.to,
|
|
58
|
+
originatingTo: input.reply.originatingTo,
|
|
59
|
+
},
|
|
60
|
+
sender: {
|
|
61
|
+
id: input.sender.id,
|
|
62
|
+
displayName: input.sender.displayName || input.sender.id,
|
|
63
|
+
},
|
|
64
|
+
message: {
|
|
65
|
+
id: input.message.id || undefined,
|
|
66
|
+
rawBody,
|
|
67
|
+
bodyForAgent: input.message.bodyForAgent ?? rawBody,
|
|
68
|
+
commandBody: input.message.commandBody ?? rawBody,
|
|
69
|
+
envelopeBody: input.message.envelopeBody,
|
|
70
|
+
},
|
|
71
|
+
media: (input.media || []).map((item) => ({
|
|
72
|
+
path: item.path,
|
|
73
|
+
contentType: item.contentType,
|
|
74
|
+
kind: item.kind,
|
|
75
|
+
messageId: item.messageId,
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Keep this payload intentionally small: OpenClaw already renders standard
|
|
81
|
+
// conversation/sender/message metadata as untrusted context. Only include
|
|
82
|
+
// bncr-specific facts that are not otherwise visible to the model, so normal
|
|
83
|
+
// text turns do not get a duplicate "Bncr inbound context" JSON block.
|
|
84
|
+
export function buildBncrPromptVisibleContextFacts(
|
|
85
|
+
facts: ReturnType<typeof buildBncrStructuredContextFacts>,
|
|
86
|
+
) {
|
|
87
|
+
const result: {
|
|
88
|
+
reply?: {
|
|
89
|
+
to: string;
|
|
90
|
+
originatingTo: string;
|
|
91
|
+
};
|
|
92
|
+
media?: Array<{
|
|
93
|
+
contentType?: string;
|
|
94
|
+
kind?: string;
|
|
95
|
+
messageId?: string;
|
|
96
|
+
}>;
|
|
97
|
+
} = {};
|
|
98
|
+
|
|
99
|
+
if (facts.reply.originatingTo !== facts.reply.to) {
|
|
100
|
+
result.reply = {
|
|
101
|
+
to: facts.reply.to,
|
|
102
|
+
originatingTo: facts.reply.originatingTo,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (facts.media.length > 0) {
|
|
107
|
+
result.media = facts.media.map((item) => ({
|
|
108
|
+
contentType: item.contentType,
|
|
109
|
+
kind: item.kind,
|
|
110
|
+
messageId: item.messageId,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function inferBncrStructuredMediaKind(contentType: string | undefined) {
|
|
118
|
+
if (contentType?.startsWith('image/')) return 'image';
|
|
119
|
+
if (contentType?.startsWith('video/')) return 'video';
|
|
120
|
+
if (contentType?.startsWith('audio/')) return 'audio';
|
|
121
|
+
return 'document';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
125
|
+
channelId: string;
|
|
126
|
+
parsed: {
|
|
127
|
+
accountId: string;
|
|
128
|
+
peer: {
|
|
129
|
+
kind: string;
|
|
130
|
+
id: string;
|
|
131
|
+
};
|
|
132
|
+
clientId?: string;
|
|
133
|
+
msgId?: string;
|
|
134
|
+
mimeType?: string;
|
|
135
|
+
};
|
|
136
|
+
resolution: {
|
|
137
|
+
chatType: string;
|
|
138
|
+
canonicalTo: string;
|
|
139
|
+
originatingTo: string;
|
|
140
|
+
resolvedRoute: {
|
|
141
|
+
agentId?: string;
|
|
142
|
+
sessionKey?: string;
|
|
143
|
+
mainSessionKey?: string;
|
|
144
|
+
};
|
|
145
|
+
dispatchSessionKey?: string;
|
|
146
|
+
};
|
|
147
|
+
prepared: {
|
|
148
|
+
rawBody: string;
|
|
149
|
+
body?: string;
|
|
150
|
+
mediaPath?: string | null;
|
|
151
|
+
};
|
|
152
|
+
senderIdForContext: string;
|
|
153
|
+
senderDisplayName?: string;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export function buildBncrStructuredContextFactsFromInboundParts(
|
|
157
|
+
input: BncrStructuredContextFactsFromInboundPartsInput,
|
|
158
|
+
) {
|
|
159
|
+
const mediaPath = input.prepared.mediaPath || undefined;
|
|
160
|
+
return buildBncrStructuredContextFacts({
|
|
161
|
+
channelId: input.channelId,
|
|
162
|
+
accountId: input.parsed.accountId,
|
|
163
|
+
route: {
|
|
164
|
+
agentId: input.resolution.resolvedRoute.agentId,
|
|
165
|
+
routeSessionKey: input.resolution.resolvedRoute.sessionKey,
|
|
166
|
+
dispatchSessionKey: input.resolution.dispatchSessionKey,
|
|
167
|
+
mainSessionKey: input.resolution.resolvedRoute.mainSessionKey,
|
|
168
|
+
},
|
|
169
|
+
conversation: {
|
|
170
|
+
kind: input.resolution.chatType,
|
|
171
|
+
id: input.parsed.peer.id,
|
|
172
|
+
label: input.resolution.canonicalTo,
|
|
173
|
+
},
|
|
174
|
+
reply: {
|
|
175
|
+
to: input.resolution.canonicalTo,
|
|
176
|
+
originatingTo: input.resolution.originatingTo,
|
|
177
|
+
},
|
|
178
|
+
sender: {
|
|
179
|
+
id: input.senderIdForContext,
|
|
180
|
+
displayName: input.senderDisplayName,
|
|
181
|
+
},
|
|
182
|
+
message: {
|
|
183
|
+
id: input.parsed.msgId,
|
|
184
|
+
rawBody: input.prepared.rawBody,
|
|
185
|
+
bodyForAgent: input.prepared.rawBody,
|
|
186
|
+
commandBody: input.prepared.rawBody,
|
|
187
|
+
envelopeBody: input.prepared.body,
|
|
188
|
+
},
|
|
189
|
+
media: mediaPath
|
|
190
|
+
? [
|
|
191
|
+
{
|
|
192
|
+
path: mediaPath,
|
|
193
|
+
contentType: input.parsed.mimeType,
|
|
194
|
+
kind: inferBncrStructuredMediaKind(input.parsed.mimeType),
|
|
195
|
+
messageId: input.parsed.msgId,
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
: [],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { resolvePinnedMainDmOwnerFromAllowlist } from 'openclaw/plugin-sdk/conversation-runtime';
|
|
3
|
-
import { resolveInboundLastRouteSessionKey } from 'openclaw/plugin-sdk/routing';
|
|
4
2
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
5
3
|
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
6
4
|
import {
|
|
@@ -9,8 +7,29 @@ import {
|
|
|
9
7
|
withTaskSessionKey,
|
|
10
8
|
} from '../../core/targets.ts';
|
|
11
9
|
import { handleBncrNativeCommand } from './commands.ts';
|
|
10
|
+
import {
|
|
11
|
+
buildBncrPromptVisibleContextFacts,
|
|
12
|
+
buildBncrStructuredContextFactsFromInboundParts,
|
|
13
|
+
} from './context-facts.ts';
|
|
12
14
|
import { buildBncrReplyConfig } from './reply-config.ts';
|
|
15
|
+
import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
|
|
13
16
|
import { wrapBncrInboundRecordSessionLabelCorrection } from './session-label.ts';
|
|
17
|
+
import { saveOpenClawChannelMediaBuffer } from '../../openclaw/media-runtime.ts';
|
|
18
|
+
import {
|
|
19
|
+
dispatchOpenClawReplyWithBufferedBlockDispatcher,
|
|
20
|
+
formatOpenClawAgentEnvelope,
|
|
21
|
+
resolveOpenClawEnvelopeFormatOptions,
|
|
22
|
+
} from '../../openclaw/reply-runtime.ts';
|
|
23
|
+
import {
|
|
24
|
+
resolveOpenClawAgentRoute,
|
|
25
|
+
resolveOpenClawInboundLastRouteSessionKey,
|
|
26
|
+
} from '../../openclaw/routing-runtime.ts';
|
|
27
|
+
import {
|
|
28
|
+
readBncrSessionUpdatedAt,
|
|
29
|
+
recordBncrInboundSession,
|
|
30
|
+
resolveBncrInboundSessionStorePath,
|
|
31
|
+
resolveBncrPinnedMainDmOwnerFromAllowlist,
|
|
32
|
+
} from '../../openclaw/inbound-session-runtime.ts';
|
|
14
33
|
|
|
15
34
|
type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
|
|
16
35
|
|
|
@@ -93,7 +112,7 @@ export function resolveBncrInboundConversation(args: {
|
|
|
93
112
|
const { api, cfg, channelId, parsed, canonicalAgentId } = args;
|
|
94
113
|
const { accountId, route, peer, sessionKeyfromroute, providedOriginatingTo, extracted } = parsed;
|
|
95
114
|
|
|
96
|
-
const resolvedRoute = api
|
|
115
|
+
const resolvedRoute = resolveOpenClawAgentRoute(api, {
|
|
97
116
|
cfg,
|
|
98
117
|
channel: channelId,
|
|
99
118
|
accountId,
|
|
@@ -149,14 +168,16 @@ async function prepareBncrInboundSessionContext(args: {
|
|
|
149
168
|
rememberSessionRoute(taskSessionKey, accountId, route);
|
|
150
169
|
}
|
|
151
170
|
|
|
152
|
-
const storePath =
|
|
171
|
+
const storePath = resolveBncrInboundSessionStorePath({
|
|
172
|
+
storeConfig: cfg?.session?.store,
|
|
153
173
|
agentId: resolvedRoute.agentId,
|
|
154
174
|
});
|
|
155
175
|
|
|
156
176
|
let mediaPath: string | undefined;
|
|
157
177
|
if (mediaBase64) {
|
|
158
178
|
const mediaBuf = decodeInboundMediaBase64(mediaBase64);
|
|
159
|
-
const saved = await
|
|
179
|
+
const saved = await saveOpenClawChannelMediaBuffer(
|
|
180
|
+
api,
|
|
160
181
|
mediaBuf,
|
|
161
182
|
mimeType,
|
|
162
183
|
'inbound',
|
|
@@ -169,15 +190,15 @@ async function prepareBncrInboundSessionContext(args: {
|
|
|
169
190
|
}
|
|
170
191
|
|
|
171
192
|
const rawBody = extracted.text || (msgType === 'text' ? '' : `[${msgType}]`);
|
|
172
|
-
const body = api
|
|
193
|
+
const body = formatOpenClawAgentEnvelope(api, {
|
|
173
194
|
channel: 'Bncr',
|
|
174
195
|
from: `${platform}:${groupId}:${userId}`,
|
|
175
196
|
timestamp: Date.now(),
|
|
176
|
-
previousTimestamp: api
|
|
197
|
+
previousTimestamp: readBncrSessionUpdatedAt(api, {
|
|
177
198
|
storePath,
|
|
178
199
|
sessionKey: dispatchSessionKey,
|
|
179
200
|
}),
|
|
180
|
-
envelope: api
|
|
201
|
+
envelope: resolveOpenClawEnvelopeFormatOptions(api, cfg),
|
|
181
202
|
body: rawBody,
|
|
182
203
|
});
|
|
183
204
|
|
|
@@ -192,6 +213,7 @@ async function prepareBncrInboundSessionContext(args: {
|
|
|
192
213
|
function buildBncrInboundTurnContext(args: {
|
|
193
214
|
api: any;
|
|
194
215
|
channelId: string;
|
|
216
|
+
parsed: ParsedInbound;
|
|
195
217
|
msgId?: string | null;
|
|
196
218
|
mimeType?: string;
|
|
197
219
|
mediaPath?: string;
|
|
@@ -207,6 +229,7 @@ function buildBncrInboundTurnContext(args: {
|
|
|
207
229
|
const {
|
|
208
230
|
api,
|
|
209
231
|
channelId,
|
|
232
|
+
parsed,
|
|
210
233
|
msgId,
|
|
211
234
|
mimeType,
|
|
212
235
|
mediaPath,
|
|
@@ -216,8 +239,31 @@ function buildBncrInboundTurnContext(args: {
|
|
|
216
239
|
resolution,
|
|
217
240
|
prepared,
|
|
218
241
|
} = args;
|
|
242
|
+
const structuredContextFacts = buildBncrStructuredContextFactsFromInboundParts({
|
|
243
|
+
channelId,
|
|
244
|
+
parsed,
|
|
245
|
+
resolution,
|
|
246
|
+
prepared: {
|
|
247
|
+
rawBody: prepared.rawBody,
|
|
248
|
+
body: prepared.body,
|
|
249
|
+
mediaPath,
|
|
250
|
+
},
|
|
251
|
+
senderIdForContext,
|
|
252
|
+
senderDisplayName,
|
|
253
|
+
});
|
|
254
|
+
const promptVisibleContextFacts = buildBncrPromptVisibleContextFacts(structuredContextFacts);
|
|
255
|
+
const supplementalUntrustedContext = Object.keys(promptVisibleContextFacts).length
|
|
256
|
+
? [
|
|
257
|
+
{
|
|
258
|
+
label: 'Bncr inbound context',
|
|
259
|
+
source: channelId,
|
|
260
|
+
type: 'bncr.inbound_context',
|
|
261
|
+
payload: promptVisibleContextFacts,
|
|
262
|
+
},
|
|
263
|
+
]
|
|
264
|
+
: [];
|
|
219
265
|
|
|
220
|
-
return api.
|
|
266
|
+
return resolveBncrChannelInboundRuntime(api).buildContext({
|
|
221
267
|
channel: channelId,
|
|
222
268
|
provider: channelId,
|
|
223
269
|
surface: channelId,
|
|
@@ -275,8 +321,13 @@ function buildBncrInboundTurnContext(args: {
|
|
|
275
321
|
},
|
|
276
322
|
]
|
|
277
323
|
: [],
|
|
324
|
+
supplemental: {
|
|
325
|
+
untrustedContext: supplementalUntrustedContext,
|
|
326
|
+
},
|
|
278
327
|
extra: {
|
|
279
328
|
OriginatingChannel: channelId,
|
|
329
|
+
BncrStructuredContextFacts: structuredContextFacts,
|
|
330
|
+
StructuredContextFacts: structuredContextFacts,
|
|
280
331
|
},
|
|
281
332
|
});
|
|
282
333
|
}
|
|
@@ -291,7 +342,7 @@ function buildBncrInboundRecordUpdateLastRoute(args: {
|
|
|
291
342
|
const { channelId, peer, senderIdForContext, resolution, pinnedMainDmOwner } = args;
|
|
292
343
|
if (peer.kind !== 'direct') return undefined;
|
|
293
344
|
|
|
294
|
-
const sessionKey =
|
|
345
|
+
const sessionKey = resolveOpenClawInboundLastRouteSessionKey({
|
|
295
346
|
route: resolution.resolvedRoute,
|
|
296
347
|
sessionKey: resolution.dispatchSessionKey,
|
|
297
348
|
});
|
|
@@ -406,6 +457,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
406
457
|
const ctxPayload = buildBncrInboundTurnContext({
|
|
407
458
|
api,
|
|
408
459
|
channelId,
|
|
460
|
+
parsed,
|
|
409
461
|
msgId,
|
|
410
462
|
mimeType,
|
|
411
463
|
mediaPath,
|
|
@@ -420,7 +472,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
420
472
|
const channelPolicy = resolveBncrChannelPolicy(cfg?.channels?.bncr || {});
|
|
421
473
|
const pinnedMainDmOwner =
|
|
422
474
|
peer.kind === 'direct'
|
|
423
|
-
?
|
|
475
|
+
? resolveBncrPinnedMainDmOwnerFromAllowlist({
|
|
424
476
|
dmScope: cfg?.session?.dmScope,
|
|
425
477
|
allowFrom: channelPolicy.allowFrom,
|
|
426
478
|
normalizeEntry: (entry: string) => String(entry || '').trim(),
|
|
@@ -434,7 +486,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
434
486
|
pinnedMainDmOwner,
|
|
435
487
|
});
|
|
436
488
|
|
|
437
|
-
await api.
|
|
489
|
+
await resolveBncrChannelInboundRuntime(api).run({
|
|
438
490
|
channel: channelId,
|
|
439
491
|
accountId,
|
|
440
492
|
raw: parsed,
|
|
@@ -454,7 +506,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
454
506
|
storePath,
|
|
455
507
|
ctxPayload,
|
|
456
508
|
recordInboundSession: wrapBncrInboundRecordSessionLabelCorrection({
|
|
457
|
-
recordInboundSession:
|
|
509
|
+
recordInboundSession: recordBncrInboundSession,
|
|
458
510
|
expectedLabel: canonicalTo,
|
|
459
511
|
}),
|
|
460
512
|
record: {
|
|
@@ -464,7 +516,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
464
516
|
},
|
|
465
517
|
},
|
|
466
518
|
runDispatch: () =>
|
|
467
|
-
api
|
|
519
|
+
dispatchOpenClawReplyWithBufferedBlockDispatcher(api, {
|
|
468
520
|
ctx: ctxPayload,
|
|
469
521
|
cfg: effectiveReply.replyCfg,
|
|
470
522
|
dispatcherOptions: {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
2
2
|
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
3
3
|
import { buildDisplayScopeCandidates } from '../../core/targets.ts';
|
|
4
|
+
import {
|
|
5
|
+
defineOpenClawStableChannelIngressIdentity,
|
|
6
|
+
resolveOpenClawChannelMessageIngress,
|
|
7
|
+
} from '../../openclaw/ingress-runtime.ts';
|
|
4
8
|
|
|
5
9
|
export type BncrGateResult = { allowed: true } | { allowed: false; reason: string };
|
|
6
10
|
|
|
@@ -10,13 +14,40 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
10
14
|
return String(v);
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
const bncrIngressIdentity = defineOpenClawStableChannelIngressIdentity({
|
|
18
|
+
key: 'displayScope',
|
|
19
|
+
kind: 'plugin:bncr-display-scope',
|
|
20
|
+
normalize: (value: string) => asString(value).trim() || null,
|
|
21
|
+
sensitivity: 'pii',
|
|
22
|
+
entryIdPrefix: 'bncr-allow',
|
|
23
|
+
aliases: [
|
|
24
|
+
{
|
|
25
|
+
key: 'routeKey',
|
|
26
|
+
kind: 'plugin:bncr-route-key',
|
|
27
|
+
normalize: (value: string) => asString(value).trim() || null,
|
|
28
|
+
sensitivity: 'pii',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function gateReasonFromIngress(reasonCode?: string): string {
|
|
34
|
+
switch (reasonCode) {
|
|
35
|
+
case 'dm_policy_disabled':
|
|
36
|
+
return 'dm disabled';
|
|
37
|
+
case 'dm_policy_not_allowlisted':
|
|
38
|
+
case 'dm_policy_pairing_required':
|
|
39
|
+
return 'dm allowlist blocked';
|
|
40
|
+
case 'group_policy_disabled':
|
|
41
|
+
return 'group disabled';
|
|
42
|
+
case 'group_policy_not_allowlisted':
|
|
43
|
+
case 'group_policy_empty_allowlist':
|
|
44
|
+
return 'group allowlist blocked';
|
|
45
|
+
default:
|
|
46
|
+
return reasonCode || 'ingress blocked';
|
|
47
|
+
}
|
|
17
48
|
}
|
|
18
49
|
|
|
19
|
-
export function checkBncrMessageGate(params: {
|
|
50
|
+
export async function checkBncrMessageGate(params: {
|
|
20
51
|
parsed: any;
|
|
21
52
|
cfg: any;
|
|
22
53
|
account: { accountId: string; enabled?: boolean };
|
|
@@ -34,31 +65,40 @@ export function checkBncrMessageGate(params: {
|
|
|
34
65
|
const route = parsed?.route;
|
|
35
66
|
const isGroup = asString(route?.groupId || '0') !== '0';
|
|
36
67
|
|
|
37
|
-
if (!isGroup && policy.dmPolicy === 'disabled') {
|
|
38
|
-
return { allowed: false, reason: 'dm disabled' };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (isGroup && policy.groupPolicy === 'disabled') {
|
|
42
|
-
return { allowed: false, reason: 'group disabled' };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
68
|
const candidates = buildDisplayScopeCandidates(route);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!matchesAllowList(policy.allowFrom, candidates)) {
|
|
49
|
-
return { allowed: false, reason: 'dm allowlist blocked' };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (isGroup && policy.groupPolicy === 'allowlist') {
|
|
54
|
-
if (!matchesAllowList(policy.groupAllowFrom, candidates)) {
|
|
55
|
-
return { allowed: false, reason: 'group allowlist blocked' };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
69
|
+
const displayScope = candidates[0] || '';
|
|
70
|
+
const routeKey = candidates.find((candidate) => candidate !== displayScope) || displayScope;
|
|
58
71
|
|
|
59
72
|
// requireMention 默认值为 false。
|
|
60
73
|
// 设计目标:当它未来真正生效时,含义是“群消息只有在明确提到机器人时才允许进入处理链”。
|
|
61
74
|
// 但当前 parse 层尚未稳定提取 mentions,上游客户端也未统一透传 mention 信号,
|
|
62
75
|
// 因此现阶段即使配置为 true,也仍不做实际拦截,避免出现半实现状态。
|
|
63
|
-
|
|
76
|
+
const resolved = await resolveOpenClawChannelMessageIngress({
|
|
77
|
+
channelId: 'bncr',
|
|
78
|
+
accountId,
|
|
79
|
+
identity: bncrIngressIdentity,
|
|
80
|
+
subject: {
|
|
81
|
+
stableId: displayScope,
|
|
82
|
+
aliases: { routeKey },
|
|
83
|
+
},
|
|
84
|
+
conversation: {
|
|
85
|
+
kind: isGroup ? 'group' : 'direct',
|
|
86
|
+
id: isGroup ? asString(route?.groupId) : asString(route?.userId || displayScope),
|
|
87
|
+
},
|
|
88
|
+
event: { kind: 'message', authMode: 'inbound', mayPair: !isGroup },
|
|
89
|
+
policy: {
|
|
90
|
+
dmPolicy: policy.dmPolicy,
|
|
91
|
+
groupPolicy: policy.groupPolicy,
|
|
92
|
+
groupAllowFromFallbackToAllowFrom: false,
|
|
93
|
+
},
|
|
94
|
+
allowFrom: policy.dmPolicy === 'open' ? ['*', ...policy.allowFrom] : policy.allowFrom,
|
|
95
|
+
groupAllowFrom: policy.groupAllowFrom,
|
|
96
|
+
accessGroups: cfg?.accessGroups,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (resolved.ingress.admission === 'dispatch' || resolved.ingress.admission === 'observe') {
|
|
100
|
+
return { allowed: true };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { allowed: false, reason: gateReasonFromIngress(resolved.ingress.reasonCode) };
|
|
64
104
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
2
|
+
|
|
3
|
+
type ChannelRuntimeCompat = {
|
|
4
|
+
buildContext: (...args: any[]) => any;
|
|
5
|
+
run: (...args: any[]) => Promise<any> | any;
|
|
6
|
+
runPreparedReply?: (...args: any[]) => Promise<any> | any;
|
|
7
|
+
dispatchReply?: (...args: any[]) => Promise<any> | any;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let warnedLegacyTurnRuntime = false;
|
|
11
|
+
|
|
12
|
+
export function resolveBncrChannelInboundRuntime(api: any): ChannelRuntimeCompat {
|
|
13
|
+
const channelRuntime = api?.runtime?.channel;
|
|
14
|
+
const inboundRuntime = channelRuntime?.inbound;
|
|
15
|
+
if (inboundRuntime?.buildContext && inboundRuntime?.run) {
|
|
16
|
+
return inboundRuntime;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const legacyTurnRuntime = channelRuntime?.turn;
|
|
20
|
+
if (legacyTurnRuntime?.buildContext && legacyTurnRuntime?.run) {
|
|
21
|
+
if (!warnedLegacyTurnRuntime) {
|
|
22
|
+
warnedLegacyTurnRuntime = true;
|
|
23
|
+
emitBncrLogLine(
|
|
24
|
+
'warn',
|
|
25
|
+
'[bncr] using legacy runtime.channel.turn compatibility path; upgrade path prefers runtime.channel.inbound',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
buildContext: legacyTurnRuntime.buildContext,
|
|
30
|
+
run: legacyTurnRuntime.run,
|
|
31
|
+
runPreparedReply: legacyTurnRuntime.runPrepared,
|
|
32
|
+
dispatchReply: legacyTurnRuntime.dispatchAssembled ?? legacyTurnRuntime.runAssembled,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new Error(
|
|
37
|
+
'OpenClaw channel inbound runtime is unavailable: expected runtime.channel.inbound.* or legacy runtime.channel.turn.*',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
recordSessionMetaFromInbound,
|
|
3
|
-
updateSessionStoreEntry,
|
|
4
|
-
} from 'openclaw/plugin-sdk/session-store-runtime';
|
|
5
1
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
2
|
+
import {
|
|
3
|
+
recordBncrSessionMetaFromInbound,
|
|
4
|
+
updateBncrSessionStoreEntry,
|
|
5
|
+
} from '../../openclaw/inbound-session-runtime.ts';
|
|
6
6
|
|
|
7
7
|
type RecordInboundSessionFn = (args: any) => Promise<unknown> | unknown;
|
|
8
8
|
|
|
@@ -57,7 +57,7 @@ export async function correctBncrInboundSessionLabel(args: {
|
|
|
57
57
|
if (!storePath || !sessionKey || !expectedLabel) return;
|
|
58
58
|
|
|
59
59
|
try {
|
|
60
|
-
await
|
|
60
|
+
await updateBncrSessionStoreEntry({
|
|
61
61
|
storePath,
|
|
62
62
|
sessionKey,
|
|
63
63
|
update: (entry: any) => {
|
|
@@ -82,14 +82,14 @@ export async function recordAndPatchBncrInboundSessionEntry(args: {
|
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
84
|
if (args.ctx) {
|
|
85
|
-
await
|
|
85
|
+
await recordBncrSessionMetaFromInbound({
|
|
86
86
|
storePath,
|
|
87
87
|
sessionKey,
|
|
88
88
|
ctx: args.ctx as any,
|
|
89
89
|
createIfMissing: true,
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
await
|
|
92
|
+
await updateBncrSessionStoreEntry({
|
|
93
93
|
storePath,
|
|
94
94
|
sessionKey,
|
|
95
95
|
update: () => args.patch,
|