@xmoxmo/bncr 0.4.9 → 0.5.0
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/package.json +1 -1
- package/src/channel.ts +1 -0
- package/src/messaging/inbound/context-facts.ts +119 -5
- package/src/messaging/inbound/dispatch.ts +2 -0
- package/src/messaging/inbound/group-history.ts +102 -18
- package/src/messaging/inbound/turn-context.ts +32 -24
- package/src/plugin/channel-runtime-builders-delivery.ts +1 -0
- package/src/plugin/inbound-acceptance.ts +29 -0
- package/src/plugin/inbound-handlers.ts +4 -2
- package/src/plugin/meta.ts +1 -0
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
1
3
|
export type BncrStructuredContextFactsInput = {
|
|
2
4
|
channelId: string;
|
|
3
5
|
accountId: string;
|
|
@@ -54,8 +56,54 @@ export type BncrStructuredContextFactsInput = {
|
|
|
54
56
|
kind?: string;
|
|
55
57
|
messageId?: string;
|
|
56
58
|
}>;
|
|
59
|
+
pendingMediaContext?: Array<{
|
|
60
|
+
messageId?: string;
|
|
61
|
+
sender?: string;
|
|
62
|
+
senderId?: string;
|
|
63
|
+
body?: string;
|
|
64
|
+
media?: Array<{
|
|
65
|
+
path?: string;
|
|
66
|
+
contentType?: string;
|
|
67
|
+
kind?: string;
|
|
68
|
+
messageId?: string;
|
|
69
|
+
}>;
|
|
70
|
+
}>;
|
|
57
71
|
};
|
|
58
72
|
|
|
73
|
+
function toBncrPromptMediaPath(id: string): string | undefined {
|
|
74
|
+
if (
|
|
75
|
+
!id ||
|
|
76
|
+
id === '.' ||
|
|
77
|
+
id === '..' ||
|
|
78
|
+
id.includes('/') ||
|
|
79
|
+
id.includes('\\') ||
|
|
80
|
+
id.includes('\0')
|
|
81
|
+
) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return `media://inbound/${encodeURIComponent(id)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveBncrPromptMediaPath(mediaPath: string | undefined): string | undefined {
|
|
88
|
+
const normalized = String(mediaPath || '').trim();
|
|
89
|
+
if (!normalized) return undefined;
|
|
90
|
+
|
|
91
|
+
const canonicalMatch = /^media:\/\/inbound\/([^/\\]+)$/i.exec(normalized);
|
|
92
|
+
if (canonicalMatch?.[1]) {
|
|
93
|
+
try {
|
|
94
|
+
return toBncrPromptMediaPath(decodeURIComponent(canonicalMatch[1]));
|
|
95
|
+
} catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const slashNormalized = normalized.replace(/\\/g, '/');
|
|
101
|
+
if (!slashNormalized.includes('/media/inbound/')) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return toBncrPromptMediaPath(path.posix.basename(slashNormalized));
|
|
105
|
+
}
|
|
106
|
+
|
|
59
107
|
export function buildBncrStructuredContextFacts(input: BncrStructuredContextFactsInput) {
|
|
60
108
|
const rawBody = input.message.rawBody;
|
|
61
109
|
return {
|
|
@@ -116,6 +164,18 @@ export function buildBncrStructuredContextFacts(input: BncrStructuredContextFact
|
|
|
116
164
|
kind: item.kind,
|
|
117
165
|
messageId: item.messageId,
|
|
118
166
|
})),
|
|
167
|
+
pendingMediaContext: (input.pendingMediaContext || []).map((entry) => ({
|
|
168
|
+
messageId: entry.messageId,
|
|
169
|
+
sender: entry.sender,
|
|
170
|
+
senderId: entry.senderId,
|
|
171
|
+
body: entry.body,
|
|
172
|
+
media: (entry.media || []).map((item) => ({
|
|
173
|
+
path: item.path,
|
|
174
|
+
contentType: item.contentType,
|
|
175
|
+
kind: item.kind,
|
|
176
|
+
messageId: item.messageId,
|
|
177
|
+
})),
|
|
178
|
+
})),
|
|
119
179
|
};
|
|
120
180
|
}
|
|
121
181
|
|
|
@@ -141,10 +201,23 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
141
201
|
originatingTo: string;
|
|
142
202
|
};
|
|
143
203
|
media?: Array<{
|
|
204
|
+
path?: string;
|
|
144
205
|
contentType?: string;
|
|
145
206
|
kind?: string;
|
|
146
207
|
messageId?: string;
|
|
147
208
|
}>;
|
|
209
|
+
pendingMediaContext?: Array<{
|
|
210
|
+
messageId?: string;
|
|
211
|
+
sender?: string;
|
|
212
|
+
senderId?: string;
|
|
213
|
+
body?: string;
|
|
214
|
+
media?: Array<{
|
|
215
|
+
path?: string;
|
|
216
|
+
contentType?: string;
|
|
217
|
+
kind?: string;
|
|
218
|
+
messageId?: string;
|
|
219
|
+
}>;
|
|
220
|
+
}>;
|
|
148
221
|
} = {};
|
|
149
222
|
|
|
150
223
|
if (
|
|
@@ -179,11 +252,39 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
179
252
|
}
|
|
180
253
|
|
|
181
254
|
if (facts.media.length > 0) {
|
|
182
|
-
result.media = facts.media.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
255
|
+
result.media = facts.media.flatMap((item) => {
|
|
256
|
+
const promptMediaPath = resolveBncrPromptMediaPath(item.path);
|
|
257
|
+
const payload = {
|
|
258
|
+
...(promptMediaPath ? { path: promptMediaPath } : {}),
|
|
259
|
+
contentType: item.contentType,
|
|
260
|
+
kind: item.kind,
|
|
261
|
+
messageId: item.messageId,
|
|
262
|
+
};
|
|
263
|
+
return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (facts.pendingMediaContext?.length) {
|
|
268
|
+
result.pendingMediaContext = facts.pendingMediaContext.flatMap((entry) => {
|
|
269
|
+
const media = (entry.media || []).flatMap((item) => {
|
|
270
|
+
const promptMediaPath = resolveBncrPromptMediaPath(item.path);
|
|
271
|
+
const payload = {
|
|
272
|
+
...(promptMediaPath ? { path: promptMediaPath } : {}),
|
|
273
|
+
contentType: item.contentType,
|
|
274
|
+
kind: item.kind,
|
|
275
|
+
messageId: item.messageId,
|
|
276
|
+
};
|
|
277
|
+
return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
|
|
278
|
+
});
|
|
279
|
+
const payload = {
|
|
280
|
+
messageId: entry.messageId,
|
|
281
|
+
sender: entry.sender,
|
|
282
|
+
senderId: entry.senderId,
|
|
283
|
+
body: entry.body,
|
|
284
|
+
...(media.length > 0 ? { media } : {}),
|
|
285
|
+
};
|
|
286
|
+
return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
|
|
287
|
+
});
|
|
187
288
|
}
|
|
188
289
|
|
|
189
290
|
return result;
|
|
@@ -248,6 +349,18 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
|
248
349
|
bridgeSenderName?: string;
|
|
249
350
|
senderIsOwner?: boolean;
|
|
250
351
|
senderIsAuthorized?: boolean;
|
|
352
|
+
pendingMediaContext?: Array<{
|
|
353
|
+
messageId?: string;
|
|
354
|
+
sender?: string;
|
|
355
|
+
senderId?: string;
|
|
356
|
+
body?: string;
|
|
357
|
+
media?: Array<{
|
|
358
|
+
path?: string;
|
|
359
|
+
contentType?: string;
|
|
360
|
+
kind?: string;
|
|
361
|
+
messageId?: string;
|
|
362
|
+
}>;
|
|
363
|
+
}>;
|
|
251
364
|
};
|
|
252
365
|
|
|
253
366
|
export function buildBncrStructuredContextFactsFromInboundParts(
|
|
@@ -310,5 +423,6 @@ export function buildBncrStructuredContextFactsFromInboundParts(
|
|
|
310
423
|
kind: item.kind || inferBncrStructuredMediaKind(item.contentType),
|
|
311
424
|
messageId: input.parsed.msgId,
|
|
312
425
|
})),
|
|
426
|
+
pendingMediaContext: input.pendingMediaContext,
|
|
313
427
|
});
|
|
314
428
|
}
|
|
@@ -153,12 +153,14 @@ export async function dispatchBncrInbound(params: {
|
|
|
153
153
|
historyMap: groupHistories,
|
|
154
154
|
parsed,
|
|
155
155
|
senderDisplayName,
|
|
156
|
+
senderId: senderIdForContext,
|
|
156
157
|
bodyText: rawBody,
|
|
157
158
|
});
|
|
158
159
|
await recordBncrPendingGroupMedia({
|
|
159
160
|
historyMap: groupHistories,
|
|
160
161
|
parsed,
|
|
161
162
|
senderDisplayName,
|
|
163
|
+
senderId: senderIdForContext,
|
|
162
164
|
bodyText: rawBody,
|
|
163
165
|
mediaItems,
|
|
164
166
|
mediaContentType: mediaContentType || mimeType,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import {
|
|
2
3
|
createChannelHistoryWindow,
|
|
3
4
|
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
@@ -8,7 +9,11 @@ import { formatOpenClawAgentEnvelope } from '../../openclaw/reply-runtime.ts';
|
|
|
8
9
|
import type { BncrInboundApi } from './contracts.ts';
|
|
9
10
|
import type { ParsedInbound } from './dispatch-prep.ts';
|
|
10
11
|
|
|
11
|
-
export type
|
|
12
|
+
export type BncrHistoryEntry = HistoryEntry & {
|
|
13
|
+
senderId?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type BncrGroupHistoryMap = Map<string, BncrHistoryEntry[]>;
|
|
12
17
|
|
|
13
18
|
type BncrHistoryMediaKind = NonNullable<HistoryMediaEntry['kind']>;
|
|
14
19
|
|
|
@@ -30,6 +35,7 @@ export function recordBncrPendingGroupText(args: {
|
|
|
30
35
|
historyMap: BncrGroupHistoryMap;
|
|
31
36
|
parsed: ParsedInbound;
|
|
32
37
|
senderDisplayName: string;
|
|
38
|
+
senderId: string;
|
|
33
39
|
bodyText: string;
|
|
34
40
|
}) {
|
|
35
41
|
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
@@ -40,6 +46,7 @@ export function recordBncrPendingGroupText(args: {
|
|
|
40
46
|
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
41
47
|
entry: {
|
|
42
48
|
sender: args.senderDisplayName,
|
|
49
|
+
senderId: args.senderId,
|
|
43
50
|
body,
|
|
44
51
|
timestamp: Date.now(),
|
|
45
52
|
messageId: args.parsed.msgId,
|
|
@@ -74,6 +81,7 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
74
81
|
historyMap: BncrGroupHistoryMap;
|
|
75
82
|
parsed: ParsedInbound;
|
|
76
83
|
senderDisplayName: string;
|
|
84
|
+
senderId: string;
|
|
77
85
|
bodyText: string;
|
|
78
86
|
mediaItems?: Array<{
|
|
79
87
|
path: string;
|
|
@@ -101,12 +109,13 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
101
109
|
mediaContentType: args.mediaContentType,
|
|
102
110
|
});
|
|
103
111
|
const body = normalizeTextBody(args.bodyText) || buildBncrHistoryMediaBody(kind);
|
|
104
|
-
if (normalizedMediaItems.length === 0
|
|
112
|
+
if (normalizedMediaItems.length === 0) {
|
|
105
113
|
createChannelHistoryWindow({ historyMap: args.historyMap }).record({
|
|
106
114
|
historyKey,
|
|
107
115
|
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
108
116
|
entry: {
|
|
109
117
|
sender: args.senderDisplayName,
|
|
118
|
+
senderId: args.senderId,
|
|
110
119
|
body,
|
|
111
120
|
timestamp: Date.now(),
|
|
112
121
|
messageId: args.parsed.msgId,
|
|
@@ -114,25 +123,25 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
114
123
|
});
|
|
115
124
|
return;
|
|
116
125
|
}
|
|
117
|
-
|
|
126
|
+
createChannelHistoryWindow({ historyMap: args.historyMap }).record({
|
|
118
127
|
historyKey,
|
|
119
128
|
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
120
129
|
entry: {
|
|
121
130
|
sender: args.senderDisplayName,
|
|
131
|
+
senderId: args.senderId,
|
|
122
132
|
body,
|
|
123
133
|
timestamp: Date.now(),
|
|
124
134
|
messageId: args.parsed.msgId,
|
|
135
|
+
media: normalizedMediaItems.map(
|
|
136
|
+
(item) =>
|
|
137
|
+
({
|
|
138
|
+
path: item.path,
|
|
139
|
+
contentType: item.contentType || args.mediaContentType,
|
|
140
|
+
kind: item.kind || kind,
|
|
141
|
+
messageId: args.parsed.msgId,
|
|
142
|
+
}) satisfies HistoryMediaEntry,
|
|
143
|
+
),
|
|
125
144
|
},
|
|
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
145
|
});
|
|
137
146
|
}
|
|
138
147
|
|
|
@@ -164,16 +173,91 @@ export function buildBncrPendingGroupContext(args: {
|
|
|
164
173
|
});
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
export function
|
|
176
|
+
export function collectBncrPendingHistoryMedia(args: {
|
|
168
177
|
historyMap: BncrGroupHistoryMap;
|
|
169
178
|
parsed: ParsedInbound;
|
|
179
|
+
}): HistoryMediaEntry[] {
|
|
180
|
+
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
181
|
+
if (!historyKey) return [];
|
|
182
|
+
const entries = args.historyMap.get(historyKey) || [];
|
|
183
|
+
return entries.flatMap((entry) => (Array.isArray(entry.media) ? entry.media : []));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function toBncrPromptMediaPath(value: string | undefined): string | undefined {
|
|
187
|
+
const normalized = String(value || '').trim();
|
|
188
|
+
if (!normalized) return undefined;
|
|
189
|
+
const slashNormalized = normalized.replace(/\\/g, '/');
|
|
190
|
+
if (!slashNormalized.includes('/media/inbound/')) return undefined;
|
|
191
|
+
const base = path.posix.basename(slashNormalized);
|
|
192
|
+
if (!base || base === '.' || base === '..') return undefined;
|
|
193
|
+
return `media://inbound/${encodeURIComponent(base)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildHistoryWindowMediaSummary(
|
|
197
|
+
media: HistoryMediaEntry[] | undefined,
|
|
198
|
+
): string | undefined {
|
|
199
|
+
if (!Array.isArray(media) || media.length === 0) return undefined;
|
|
200
|
+
const kinds = media
|
|
201
|
+
.map((item) =>
|
|
202
|
+
String(item?.kind || '')
|
|
203
|
+
.trim()
|
|
204
|
+
.toLowerCase(),
|
|
205
|
+
)
|
|
206
|
+
.filter(Boolean);
|
|
207
|
+
const uniformKind = kinds[0] && kinds.every((item) => item === kinds[0]) ? kinds[0] : 'document';
|
|
208
|
+
const count = media.length;
|
|
209
|
+
if (uniformKind === 'image')
|
|
210
|
+
return count === 1 ? '<media:image>' : `<media:image> (${count} images)`;
|
|
211
|
+
if (uniformKind === 'video')
|
|
212
|
+
return count === 1 ? '<media:video>' : `<media:video> (${count} videos)`;
|
|
213
|
+
if (uniformKind === 'audio') {
|
|
214
|
+
return count === 1 ? '<media:audio>' : `<media:audio> (${count} audio attachments)`;
|
|
215
|
+
}
|
|
216
|
+
return count === 1 ? '<media:document>' : `<media:document> (${count} attachments)`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildHistoryWindowMedias(media: HistoryMediaEntry[] | undefined) {
|
|
220
|
+
if (!Array.isArray(media) || media.length === 0) return [];
|
|
221
|
+
return media.flatMap((item) => {
|
|
222
|
+
const promptPath = toBncrPromptMediaPath(item?.path);
|
|
223
|
+
const payload = {
|
|
224
|
+
...(promptPath ? { path: promptPath } : {}),
|
|
225
|
+
...(item?.contentType ? { contentType: item.contentType } : {}),
|
|
226
|
+
...(item?.kind ? { kind: item.kind } : {}),
|
|
227
|
+
...(item?.messageId ? { messageId: item.messageId } : {}),
|
|
228
|
+
};
|
|
229
|
+
return Object.keys(payload).length > 0 ? [payload] : [];
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function buildBncrPendingHistoryWindowContext(args: {
|
|
234
|
+
historyMap: BncrGroupHistoryMap;
|
|
235
|
+
parsed: ParsedInbound;
|
|
236
|
+
channelId: string;
|
|
170
237
|
}) {
|
|
171
238
|
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
172
239
|
if (!historyKey) return undefined;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
240
|
+
const entries = args.historyMap.get(historyKey) || [];
|
|
241
|
+
if (entries.length === 0) return undefined;
|
|
242
|
+
const messages = entries.slice(-DEFAULT_GROUP_HISTORY_LIMIT).map((entry) => ({
|
|
243
|
+
...(entry.messageId ? { messageId: entry.messageId } : {}),
|
|
244
|
+
sender: entry.sender,
|
|
245
|
+
senderId: entry.senderId,
|
|
246
|
+
...(typeof entry.timestamp === 'number' ? { timestampMs: entry.timestamp } : {}),
|
|
247
|
+
body: entry.body,
|
|
248
|
+
mediaSummary: buildHistoryWindowMediaSummary(entry.media),
|
|
249
|
+
medias: buildHistoryWindowMedias(entry.media),
|
|
250
|
+
}));
|
|
251
|
+
return {
|
|
252
|
+
label: 'Bncr history window',
|
|
253
|
+
source: args.channelId,
|
|
254
|
+
type: 'bncr.history_window',
|
|
255
|
+
payload: {
|
|
256
|
+
relation: 'before_current_message',
|
|
257
|
+
order: 'chronological',
|
|
258
|
+
messages,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
177
261
|
}
|
|
178
262
|
|
|
179
263
|
export function clearBncrPendingGroupHistory(args: {
|
|
@@ -12,8 +12,9 @@ import type {
|
|
|
12
12
|
} from './dispatch-prep.ts';
|
|
13
13
|
import {
|
|
14
14
|
type BncrGroupHistoryMap,
|
|
15
|
-
buildBncrInboundHistory,
|
|
16
15
|
buildBncrPendingGroupContext,
|
|
16
|
+
buildBncrPendingHistoryWindowContext,
|
|
17
|
+
collectBncrPendingHistoryMedia,
|
|
17
18
|
} from './group-history.ts';
|
|
18
19
|
import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
|
|
19
20
|
|
|
@@ -88,6 +89,15 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
88
89
|
} = args;
|
|
89
90
|
const senderIsOwner = parsed.isAdmin === true || Boolean(ownerAllowFrom?.length);
|
|
90
91
|
const senderIsAuthorized = senderIsOwner;
|
|
92
|
+
const inboundHistory = undefined;
|
|
93
|
+
const pendingHistoryMedia =
|
|
94
|
+
shouldDispatch && parsed.peer.kind === 'group'
|
|
95
|
+
? collectBncrPendingHistoryMedia({ historyMap: groupHistories, parsed })
|
|
96
|
+
: [];
|
|
97
|
+
const pendingHistoryWindowContext =
|
|
98
|
+
shouldDispatch && parsed.peer.kind === 'group'
|
|
99
|
+
? buildBncrPendingHistoryWindowContext({ historyMap: groupHistories, parsed, channelId })
|
|
100
|
+
: undefined;
|
|
91
101
|
const structuredContextFacts = buildBncrStructuredContextFactsFromInboundParts({
|
|
92
102
|
channelId,
|
|
93
103
|
parsed,
|
|
@@ -105,16 +115,19 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
105
115
|
senderIsAuthorized,
|
|
106
116
|
});
|
|
107
117
|
const promptVisibleContextFacts = buildBncrPromptVisibleContextFacts(structuredContextFacts);
|
|
108
|
-
const supplementalUntrustedContext =
|
|
109
|
-
? [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
const supplementalUntrustedContext = [
|
|
119
|
+
...(pendingHistoryWindowContext ? [pendingHistoryWindowContext] : []),
|
|
120
|
+
...(Object.keys(promptVisibleContextFacts).length
|
|
121
|
+
? [
|
|
122
|
+
{
|
|
123
|
+
label: 'Bncr inbound context',
|
|
124
|
+
source: channelId,
|
|
125
|
+
type: 'bncr.inbound_context',
|
|
126
|
+
payload: promptVisibleContextFacts,
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
: []),
|
|
130
|
+
];
|
|
118
131
|
const platformWantsReply = parsed.shouldRespond === true;
|
|
119
132
|
const wasMentioned = parsed.isBotMentioned === true || platformWantsReply;
|
|
120
133
|
const isAuthorizedTextCommand = Boolean(ownerAllowFrom?.length);
|
|
@@ -134,10 +147,13 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
134
147
|
currentMessage: prepared.body,
|
|
135
148
|
})
|
|
136
149
|
: prepared.body;
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
const currentTurnMedia = prepared.mediaItems.map((item) => ({
|
|
151
|
+
path: item.path,
|
|
152
|
+
contentType: item.contentType,
|
|
153
|
+
kind: item.kind,
|
|
154
|
+
messageId: msgId ?? undefined,
|
|
155
|
+
}));
|
|
156
|
+
const media = [...pendingHistoryMedia, ...currentTurnMedia];
|
|
141
157
|
|
|
142
158
|
return Promise.resolve(
|
|
143
159
|
resolveBncrChannelInboundRuntime(api).buildContext({
|
|
@@ -194,15 +210,7 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
194
210
|
},
|
|
195
211
|
}
|
|
196
212
|
: {}),
|
|
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
|
-
: [],
|
|
213
|
+
media,
|
|
206
214
|
access: {
|
|
207
215
|
mentions: {
|
|
208
216
|
canDetectMention: true,
|
|
@@ -11,6 +11,34 @@ import { decideSceneAdmission } from './scene-registry.ts';
|
|
|
11
11
|
|
|
12
12
|
type InboundAcceptanceResponsePayload = ReturnType<typeof buildInboundResponsePayload>;
|
|
13
13
|
|
|
14
|
+
function resolveDispatchBy(args: {
|
|
15
|
+
parsed: ReturnType<typeof parseBncrInboundParams>;
|
|
16
|
+
admission: ReturnType<typeof decideSceneAdmission>;
|
|
17
|
+
cfg: BncrChannelConfigRoot;
|
|
18
|
+
}): string {
|
|
19
|
+
const { parsed, admission, cfg } = args;
|
|
20
|
+
if (parsed.peer.kind === 'direct') return 'direct';
|
|
21
|
+
if (!admission.allowed) return 'denied';
|
|
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 'native-command-admin';
|
|
32
|
+
|
|
33
|
+
const mode = admission.scene.groupReplyMode || 'admin';
|
|
34
|
+
if (mode === 'all') return 'mode-all';
|
|
35
|
+
if (mode === 'admin') return parsed.isAdmin === true ? 'mode-admin-admin' : 'mode-admin-blocked';
|
|
36
|
+
if (mode === 'mention')
|
|
37
|
+
return parsed.shouldRespond === true ? 'mode-mention-trigger' : 'mode-mention-idle';
|
|
38
|
+
if (parsed.isAdmin === true) return 'mode-hybrid-admin';
|
|
39
|
+
return parsed.shouldRespond === true ? 'mode-hybrid-trigger' : 'mode-hybrid-idle';
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
function shouldDispatchForScene(args: {
|
|
15
43
|
parsed: ReturnType<typeof parseBncrInboundParams>;
|
|
16
44
|
admission: ReturnType<typeof decideSceneAdmission>;
|
|
@@ -184,5 +212,6 @@ export async function prepareBncrInboundAcceptance(args: {
|
|
|
184
212
|
resolvedAgentId: admission.agentId,
|
|
185
213
|
shouldDispatch: shouldDispatchForScene({ parsed, admission, cfg }),
|
|
186
214
|
shouldAccumulate: shouldAccumulateForScene({ parsed, admission, cfg }),
|
|
215
|
+
dispatchBy: resolveDispatchBy({ parsed, admission, cfg }),
|
|
187
216
|
};
|
|
188
217
|
}
|
|
@@ -45,6 +45,7 @@ type InboundAcceptanceResult =
|
|
|
45
45
|
resolvedAgentId: string;
|
|
46
46
|
shouldDispatch: boolean;
|
|
47
47
|
shouldAccumulate: boolean;
|
|
48
|
+
dispatchBy: string;
|
|
48
49
|
}
|
|
49
50
|
| {
|
|
50
51
|
ok: false;
|
|
@@ -254,7 +255,7 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
|
|
|
254
255
|
.trim();
|
|
255
256
|
runtime.logInfo(
|
|
256
257
|
'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}|
|
|
258
|
+
`accept reject|accountId=${accountId}|msgId=${msgId ?? '-'}|scope=${runtime.formatDisplayScope(route)}|chatType=${peer.kind}|msgType=${msgType}|status=${acceptance.status}|reason=${reason || '-'}|isAdmin=${parsed.isAdmin === true}|platformShouldRespond=${parsed.shouldRespond === true}`,
|
|
258
259
|
);
|
|
259
260
|
const pendingNotice = buildBncrPendingDirectApprovalNotice({ parsed, reason });
|
|
260
261
|
if (pendingNotice) {
|
|
@@ -280,10 +281,11 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
|
|
|
280
281
|
resolvedAgentId,
|
|
281
282
|
shouldDispatch,
|
|
282
283
|
shouldAccumulate,
|
|
284
|
+
dispatchBy,
|
|
283
285
|
} = acceptance;
|
|
284
286
|
runtime.logInfo(
|
|
285
287
|
'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}|
|
|
288
|
+
`accept ok|accountId=${accountId}|msgId=${msgId ?? '-'}|scope=${runtime.formatDisplayScope(route)}|chatType=${peer.kind}|msgType=${msgType}|sessionKey=${sessionKey}|agent=${resolvedAgentId}|dispatch=${shouldDispatch}|dispatchBy=${dispatchBy}|accumulate=${shouldAccumulate}|isAdmin=${parsed.isAdmin === true}|platformShouldRespond=${parsed.shouldRespond === true}`,
|
|
287
289
|
);
|
|
288
290
|
runtime.logInfo(
|
|
289
291
|
'inbound',
|