@xmoxmo/bncr 0.5.4 → 0.5.6
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 +48 -7
- package/src/core/outbox-entry-builders.ts +7 -0
- package/src/messaging/inbound/context-facts.ts +5 -4
- package/src/messaging/inbound/group-history.ts +4 -1
- package/src/messaging/inbound/native-command.ts +7 -3
- package/src/messaging/inbound/scene-admin.ts +181 -5
- package/src/messaging/inbound/turn-context.ts +16 -0
- package/src/messaging/outbound/marker-parser.ts +154 -0
- package/src/messaging/outbound/media.ts +5 -14
- package/src/messaging/outbound/reply-enqueue-media.ts +4 -0
- package/src/messaging/outbound/reply-enqueue.ts +25 -3
- package/src/messaging/outbound/send-params.ts +43 -10
- package/src/messaging/outbound/send.ts +6 -0
- package/src/plugin/bridge-file-transfer-push-facade.ts +3 -1
- package/src/plugin/bridge-media-facade.ts +2 -1
- package/src/plugin/channel-plugin-surface-group.ts +30 -0
- package/src/plugin/channel-runtime-builders-delivery.ts +2 -0
- package/src/plugin/channel-runtime-types.ts +8 -0
- package/src/plugin/channel-send.ts +197 -94
- package/src/plugin/file-transfer-orchestrator.ts +105 -3
- package/src/plugin/file-transfer-setup.ts +1 -1
- package/src/plugin/media-orchestrators-runtime-group.ts +2 -0
- package/src/plugin/outbound.ts +4 -0
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -288,6 +288,9 @@ class BncrBridgeRuntime {
|
|
|
288
288
|
{ accountId: string; route: BncrRoute; updatedAt: number }
|
|
289
289
|
>();
|
|
290
290
|
private sceneRegistry = new Map<string, BncrSceneRecord>();
|
|
291
|
+
getSceneRegistry(): Map<string, BncrSceneRecord> {
|
|
292
|
+
return this.sceneRegistry;
|
|
293
|
+
}
|
|
291
294
|
private groupHistories: BncrGroupHistoryMap = new Map();
|
|
292
295
|
private routeAliases = new Map<
|
|
293
296
|
string,
|
|
@@ -1186,6 +1189,7 @@ class BncrBridgeRuntime {
|
|
|
1186
1189
|
kind?: 'tool' | 'block' | 'final';
|
|
1187
1190
|
replyToId?: string;
|
|
1188
1191
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
1192
|
+
downloadMedia?: boolean;
|
|
1189
1193
|
}): OutboxEntry {
|
|
1190
1194
|
return buildFileTransferOutboxEntryFromRuntime({
|
|
1191
1195
|
createMessageId: () => randomUUID(),
|
|
@@ -1205,6 +1209,7 @@ class BncrBridgeRuntime {
|
|
|
1205
1209
|
kind: params.kind,
|
|
1206
1210
|
replyToId: asString(params.replyToId || '').trim() || undefined,
|
|
1207
1211
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
1212
|
+
downloadMedia: params.downloadMedia === true ? true : undefined,
|
|
1208
1213
|
});
|
|
1209
1214
|
}
|
|
1210
1215
|
|
|
@@ -1222,6 +1227,8 @@ class BncrBridgeRuntime {
|
|
|
1222
1227
|
this.mediaDedupeRuntime.rememberRecentMediaSend(params);
|
|
1223
1228
|
}
|
|
1224
1229
|
|
|
1230
|
+
private HTTP_URL_RE = /^https?:\/\//i;
|
|
1231
|
+
|
|
1225
1232
|
private tryBuildMediaDedupeFallback(params: {
|
|
1226
1233
|
sessionKey: string;
|
|
1227
1234
|
mediaUrl: string;
|
|
@@ -1229,6 +1236,8 @@ class BncrBridgeRuntime {
|
|
|
1229
1236
|
replyToId: string;
|
|
1230
1237
|
currentTime?: number;
|
|
1231
1238
|
}): { text: string; reason: 'same-text-sent-checkmark' | 'text-changed-downgrade' } | null {
|
|
1239
|
+
// Only deduplicate local file paths - HTTP URLs bypass dedup
|
|
1240
|
+
if (this.HTTP_URL_RE.test(params.mediaUrl)) return null;
|
|
1232
1241
|
return this.mediaDedupeRuntime.tryBuildMediaDedupeFallback(params);
|
|
1233
1242
|
}
|
|
1234
1243
|
|
|
@@ -1246,6 +1255,17 @@ class BncrBridgeRuntime {
|
|
|
1246
1255
|
? params.meta.messageKind
|
|
1247
1256
|
: undefined;
|
|
1248
1257
|
|
|
1258
|
+
const hasPayload = !!(params.media.base64 || params.media.path);
|
|
1259
|
+
if (hasPayload) {
|
|
1260
|
+
console.log(
|
|
1261
|
+
'[bncr] outbound-media download=true|media=' +
|
|
1262
|
+
(params.media.path || `base64:${(params.media.base64 || '').substring(0, 12)}...`) +
|
|
1263
|
+
'|url=' +
|
|
1264
|
+
params.mediaUrl.split('?')[0],
|
|
1265
|
+
);
|
|
1266
|
+
} else {
|
|
1267
|
+
console.log(`[bncr] outbound-media pass-through|url=${params.mediaUrl.split('?')[0]}`);
|
|
1268
|
+
}
|
|
1249
1269
|
return buildBncrMediaOutboundFrame({
|
|
1250
1270
|
messageId: params.entry.messageId,
|
|
1251
1271
|
sessionKey: params.entry.sessionKey,
|
|
@@ -1254,7 +1274,7 @@ class BncrBridgeRuntime {
|
|
|
1254
1274
|
mode: params.media.path ? 'chunk' : 'base64',
|
|
1255
1275
|
mimeType: params.media.mimeType,
|
|
1256
1276
|
fileName: params.media.fileName,
|
|
1257
|
-
|
|
1277
|
+
base64: params.media.base64,
|
|
1258
1278
|
path: params.media.path,
|
|
1259
1279
|
},
|
|
1260
1280
|
mediaUrl: params.mediaUrl,
|
|
@@ -1281,9 +1301,11 @@ class BncrBridgeRuntime {
|
|
|
1281
1301
|
sessionKey: string;
|
|
1282
1302
|
route: BncrRoute;
|
|
1283
1303
|
text: string;
|
|
1304
|
+
extra?: Record<string, unknown>;
|
|
1284
1305
|
kind?: 'tool' | 'block' | 'final';
|
|
1285
1306
|
replyToId?: string;
|
|
1286
1307
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
1308
|
+
downloadMedia?: boolean;
|
|
1287
1309
|
}): OutboxEntry {
|
|
1288
1310
|
return buildTextOutboxEntryFromRuntime({
|
|
1289
1311
|
createMessageId: () => randomUUID(),
|
|
@@ -1294,9 +1316,11 @@ class BncrBridgeRuntime {
|
|
|
1294
1316
|
sessionKey: params.sessionKey,
|
|
1295
1317
|
route: params.route,
|
|
1296
1318
|
text: params.text,
|
|
1319
|
+
extra: params.extra && Object.keys(params.extra).length > 0 ? { ...params.extra } : undefined,
|
|
1297
1320
|
kind: params.kind,
|
|
1298
1321
|
replyToId: params.replyToId,
|
|
1299
1322
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
1323
|
+
downloadMedia: params.downloadMedia === true ? true : undefined,
|
|
1300
1324
|
});
|
|
1301
1325
|
}
|
|
1302
1326
|
|
|
@@ -2722,7 +2746,7 @@ class BncrBridgeRuntime {
|
|
|
2722
2746
|
mode: 'base64' | 'chunk';
|
|
2723
2747
|
mimeType?: string;
|
|
2724
2748
|
fileName?: string;
|
|
2725
|
-
|
|
2749
|
+
base64?: string;
|
|
2726
2750
|
path?: string;
|
|
2727
2751
|
}> {
|
|
2728
2752
|
return this.bridgeMediaFacade.transferMediaToBncrClient(params);
|
|
@@ -2985,6 +3009,22 @@ class BncrBridgeRuntime {
|
|
|
2985
3009
|
await stopBncrStatusWorker(this.buildStatusWorkerRuntime(), ctx);
|
|
2986
3010
|
};
|
|
2987
3011
|
|
|
3012
|
+
/**
|
|
3013
|
+
* Merge host-level outbound fields (forceDocument, gifPlayback, silent)
|
|
3014
|
+
* into extra before dispatching to channel-send runtime.
|
|
3015
|
+
*/
|
|
3016
|
+
private mergeHostFields(ctx: BncrChannelSendContext): BncrChannelSendContext {
|
|
3017
|
+
if (!ctx.forceDocument && !ctx.gifPlayback && !ctx.silent) {
|
|
3018
|
+
return ctx;
|
|
3019
|
+
}
|
|
3020
|
+
const extra = { ...(typeof ctx.extra === 'object' && ctx.extra !== null ? ctx.extra : {}) };
|
|
3021
|
+
if (ctx.forceDocument === true) extra.forceDocument = true;
|
|
3022
|
+
if (ctx.gifPlayback === true) extra.gifPlayback = true;
|
|
3023
|
+
if (ctx.silent === true) extra.silent = true;
|
|
3024
|
+
// Clear original fields so resolveUnifiedOutboundExtra doesn't re-add and override marker values
|
|
3025
|
+
return { ...ctx, extra, forceDocument: undefined, gifPlayback: undefined, silent: undefined };
|
|
3026
|
+
}
|
|
3027
|
+
|
|
2988
3028
|
// Channel send surface assembly ------------------------------------------
|
|
2989
3029
|
// This group backs the public channel send APIs and should stay adjacent to
|
|
2990
3030
|
// the final exposed channel* methods for quick top-down scanning.
|
|
@@ -2996,19 +3036,19 @@ class BncrBridgeRuntime {
|
|
|
2996
3036
|
private readonly channelSendRuntime = this.channelSendRuntimeGroup.channelSendRuntime;
|
|
2997
3037
|
|
|
2998
3038
|
channelSendText = async (ctx: BncrChannelSendContext) =>
|
|
2999
|
-
this.channelSendRuntime.channelSendText(ctx);
|
|
3039
|
+
this.channelSendRuntime.channelSendText(this.mergeHostFields(ctx));
|
|
3000
3040
|
|
|
3001
3041
|
channelSendMedia = async (ctx: BncrChannelSendContext) =>
|
|
3002
|
-
this.channelSendRuntime.channelSendMedia(ctx);
|
|
3042
|
+
this.channelSendRuntime.channelSendMedia(this.mergeHostFields(ctx));
|
|
3003
3043
|
|
|
3004
3044
|
channelMessageSendText = async (ctx: BncrChannelSendContext) =>
|
|
3005
|
-
this.channelSendRuntime.channelMessageSendText(ctx);
|
|
3045
|
+
this.channelSendRuntime.channelMessageSendText(this.mergeHostFields(ctx));
|
|
3006
3046
|
|
|
3007
3047
|
channelMessageSendMedia = async (ctx: BncrChannelSendContext) =>
|
|
3008
|
-
this.channelSendRuntime.channelMessageSendMedia(ctx);
|
|
3048
|
+
this.channelSendRuntime.channelMessageSendMedia(this.mergeHostFields(ctx));
|
|
3009
3049
|
|
|
3010
3050
|
channelMessageSendPayload = async (ctx: BncrChannelSendContext) =>
|
|
3011
|
-
this.channelSendRuntime.channelMessageSendPayload(ctx);
|
|
3051
|
+
this.channelSendRuntime.channelMessageSendPayload(this.mergeHostFields(ctx));
|
|
3012
3052
|
}
|
|
3013
3053
|
|
|
3014
3054
|
// Plugin surface export -----------------------------------------------------
|
|
@@ -3029,6 +3069,7 @@ export function createBncrChannelPlugin(getBridge: () => BncrBridgeRuntime) {
|
|
|
3029
3069
|
|
|
3030
3070
|
return createBncrChannelPluginSurfaceGroup({
|
|
3031
3071
|
channelId: CHANNEL_ID,
|
|
3072
|
+
sceneRegistry: getBridge().getSceneRegistry(),
|
|
3032
3073
|
getMessageSendBridge: bridgeGroup.getMessageSendBridge,
|
|
3033
3074
|
getOutboundBridge: bridgeGroup.getOutboundBridge,
|
|
3034
3075
|
getMessagingBridge: bridgeGroup.getMessagingBridge,
|
|
@@ -20,6 +20,7 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
20
20
|
kind?: 'tool' | 'block' | 'final';
|
|
21
21
|
replyToId?: string;
|
|
22
22
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
23
|
+
downloadMedia?: boolean;
|
|
23
24
|
}): OutboxEntry {
|
|
24
25
|
const messageId = args.createMessageId();
|
|
25
26
|
const createdAt = args.now();
|
|
@@ -39,6 +40,7 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
39
40
|
asVoice: args.asVoice === true,
|
|
40
41
|
audioAsVoice: args.audioAsVoice === true,
|
|
41
42
|
type: args.type,
|
|
43
|
+
downloadMedia: args.downloadMedia === true,
|
|
42
44
|
...(args.extra ? { extra: { ...args.extra } } : {}),
|
|
43
45
|
finalEvent: args.pushEvent,
|
|
44
46
|
replyToId:
|
|
@@ -62,12 +64,14 @@ export function buildTextOutboxEntry(args: {
|
|
|
62
64
|
normalizeAccountId: (accountId?: string | null) => string;
|
|
63
65
|
normalizeReplyToId: (value?: string | null) => string;
|
|
64
66
|
accountId: string;
|
|
67
|
+
extra?: Record<string, unknown>;
|
|
65
68
|
sessionKey: string;
|
|
66
69
|
route: BncrRoute;
|
|
67
70
|
text: string;
|
|
68
71
|
kind?: 'tool' | 'block' | 'final';
|
|
69
72
|
replyToId?: string;
|
|
70
73
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
74
|
+
downloadMedia?: boolean;
|
|
71
75
|
}): OutboxEntry {
|
|
72
76
|
const messageId = args.createMessageId();
|
|
73
77
|
const createdAt = args.now();
|
|
@@ -92,6 +96,9 @@ export function buildTextOutboxEntry(args: {
|
|
|
92
96
|
path: '',
|
|
93
97
|
base64: '',
|
|
94
98
|
fileName: '',
|
|
99
|
+
// Extra fields from marker/pipeline spread for adapter-specific data
|
|
100
|
+
// (e.g. type="appmsg", msg="<appmsg>..." from [BncrParam:...])
|
|
101
|
+
...(args.extra ? { ...args.extra } : {}),
|
|
95
102
|
},
|
|
96
103
|
ts: createdAt,
|
|
97
104
|
};
|
|
@@ -272,7 +272,6 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
272
272
|
return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
|
-
|
|
276
275
|
if (facts.pendingMediaContext?.length) {
|
|
277
276
|
result.pendingMediaContext = facts.pendingMediaContext.flatMap((entry) => {
|
|
278
277
|
const media = (entry.media || []).flatMap((item) => {
|
|
@@ -359,7 +358,7 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
|
359
358
|
bridgeSenderName?: string;
|
|
360
359
|
senderIsOwner?: boolean;
|
|
361
360
|
senderIsAuthorized?: boolean;
|
|
362
|
-
|
|
361
|
+
pendingHistoryContext?: Array<{
|
|
363
362
|
messageId?: string;
|
|
364
363
|
sender?: string;
|
|
365
364
|
senderId?: string;
|
|
@@ -372,11 +371,13 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
|
372
371
|
}>;
|
|
373
372
|
}>;
|
|
374
373
|
};
|
|
375
|
-
|
|
376
374
|
export function buildBncrStructuredContextFactsFromInboundParts(
|
|
377
375
|
input: BncrStructuredContextFactsFromInboundPartsInput,
|
|
378
376
|
) {
|
|
379
377
|
const mediaItems = Array.isArray(input.prepared.mediaItems) ? input.prepared.mediaItems : [];
|
|
378
|
+
const pendingHistoryContext = Array.isArray(input.pendingHistoryContext)
|
|
379
|
+
? input.pendingHistoryContext
|
|
380
|
+
: [];
|
|
380
381
|
return buildBncrStructuredContextFacts({
|
|
381
382
|
channelId: input.channelId,
|
|
382
383
|
accountId: input.parsed.accountId,
|
|
@@ -434,6 +435,6 @@ export function buildBncrStructuredContextFactsFromInboundParts(
|
|
|
434
435
|
kind: item.kind || inferBncrStructuredMediaKind(item.contentType),
|
|
435
436
|
messageId: input.parsed.msgId,
|
|
436
437
|
})),
|
|
437
|
-
pendingMediaContext:
|
|
438
|
+
pendingMediaContext: pendingHistoryContext,
|
|
438
439
|
});
|
|
439
440
|
}
|
|
@@ -226,7 +226,10 @@ export function buildBncrGroupContextFromEntries(args: {
|
|
|
226
226
|
.map((entry) =>
|
|
227
227
|
formatOpenClawAgentEnvelope(args.api, {
|
|
228
228
|
channel: 'Bncr',
|
|
229
|
-
from:
|
|
229
|
+
from:
|
|
230
|
+
(entry.senderId
|
|
231
|
+
? `${entry.sender} (${entry.senderId})`
|
|
232
|
+
: entry.sender || args.channelLabel) + (entry.messageId ? ` #${entry.messageId}` : ''),
|
|
230
233
|
timestamp: entry.timestamp || args.currentTimestamp,
|
|
231
234
|
previousTimestamp: args.previousTimestamp,
|
|
232
235
|
envelope: args.envelope,
|
|
@@ -50,15 +50,18 @@ const BNCR_HELP_TEXT = [
|
|
|
50
50
|
' • /bncr deny [<SceneId>]',
|
|
51
51
|
' • /bncr bind <agentId> [<SceneId>]',
|
|
52
52
|
' • /bncr mode help',
|
|
53
|
-
' • /bncr mode <admin|mention|hybrid|all> [<SceneId>]',
|
|
53
|
+
' • /bncr mode <admin|mention|hybrid|all|clear> [<SceneId>]',
|
|
54
54
|
' • /bncr revoke [<SceneId>]',
|
|
55
55
|
' • /bncr list pending [filters...]',
|
|
56
56
|
' • /bncr list scenes [filters...]',
|
|
57
57
|
'',
|
|
58
58
|
'📋 Group history',
|
|
59
59
|
' • /bncr history-help',
|
|
60
|
-
' • /bncr history-limit [<number
|
|
61
|
-
' • /bncr history-force on|off [<SceneId>]',
|
|
60
|
+
' • /bncr history-limit [<number>|clear] [<SceneId>]',
|
|
61
|
+
' • /bncr history-force on|off|clear [<SceneId>]',
|
|
62
|
+
'',
|
|
63
|
+
'🌐 Remote media',
|
|
64
|
+
' • /bncr download-media on|off|clear|default on|off [<SceneId>]',
|
|
62
65
|
].join('\n');
|
|
63
66
|
|
|
64
67
|
const BNCR_NATIVE_COMMANDS = new Set([
|
|
@@ -77,6 +80,7 @@ const BNCR_NATIVE_COMMANDS = new Set([
|
|
|
77
80
|
'history-limit',
|
|
78
81
|
'history-help',
|
|
79
82
|
'history-force',
|
|
83
|
+
'download-media',
|
|
80
84
|
]);
|
|
81
85
|
|
|
82
86
|
export const BNCR_DIRECT_ALLOWED_BARE_COMMANDS = new Set([
|
|
@@ -13,16 +13,22 @@ export type BncrSceneAdminCommand =
|
|
|
13
13
|
| { kind: 'bind'; sceneKey?: string; agentId: string }
|
|
14
14
|
| { kind: 'mode-help' }
|
|
15
15
|
| { kind: 'mode-get'; sceneKey?: string }
|
|
16
|
-
| { kind: 'mode'; sceneKey: string; mode:
|
|
16
|
+
| { kind: 'mode'; sceneKey: string; mode: string }
|
|
17
17
|
| { kind: 'list'; scope: 'pending' | 'scenes'; filters?: string[] }
|
|
18
18
|
| { kind: 'history-limit-get'; sceneKey?: string }
|
|
19
|
-
| { kind: 'history-limit-set'; sceneKey: string; limit: number }
|
|
19
|
+
| { kind: 'history-limit-set'; sceneKey: string; limit: number | 'clear' }
|
|
20
20
|
| { kind: 'history-force-get'; sceneKey?: string }
|
|
21
|
-
| { kind: 'history-force-set'; sceneKey: string; enabled: boolean }
|
|
22
|
-
| { kind: 'history-help' }
|
|
21
|
+
| { kind: 'history-force-set'; sceneKey: string; enabled: boolean | 'clear' }
|
|
22
|
+
| { kind: 'history-help' }
|
|
23
|
+
| { kind: 'download-media-get'; sceneKey?: string }
|
|
24
|
+
| { kind: 'download-media-set'; sceneKey: string; enabled: boolean }
|
|
25
|
+
| { kind: 'download-media-global-get' }
|
|
26
|
+
| { kind: 'download-media-global-set'; enabled: boolean };
|
|
23
27
|
|
|
24
28
|
const GROUP_REPLY_MODES = new Set<BncrGroupReplyMode>(['admin', 'mention', 'hybrid', 'all']);
|
|
25
29
|
|
|
30
|
+
const GLOBAL_SCENE_KEY = '__global__';
|
|
31
|
+
|
|
26
32
|
const MODE_HELP_TEXT = [
|
|
27
33
|
'💬 Bncr Group Reply Mode Configuration',
|
|
28
34
|
'',
|
|
@@ -122,6 +128,13 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
122
128
|
return { matched: true, valid: true, command: { kind: 'mode-get' } };
|
|
123
129
|
}
|
|
124
130
|
if (args.length === 1) {
|
|
131
|
+
if (args[0] === 'clear') {
|
|
132
|
+
return {
|
|
133
|
+
matched: true,
|
|
134
|
+
valid: true,
|
|
135
|
+
command: { kind: 'mode', sceneKey: '', mode: 'clear' as any },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
125
138
|
if (args[0] === 'help') {
|
|
126
139
|
return {
|
|
127
140
|
matched: true,
|
|
@@ -142,6 +155,13 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
142
155
|
command: { kind: 'mode-get', sceneKey: args[0] },
|
|
143
156
|
};
|
|
144
157
|
}
|
|
158
|
+
if (args[0] === 'clear' && args[1]) {
|
|
159
|
+
return {
|
|
160
|
+
matched: true,
|
|
161
|
+
valid: true,
|
|
162
|
+
command: { kind: 'mode', sceneKey: args[1], mode: 'clear' as any },
|
|
163
|
+
};
|
|
164
|
+
}
|
|
145
165
|
if (GROUP_REPLY_MODES.has(args[0] as BncrGroupReplyMode) && args[1]) {
|
|
146
166
|
return {
|
|
147
167
|
matched: true,
|
|
@@ -181,6 +201,13 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
181
201
|
return { matched: true, valid: true, command: { kind: 'history-limit-get' } };
|
|
182
202
|
}
|
|
183
203
|
if (args.length === 1) {
|
|
204
|
+
if (args[0] === 'clear') {
|
|
205
|
+
return {
|
|
206
|
+
matched: true,
|
|
207
|
+
valid: true,
|
|
208
|
+
command: { kind: 'history-limit-set', sceneKey: '', limit: 'clear' as any },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
184
211
|
const num = parseInt(args[0], 10);
|
|
185
212
|
if (Number.isFinite(num)) {
|
|
186
213
|
return {
|
|
@@ -196,6 +223,13 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
196
223
|
};
|
|
197
224
|
}
|
|
198
225
|
if (args.length === 2) {
|
|
226
|
+
if (args[0] === 'clear') {
|
|
227
|
+
return {
|
|
228
|
+
matched: true,
|
|
229
|
+
valid: true,
|
|
230
|
+
command: { kind: 'history-limit-set', sceneKey: args[1], limit: 'clear' as any },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
199
233
|
const num = parseInt(args[0], 10);
|
|
200
234
|
if (Number.isFinite(num)) {
|
|
201
235
|
return {
|
|
@@ -214,6 +248,20 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
214
248
|
if (args.length === 0) {
|
|
215
249
|
return { matched: true, valid: true, command: { kind: 'history-force-get' } };
|
|
216
250
|
}
|
|
251
|
+
if (args[0] === 'clear') {
|
|
252
|
+
if (args.length === 1) {
|
|
253
|
+
return {
|
|
254
|
+
matched: true,
|
|
255
|
+
valid: true,
|
|
256
|
+
command: { kind: 'history-force-set', sceneKey: '', enabled: 'clear' as any },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
matched: true,
|
|
261
|
+
valid: true,
|
|
262
|
+
command: { kind: 'history-force-set', sceneKey: args[1], enabled: 'clear' as any },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
217
265
|
if (args[0] === 'on' || args[0] === 'off') {
|
|
218
266
|
if (args.length === 1) {
|
|
219
267
|
return {
|
|
@@ -233,6 +281,50 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
233
281
|
valid: true,
|
|
234
282
|
command: { kind: 'history-force-get', sceneKey: args[0] },
|
|
235
283
|
};
|
|
284
|
+
case 'download-media':
|
|
285
|
+
if (args.length === 0) {
|
|
286
|
+
return { matched: true, valid: true, command: { kind: 'download-media-get' } };
|
|
287
|
+
}
|
|
288
|
+
if (args[0] === 'default') {
|
|
289
|
+
if (args[1] === 'on' || args[1] === 'off') {
|
|
290
|
+
return {
|
|
291
|
+
matched: true,
|
|
292
|
+
valid: true,
|
|
293
|
+
command: { kind: 'download-media-global-set', enabled: args[1] === 'on' },
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return { matched: true, valid: true, command: { kind: 'download-media-global-get' } };
|
|
297
|
+
}
|
|
298
|
+
if (args[0] === 'clear') {
|
|
299
|
+
return {
|
|
300
|
+
matched: true,
|
|
301
|
+
valid: true,
|
|
302
|
+
command: {
|
|
303
|
+
kind: 'download-media-set',
|
|
304
|
+
sceneKey: args[1] || '',
|
|
305
|
+
enabled: undefined as any,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (args[0] === 'on' || args[0] === 'off') {
|
|
310
|
+
if (args.length === 1) {
|
|
311
|
+
return {
|
|
312
|
+
matched: true,
|
|
313
|
+
valid: true,
|
|
314
|
+
command: { kind: 'download-media-set', sceneKey: '', enabled: args[0] === 'on' },
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
matched: true,
|
|
319
|
+
valid: true,
|
|
320
|
+
command: { kind: 'download-media-set', sceneKey: args[1], enabled: args[0] === 'on' },
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
matched: true,
|
|
325
|
+
valid: true,
|
|
326
|
+
command: { kind: 'download-media-get', sceneKey: args[0] },
|
|
327
|
+
};
|
|
236
328
|
case 'history-help':
|
|
237
329
|
return { matched: true, valid: true, command: { kind: 'history-help' } };
|
|
238
330
|
default:
|
|
@@ -399,6 +491,11 @@ export function executeSceneAdminCommand(args: {
|
|
|
399
491
|
}
|
|
400
492
|
const sExisting = sceneRegistry.get(sSceneKey);
|
|
401
493
|
if (!sExisting) return { ok: false, text: `Scene not found: ${sSceneKey}` };
|
|
494
|
+
if (command.limit === 'clear') {
|
|
495
|
+
const { historyLimit: _, ...rest } = sExisting;
|
|
496
|
+
sceneRegistry.set(sSceneKey, { ...rest, lastSeenAt: now() });
|
|
497
|
+
return { ok: true, text: `Cleared ${sSceneKey} history limit. Will use default (50).` };
|
|
498
|
+
}
|
|
402
499
|
const rawLimit = command.limit;
|
|
403
500
|
if (!Number.isFinite(rawLimit) || Number.isNaN(rawLimit)) {
|
|
404
501
|
return { ok: false, text: 'Invalid number.' };
|
|
@@ -443,6 +540,11 @@ export function executeSceneAdminCommand(args: {
|
|
|
443
540
|
}
|
|
444
541
|
const sExisting = sceneRegistry.get(sSceneKey);
|
|
445
542
|
if (!sExisting) return { ok: false, text: `Scene not found: ${sSceneKey}` };
|
|
543
|
+
if (command.enabled === 'clear') {
|
|
544
|
+
const { historyForce: _, ...rest } = sExisting;
|
|
545
|
+
sceneRegistry.set(sSceneKey, { ...rest, lastSeenAt: now() });
|
|
546
|
+
return { ok: true, text: `Cleared ${sSceneKey} history auto flush. Will use default (on).` };
|
|
547
|
+
}
|
|
446
548
|
sceneRegistry.set(sSceneKey, {
|
|
447
549
|
...sExisting,
|
|
448
550
|
historyForce: command.enabled,
|
|
@@ -454,6 +556,75 @@ export function executeSceneAdminCommand(args: {
|
|
|
454
556
|
};
|
|
455
557
|
}
|
|
456
558
|
|
|
559
|
+
if (command.kind === 'download-media-global-get') {
|
|
560
|
+
const g = sceneRegistry.get(GLOBAL_SCENE_KEY);
|
|
561
|
+
const enabled = g?.downloadMedia === true;
|
|
562
|
+
return { ok: true, text: `Global default download remote media is ${enabled ? 'on' : 'off'}.` };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (command.kind === 'download-media-global-set') {
|
|
566
|
+
const g = sceneRegistry.get(GLOBAL_SCENE_KEY);
|
|
567
|
+
sceneRegistry.set(GLOBAL_SCENE_KEY, {
|
|
568
|
+
sceneKey: GLOBAL_SCENE_KEY,
|
|
569
|
+
kind: 'group',
|
|
570
|
+
status: 'allowed',
|
|
571
|
+
...(g || { platform: 'bncr' }),
|
|
572
|
+
downloadMedia: command.enabled,
|
|
573
|
+
lastSeenAt: now(),
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
ok: true,
|
|
577
|
+
text: `Global default download remote media set to ${command.enabled ? 'on' : 'off'}.`,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (command.kind === 'download-media-get') {
|
|
582
|
+
const qSceneKey = command.sceneKey || resolveCurrentGroupSceneKey(parsed);
|
|
583
|
+
if (!qSceneKey) {
|
|
584
|
+
return {
|
|
585
|
+
ok: true,
|
|
586
|
+
text: `Global default download remote media is ${sceneRegistry.get(GLOBAL_SCENE_KEY)?.downloadMedia === true ? 'on' : 'off'}.`,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const s = sceneRegistry.get(qSceneKey);
|
|
590
|
+
if (!s) return { ok: true, text: 'Default download remote media is off.' };
|
|
591
|
+
const enabled = s.downloadMedia === true;
|
|
592
|
+
return {
|
|
593
|
+
ok: true,
|
|
594
|
+
text: `Current ${qSceneKey} download remote media is ${enabled ? 'on' : 'off'}.`,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (command.kind === 'download-media-set') {
|
|
599
|
+
const sSceneKey = command.sceneKey || resolveCurrentGroupSceneKey(parsed);
|
|
600
|
+
if (!sSceneKey) {
|
|
601
|
+
return { ok: false, text: 'Current group shortcut only works inside a group chat.' };
|
|
602
|
+
}
|
|
603
|
+
const sExisting = sceneRegistry.get(sSceneKey);
|
|
604
|
+
if (command.enabled === undefined) {
|
|
605
|
+
// Clear per-group setting
|
|
606
|
+
if (sExisting) {
|
|
607
|
+
const { downloadMedia: _, ...rest } = sExisting;
|
|
608
|
+
sceneRegistry.set(sSceneKey, { ...rest, lastSeenAt: now() });
|
|
609
|
+
return {
|
|
610
|
+
ok: true,
|
|
611
|
+
text: `Cleared ${sSceneKey} download remote media. Will use global default.`,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
return { ok: true, text: `No per-group config to clear for ${sSceneKey}.` };
|
|
615
|
+
}
|
|
616
|
+
if (!sExisting) return { ok: false, text: `Scene not found: ${sSceneKey}` };
|
|
617
|
+
sceneRegistry.set(sSceneKey, {
|
|
618
|
+
...sExisting,
|
|
619
|
+
downloadMedia: command.enabled,
|
|
620
|
+
lastSeenAt: now(),
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
ok: true,
|
|
624
|
+
text: `Set ${sSceneKey} download remote media to ${command.enabled ? 'on' : 'off'}.`,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
457
628
|
const sceneKey = command.sceneKey || resolveCurrentGroupSceneKey(parsed);
|
|
458
629
|
if (!sceneKey) {
|
|
459
630
|
return { ok: false, text: 'Current group shortcut only works inside a group chat.' };
|
|
@@ -482,9 +653,14 @@ export function executeSceneAdminCommand(args: {
|
|
|
482
653
|
if (existing.kind !== 'group') {
|
|
483
654
|
return { ok: false, text: `Scene ${sceneKey} is not a group scene.` };
|
|
484
655
|
}
|
|
656
|
+
if (command.mode === 'clear') {
|
|
657
|
+
const { groupReplyMode: _, ...rest } = existing;
|
|
658
|
+
sceneRegistry.set(sceneKey, { ...rest, lastSeenAt: now() });
|
|
659
|
+
return { ok: true, text: `Cleared ${sceneKey} reply mode. Will use default (admin).` };
|
|
660
|
+
}
|
|
485
661
|
sceneRegistry.set(sceneKey, {
|
|
486
662
|
...existing,
|
|
487
|
-
groupReplyMode: command.mode,
|
|
663
|
+
groupReplyMode: command.mode as BncrGroupReplyMode,
|
|
488
664
|
lastSeenAt: now(),
|
|
489
665
|
});
|
|
490
666
|
return { ok: true, text: `Set ${sceneKey} reply mode to ${command.mode}.` };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HistoryMediaEntry } from 'openclaw/plugin-sdk/reply-history';
|
|
1
2
|
import { readBncrSessionUpdatedAt } from '../../openclaw/inbound-session-runtime.ts';
|
|
2
3
|
import {
|
|
3
4
|
formatOpenClawAgentEnvelope,
|
|
@@ -125,6 +126,20 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
125
126
|
const pendingHistoryMedia = shouldDispatch
|
|
126
127
|
? collectBncrHistoryMediaFromEntries({ entries: pendingHistoryEntries })
|
|
127
128
|
: [];
|
|
129
|
+
const pendingHistoryContext = shouldDispatch
|
|
130
|
+
? pendingHistoryEntries.map((entry) => ({
|
|
131
|
+
messageId: entry.messageId,
|
|
132
|
+
sender: entry.sender,
|
|
133
|
+
senderId: entry.senderId,
|
|
134
|
+
body: entry.body,
|
|
135
|
+
media: (entry.media || []).map((item: HistoryMediaEntry) => ({
|
|
136
|
+
path: item.path,
|
|
137
|
+
contentType: item.contentType,
|
|
138
|
+
kind: item.kind,
|
|
139
|
+
messageId: item.messageId,
|
|
140
|
+
})),
|
|
141
|
+
}))
|
|
142
|
+
: [];
|
|
128
143
|
const structuredContextFacts = buildBncrStructuredContextFactsFromInboundParts({
|
|
129
144
|
channelId,
|
|
130
145
|
parsed,
|
|
@@ -140,6 +155,7 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
140
155
|
bridgeSenderName,
|
|
141
156
|
senderIsOwner,
|
|
142
157
|
senderIsAuthorized,
|
|
158
|
+
pendingHistoryContext,
|
|
143
159
|
});
|
|
144
160
|
const promptVisibleContextFacts = buildBncrPromptVisibleContextFacts(structuredContextFacts);
|
|
145
161
|
const supplementalUntrustedContext = [
|