@xmoxmo/bncr 0.6.4 → 0.6.5
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 +36 -5
- package/package.json +1 -1
- package/scripts/check-pack.mjs +2 -0
- package/scripts/selfcheck.mjs +2 -0
- package/src/messaging/inbound/command-owner.ts +33 -0
- package/src/messaging/inbound/commands.ts +386 -252
- package/src/messaging/inbound/dispatch-prep.ts +8 -4
- package/src/messaging/inbound/dispatch.ts +3 -1
- package/src/messaging/inbound/native-command-runtime.ts +4 -0
- package/src/messaging/inbound/native-command.ts +89 -15
- package/src/messaging/inbound/reply-dispatch.ts +17 -36
- package/src/messaging/inbound/scene-admin.ts +31 -3
- package/src/openclaw/inbound-session-runtime.ts +17 -0
- package/src/openclaw/session-reset-runtime.ts +77 -0
- package/src/plugin/inbound-acceptance.ts +18 -7
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { hasControlCommand } from 'openclaw/plugin-sdk/command-auth';
|
|
1
2
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
2
3
|
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
3
4
|
import { formatDisplayScope } from '../../core/targets.ts';
|
|
4
5
|
import {
|
|
6
|
+
readBncrSessionEntry,
|
|
5
7
|
recordBncrInboundSession,
|
|
6
8
|
resolveBncrInboundSessionStorePath,
|
|
7
9
|
resolveBncrPinnedMainDmOwnerFromAllowlist,
|
|
8
10
|
} from '../../openclaw/inbound-session-runtime.ts';
|
|
9
11
|
import { dispatchOpenClawReplyWithBufferedBlockDispatcher } from '../../openclaw/reply-runtime.ts';
|
|
10
12
|
import { resolveOpenClawAgentRoute } from '../../openclaw/routing-runtime.ts';
|
|
13
|
+
import { performBncrGatewaySessionReset } from '../../openclaw/session-reset-runtime.ts';
|
|
14
|
+
import { mergeBncrOwnerAllowFromIntoConfig } from './command-owner.ts';
|
|
11
15
|
import type {
|
|
12
16
|
BncrEnqueueFromReply,
|
|
13
17
|
BncrInboundApi,
|
|
@@ -18,8 +22,13 @@ import type {
|
|
|
18
22
|
import { assertResolvedAgentRoute, type ParsedInbound } from './dispatch-prep.ts';
|
|
19
23
|
import { buildBncrInboundRecordUpdateLastRoute } from './last-route.ts';
|
|
20
24
|
import {
|
|
25
|
+
BNCR_OPENCLAW_NATIVE_COMMAND,
|
|
26
|
+
BNCR_SELF_SERVICE_COMMANDS,
|
|
27
|
+
isBncrStopCommandText,
|
|
28
|
+
isBncrWhitelistBareCommandText,
|
|
21
29
|
parseBncrNativeCommand,
|
|
22
30
|
parseBncrUnsupportedDirectCommand,
|
|
31
|
+
resolveBncrNativeCommandParseOptions,
|
|
23
32
|
resolveBncrNativeHelpCommand,
|
|
24
33
|
resolveBncrNativeSessionResetCommand,
|
|
25
34
|
resolveBncrNativeStatusCommand,
|
|
@@ -108,14 +117,49 @@ export async function handleBncrNativeCommand(params: {
|
|
|
108
117
|
enqueueFromReply,
|
|
109
118
|
} = params;
|
|
110
119
|
const { accountId, route, peer, clientId, extracted, msgId } = parsed;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
|
|
121
|
+
// Stop always goes through the unified stop fast path in dispatch.ts.
|
|
122
|
+
// It is not a bncr native command and must never be processed here.
|
|
123
|
+
if (isBncrStopCommandText(extracted.text)) {
|
|
124
|
+
return { handled: false };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const whitelistBareCommand =
|
|
128
|
+
!parsed.isAdmin && parsed.peer.kind === 'direct'
|
|
129
|
+
? isBncrWhitelistBareCommandText(extracted.text)
|
|
130
|
+
: null;
|
|
131
|
+
let dispatchToOpenClaw =
|
|
132
|
+
whitelistBareCommand === 'whoami' ||
|
|
133
|
+
whitelistBareCommand === 'status' ||
|
|
134
|
+
whitelistBareCommand === 'model' ||
|
|
135
|
+
whitelistBareCommand === 'verbose';
|
|
136
|
+
const effectiveIsAdmin = whitelistBareCommand ? true : parsed.isAdmin;
|
|
137
|
+
let command: ReturnType<typeof parseBncrNativeCommand>;
|
|
138
|
+
if (whitelistBareCommand) {
|
|
139
|
+
// Private non-admin whitelist commands are temporarily elevated to admin
|
|
140
|
+
// for the local session-reset handlers or handed to the OpenClaw native
|
|
141
|
+
// parser. They never fall back to the agent so the agent cannot observe
|
|
142
|
+
// a conflicting identity.
|
|
143
|
+
const raw = String(extracted.text || '').trim();
|
|
144
|
+
command = { command: whitelistBareCommand, raw, body: raw, argsText: '' };
|
|
145
|
+
const argsTextStart = raw.indexOf(' ');
|
|
146
|
+
if (argsTextStart !== -1) {
|
|
147
|
+
command.argsText = raw.slice(argsTextStart + 1).trim();
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
command = parseBncrNativeCommand(
|
|
151
|
+
extracted.text,
|
|
152
|
+
resolveBncrNativeCommandParseOptions({
|
|
153
|
+
isAdmin: parsed.isAdmin,
|
|
154
|
+
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (!command) {
|
|
159
|
+
const unsupportedCommand = parseBncrUnsupportedDirectCommand(extracted.text);
|
|
160
|
+
// Unknown /bncr subcommands are rejected by bncr without agent fallback,
|
|
161
|
+
// for both admin and non-admin callers and in private or group chat.
|
|
162
|
+
if (unsupportedCommand?.command.startsWith('bncr ')) {
|
|
119
163
|
const rejectedRoute = buildBncrNativeCommandResolvedRoute({
|
|
120
164
|
api,
|
|
121
165
|
cfg,
|
|
@@ -133,7 +177,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
133
177
|
logBncrNativeCommandSummary(
|
|
134
178
|
buildBncrNativeCommandSummary({
|
|
135
179
|
kind: 'unsupported',
|
|
136
|
-
command:
|
|
180
|
+
command: unsupportedCommand.command,
|
|
137
181
|
accountId,
|
|
138
182
|
to: displayTo,
|
|
139
183
|
msgId: msgId || null,
|
|
@@ -145,20 +189,43 @@ export async function handleBncrNativeCommand(params: {
|
|
|
145
189
|
sessionKey: baseSessionKey,
|
|
146
190
|
route,
|
|
147
191
|
payload: {
|
|
148
|
-
text: `Unsupported
|
|
192
|
+
text: `Unsupported command: /${unsupportedCommand.command}`,
|
|
149
193
|
replyToId: msgId || undefined,
|
|
150
194
|
},
|
|
151
195
|
});
|
|
152
|
-
return { handled: true, command:
|
|
196
|
+
return { handled: true, command: unsupportedCommand.command, sessionKey };
|
|
197
|
+
}
|
|
198
|
+
// Non-bncr bare commands fall through to the OpenClaw parser. Admin
|
|
199
|
+
// callers may fall back to the agent; non-admin callers keep their
|
|
200
|
+
// original identity when they do.
|
|
201
|
+
|
|
202
|
+
// Admin bare OpenClaw native commands are routed to the OpenClaw parser
|
|
203
|
+
// and handled there, preventing a second agent turn from producing a
|
|
204
|
+
// duplicate reply. This mirrors the non-admin whitelist elevation path.
|
|
205
|
+
if (
|
|
206
|
+
!command &&
|
|
207
|
+
parsed.isAdmin &&
|
|
208
|
+
hasControlCommand(extracted.text, cfg as Parameters<typeof hasControlCommand>[1])
|
|
209
|
+
) {
|
|
210
|
+
const raw = String(extracted.text || '').trim();
|
|
211
|
+
command = { command: BNCR_OPENCLAW_NATIVE_COMMAND, raw, body: raw, argsText: '' };
|
|
212
|
+
const argsTextStart = raw.indexOf(' ');
|
|
213
|
+
if (argsTextStart !== -1) {
|
|
214
|
+
command.argsText = raw.slice(argsTextStart + 1).trim();
|
|
215
|
+
}
|
|
216
|
+
dispatchToOpenClaw = true;
|
|
153
217
|
}
|
|
154
218
|
}
|
|
155
219
|
if (!command) return { handled: false };
|
|
156
220
|
const nativeCommandDebugEnabled = resolveNativeCommandDebugEnabled({ cfg, channelId });
|
|
157
221
|
|
|
222
|
+
const displayCommand =
|
|
223
|
+
command.command === BNCR_OPENCLAW_NATIVE_COMMAND ? command.raw : command.command;
|
|
224
|
+
|
|
158
225
|
logBncrNativeCommandEvent(
|
|
159
226
|
'detected',
|
|
160
227
|
{
|
|
161
|
-
command:
|
|
228
|
+
command: displayCommand,
|
|
162
229
|
accountId,
|
|
163
230
|
to: formatDisplayScope(route),
|
|
164
231
|
msgId: msgId || null,
|
|
@@ -198,6 +265,9 @@ export async function handleBncrNativeCommand(params: {
|
|
|
198
265
|
agentId: resolvedRoute.agentId,
|
|
199
266
|
});
|
|
200
267
|
|
|
268
|
+
const dispatchOwnerAllowFrom =
|
|
269
|
+
dispatchToOpenClaw && senderIdForContext ? [senderIdForContext] : undefined;
|
|
270
|
+
|
|
201
271
|
const ctxPayload = await Promise.resolve(
|
|
202
272
|
createNativeCommandTurnContext({
|
|
203
273
|
api,
|
|
@@ -207,6 +277,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
207
277
|
peer,
|
|
208
278
|
resolvedRoute,
|
|
209
279
|
sessionKey,
|
|
280
|
+
ownerAllowFrom: dispatchOwnerAllowFrom,
|
|
210
281
|
displayTo,
|
|
211
282
|
originatingTo,
|
|
212
283
|
senderIdForContext,
|
|
@@ -223,9 +294,16 @@ export async function handleBncrNativeCommand(params: {
|
|
|
223
294
|
senderId: senderIdForContext,
|
|
224
295
|
});
|
|
225
296
|
|
|
226
|
-
const
|
|
297
|
+
const currentVerboseLevel = storePath
|
|
298
|
+
? ((await readBncrSessionEntry({ storePath, sessionKey }))?.verboseLevel as
|
|
299
|
+
| 'on'
|
|
300
|
+
| 'off'
|
|
301
|
+
| 'full'
|
|
302
|
+
| undefined)
|
|
303
|
+
: undefined;
|
|
304
|
+
const nativeVerbose = resolveBncrNativeVerboseCommand(command, currentVerboseLevel);
|
|
227
305
|
const nativeHelp = resolveBncrNativeHelpCommand(command, {
|
|
228
|
-
isAdmin:
|
|
306
|
+
isAdmin: effectiveIsAdmin,
|
|
229
307
|
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
230
308
|
});
|
|
231
309
|
const nativeWhoami = resolveBncrNativeWhoamiCommand({
|
|
@@ -236,7 +314,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
236
314
|
userId: parsed.userId,
|
|
237
315
|
userName: parsed.userName,
|
|
238
316
|
isGroup: parsed.isGroup,
|
|
239
|
-
isAdmin:
|
|
317
|
+
isAdmin: effectiveIsAdmin,
|
|
240
318
|
});
|
|
241
319
|
const nativeStatus = resolveBncrNativeStatusCommand({
|
|
242
320
|
command,
|
|
@@ -251,107 +329,16 @@ export async function handleBncrNativeCommand(params: {
|
|
|
251
329
|
command,
|
|
252
330
|
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
253
331
|
});
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
buildBncrNativeCommandSummary({
|
|
257
|
-
kind: 'help',
|
|
258
|
-
command: command.command,
|
|
259
|
-
accountId,
|
|
260
|
-
to: displayTo,
|
|
261
|
-
msgId: msgId || null,
|
|
262
|
-
result: 'handled',
|
|
263
|
-
}),
|
|
264
|
-
);
|
|
265
|
-
await recordAndPatchBncrInboundSessionEntry({
|
|
266
|
-
storePath,
|
|
267
|
-
sessionKey,
|
|
268
|
-
ctx: ctxPayload,
|
|
269
|
-
patch: sessionIdentityPatch,
|
|
270
|
-
});
|
|
271
|
-
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
272
|
-
await enqueueFromReply({
|
|
273
|
-
accountId,
|
|
274
|
-
sessionKey,
|
|
275
|
-
route,
|
|
276
|
-
payload: {
|
|
277
|
-
text: nativeHelp.text,
|
|
278
|
-
replyToId: msgId || undefined,
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
return { handled: true, command: command.command, sessionKey };
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (nativeWhoami) {
|
|
285
|
-
logBncrNativeCommandSummary(
|
|
286
|
-
buildBncrNativeCommandSummary({
|
|
287
|
-
kind: 'whoami',
|
|
288
|
-
command: command.command,
|
|
289
|
-
accountId,
|
|
290
|
-
to: displayTo,
|
|
291
|
-
msgId: msgId || null,
|
|
292
|
-
result: 'handled',
|
|
293
|
-
}),
|
|
294
|
-
);
|
|
295
|
-
await recordAndPatchBncrInboundSessionEntry({
|
|
296
|
-
storePath,
|
|
297
|
-
sessionKey,
|
|
298
|
-
ctx: ctxPayload,
|
|
299
|
-
patch: sessionIdentityPatch,
|
|
300
|
-
});
|
|
301
|
-
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
302
|
-
await enqueueFromReply({
|
|
303
|
-
accountId,
|
|
304
|
-
sessionKey,
|
|
305
|
-
route,
|
|
306
|
-
payload: {
|
|
307
|
-
text: nativeWhoami.text,
|
|
308
|
-
replyToId: msgId || undefined,
|
|
309
|
-
},
|
|
310
|
-
});
|
|
311
|
-
return { handled: true, command: command.command, sessionKey };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (nativeStatus) {
|
|
315
|
-
logBncrNativeCommandSummary(
|
|
316
|
-
buildBncrNativeCommandSummary({
|
|
317
|
-
kind: 'status',
|
|
318
|
-
command: command.command,
|
|
319
|
-
accountId,
|
|
320
|
-
to: displayTo,
|
|
321
|
-
msgId: msgId || null,
|
|
322
|
-
result: 'handled',
|
|
323
|
-
}),
|
|
324
|
-
);
|
|
325
|
-
await recordAndPatchBncrInboundSessionEntry({
|
|
326
|
-
storePath,
|
|
327
|
-
sessionKey,
|
|
328
|
-
ctx: ctxPayload,
|
|
329
|
-
patch: sessionIdentityPatch,
|
|
330
|
-
});
|
|
331
|
-
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
332
|
-
await enqueueFromReply({
|
|
333
|
-
accountId,
|
|
334
|
-
sessionKey,
|
|
335
|
-
route,
|
|
336
|
-
payload: {
|
|
337
|
-
text: nativeStatus.text,
|
|
338
|
-
replyToId: msgId || undefined,
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
return { handled: true, command: command.command, sessionKey };
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (nativeSessionReset) {
|
|
345
|
-
const allowLocalSessionReset = parsed.peer.kind === 'direct' || parsed.isAdmin;
|
|
346
|
-
if (!allowLocalSessionReset) {
|
|
332
|
+
if (!dispatchToOpenClaw) {
|
|
333
|
+
if (nativeHelp) {
|
|
347
334
|
logBncrNativeCommandSummary(
|
|
348
335
|
buildBncrNativeCommandSummary({
|
|
349
|
-
kind:
|
|
336
|
+
kind: 'help',
|
|
350
337
|
command: command.command,
|
|
351
338
|
accountId,
|
|
352
339
|
to: displayTo,
|
|
353
340
|
msgId: msgId || null,
|
|
354
|
-
result: '
|
|
341
|
+
result: 'handled',
|
|
355
342
|
}),
|
|
356
343
|
);
|
|
357
344
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -361,63 +348,57 @@ export async function handleBncrNativeCommand(params: {
|
|
|
361
348
|
patch: sessionIdentityPatch,
|
|
362
349
|
});
|
|
363
350
|
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
351
|
+
await enqueueFromReply({
|
|
352
|
+
accountId,
|
|
353
|
+
sessionKey,
|
|
354
|
+
route,
|
|
355
|
+
payload: {
|
|
356
|
+
text: nativeHelp.text,
|
|
357
|
+
replyToId: msgId || undefined,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
364
360
|
return { handled: true, command: command.command, sessionKey };
|
|
365
361
|
}
|
|
366
362
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
throw new Error(`sessions.reset failed for ${baseSessionKey}`);
|
|
387
|
-
}
|
|
388
|
-
logBncrNativeCommandSummary(
|
|
389
|
-
buildBncrNativeCommandSummary({
|
|
390
|
-
kind: nativeSessionReset.reason,
|
|
391
|
-
command: command.command,
|
|
363
|
+
if (nativeWhoami) {
|
|
364
|
+
logBncrNativeCommandSummary(
|
|
365
|
+
buildBncrNativeCommandSummary({
|
|
366
|
+
kind: 'whoami',
|
|
367
|
+
command: command.command,
|
|
368
|
+
accountId,
|
|
369
|
+
to: displayTo,
|
|
370
|
+
msgId: msgId || null,
|
|
371
|
+
result: 'handled',
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
375
|
+
storePath,
|
|
376
|
+
sessionKey,
|
|
377
|
+
ctx: ctxPayload,
|
|
378
|
+
patch: sessionIdentityPatch,
|
|
379
|
+
});
|
|
380
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
381
|
+
await enqueueFromReply({
|
|
392
382
|
accountId,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
route,
|
|
403
|
-
payload: {
|
|
404
|
-
text: nativeSessionReset.text,
|
|
405
|
-
replyToId: msgId || undefined,
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
409
|
-
}
|
|
383
|
+
sessionKey,
|
|
384
|
+
route,
|
|
385
|
+
payload: {
|
|
386
|
+
text: nativeWhoami.text,
|
|
387
|
+
replyToId: msgId || undefined,
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
return { handled: true, command: command.command, sessionKey };
|
|
391
|
+
}
|
|
410
392
|
|
|
411
|
-
|
|
412
|
-
if (!parsed.isAdmin) {
|
|
393
|
+
if (nativeStatus) {
|
|
413
394
|
logBncrNativeCommandSummary(
|
|
414
395
|
buildBncrNativeCommandSummary({
|
|
415
|
-
kind: '
|
|
396
|
+
kind: 'status',
|
|
416
397
|
command: command.command,
|
|
417
398
|
accountId,
|
|
418
399
|
to: displayTo,
|
|
419
400
|
msgId: msgId || null,
|
|
420
|
-
result: '
|
|
401
|
+
result: 'handled',
|
|
421
402
|
}),
|
|
422
403
|
);
|
|
423
404
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -427,96 +408,258 @@ export async function handleBncrNativeCommand(params: {
|
|
|
427
408
|
patch: sessionIdentityPatch,
|
|
428
409
|
});
|
|
429
410
|
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
430
|
-
|
|
411
|
+
await enqueueFromReply({
|
|
412
|
+
accountId,
|
|
413
|
+
sessionKey,
|
|
414
|
+
route,
|
|
415
|
+
payload: {
|
|
416
|
+
text: nativeStatus.text,
|
|
417
|
+
replyToId: msgId || undefined,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
return { handled: true, command: command.command, sessionKey };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (nativeSessionReset) {
|
|
424
|
+
const allowLocalSessionReset = parsed.peer.kind === 'direct' || effectiveIsAdmin;
|
|
425
|
+
if (!allowLocalSessionReset) {
|
|
426
|
+
logBncrNativeCommandSummary(
|
|
427
|
+
buildBncrNativeCommandSummary({
|
|
428
|
+
kind: nativeSessionReset.reason,
|
|
429
|
+
command: command.command,
|
|
430
|
+
accountId,
|
|
431
|
+
to: displayTo,
|
|
432
|
+
msgId: msgId || null,
|
|
433
|
+
result: 'rejected',
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
437
|
+
storePath,
|
|
438
|
+
sessionKey,
|
|
439
|
+
ctx: ctxPayload,
|
|
440
|
+
patch: sessionIdentityPatch,
|
|
441
|
+
});
|
|
442
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
443
|
+
return { handled: true, command: command.command, sessionKey };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const requestedAgentId = resolvedRoute.agentId || canonicalAgentId;
|
|
447
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
448
|
+
storePath,
|
|
449
|
+
sessionKey,
|
|
450
|
+
ctx: ctxPayload,
|
|
451
|
+
patch: sessionIdentityPatch,
|
|
452
|
+
});
|
|
453
|
+
let resetResult: { ok?: boolean } | undefined;
|
|
454
|
+
try {
|
|
455
|
+
resetResult = await performBncrGatewaySessionReset({
|
|
456
|
+
key: baseSessionKey,
|
|
457
|
+
reason: nativeSessionReset.reason,
|
|
458
|
+
...(requestedAgentId ? { agentId: requestedAgentId } : {}),
|
|
459
|
+
commandSource: 'bncr:native-command',
|
|
460
|
+
});
|
|
461
|
+
} catch (resetError) {
|
|
462
|
+
emitBncrLogLine(
|
|
463
|
+
'error',
|
|
464
|
+
`[bncr] native-command sessions.reset failed|key=${baseSessionKey}|err=${resetError instanceof Error ? resetError.message : String(resetError)}`,
|
|
465
|
+
);
|
|
431
466
|
await enqueueFromReply({
|
|
432
467
|
accountId,
|
|
433
|
-
sessionKey,
|
|
468
|
+
sessionKey: baseSessionKey,
|
|
434
469
|
route,
|
|
435
470
|
payload: {
|
|
436
|
-
text:
|
|
471
|
+
text: `Session reset failed: ${resetError instanceof Error ? resetError.message : String(resetError)}`,
|
|
437
472
|
replyToId: msgId || undefined,
|
|
438
473
|
},
|
|
439
474
|
});
|
|
475
|
+
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
440
476
|
}
|
|
441
|
-
|
|
477
|
+
if (resetResult?.ok === false) {
|
|
478
|
+
await enqueueFromReply({
|
|
479
|
+
accountId,
|
|
480
|
+
sessionKey: baseSessionKey,
|
|
481
|
+
route,
|
|
482
|
+
payload: {
|
|
483
|
+
text: 'Session reset was rejected by the gateway.',
|
|
484
|
+
replyToId: msgId || undefined,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
488
|
+
}
|
|
489
|
+
logBncrNativeCommandSummary(
|
|
490
|
+
buildBncrNativeCommandSummary({
|
|
491
|
+
kind: nativeSessionReset.reason,
|
|
492
|
+
command: command.command,
|
|
493
|
+
accountId,
|
|
494
|
+
to: displayTo,
|
|
495
|
+
msgId: msgId || null,
|
|
496
|
+
result: 'handled',
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
500
|
+
await enqueueFromReply({
|
|
501
|
+
accountId,
|
|
502
|
+
sessionKey: baseSessionKey,
|
|
503
|
+
route,
|
|
504
|
+
payload: {
|
|
505
|
+
text: nativeSessionReset.text,
|
|
506
|
+
replyToId: msgId || undefined,
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
442
510
|
}
|
|
443
511
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
await enqueueFromReply({
|
|
477
|
-
accountId,
|
|
478
|
-
sessionKey,
|
|
479
|
-
route,
|
|
480
|
-
payload: {
|
|
481
|
-
text: nativeVerbose.text,
|
|
482
|
-
replyToId: msgId || undefined,
|
|
483
|
-
},
|
|
484
|
-
});
|
|
485
|
-
return { handled: true, command: command.command, sessionKey };
|
|
486
|
-
}
|
|
512
|
+
if (nativeVerbose) {
|
|
513
|
+
if (!effectiveIsAdmin && parsed.peer.kind !== 'direct') {
|
|
514
|
+
logBncrNativeCommandSummary(
|
|
515
|
+
buildBncrNativeCommandSummary({
|
|
516
|
+
kind: 'verbose',
|
|
517
|
+
command: command.command,
|
|
518
|
+
accountId,
|
|
519
|
+
to: displayTo,
|
|
520
|
+
msgId: msgId || null,
|
|
521
|
+
result: 'rejected',
|
|
522
|
+
}),
|
|
523
|
+
);
|
|
524
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
525
|
+
storePath,
|
|
526
|
+
sessionKey,
|
|
527
|
+
ctx: ctxPayload,
|
|
528
|
+
patch: sessionIdentityPatch,
|
|
529
|
+
});
|
|
530
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
531
|
+
if (parsed.peer.kind !== 'group') {
|
|
532
|
+
await enqueueFromReply({
|
|
533
|
+
accountId,
|
|
534
|
+
sessionKey,
|
|
535
|
+
route,
|
|
536
|
+
payload: {
|
|
537
|
+
text: 'Admin permission required.',
|
|
538
|
+
replyToId: msgId || undefined,
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return { handled: true, command: command.command, sessionKey };
|
|
543
|
+
}
|
|
487
544
|
|
|
488
|
-
const sceneAdmin = parseSceneAdminCommand(command);
|
|
489
|
-
if (sceneAdmin.matched) {
|
|
490
|
-
if (!parsed.isAdmin) {
|
|
491
545
|
logBncrNativeCommandSummary(
|
|
492
546
|
buildBncrNativeCommandSummary({
|
|
493
|
-
kind: '
|
|
547
|
+
kind: 'verbose',
|
|
494
548
|
command: command.command,
|
|
495
549
|
accountId,
|
|
496
550
|
to: displayTo,
|
|
497
|
-
msgId: msgId
|
|
498
|
-
result: '
|
|
551
|
+
msgId: msgId || null,
|
|
552
|
+
result: 'handled',
|
|
499
553
|
}),
|
|
500
554
|
);
|
|
555
|
+
logBncrNativeCommandEvent(
|
|
556
|
+
'handled-verbose',
|
|
557
|
+
{
|
|
558
|
+
command: command.command,
|
|
559
|
+
accountId,
|
|
560
|
+
sessionKey,
|
|
561
|
+
to: displayTo,
|
|
562
|
+
msgId: msgId || null,
|
|
563
|
+
fallbackToAgent: false,
|
|
564
|
+
},
|
|
565
|
+
{ debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
|
|
566
|
+
);
|
|
501
567
|
await recordAndPatchBncrInboundSessionEntry({
|
|
502
568
|
storePath,
|
|
503
569
|
sessionKey,
|
|
504
570
|
ctx: ctxPayload,
|
|
505
|
-
patch:
|
|
571
|
+
patch: {
|
|
572
|
+
...sessionIdentityPatch,
|
|
573
|
+
...(nativeVerbose.verboseLevel ? { verboseLevel: nativeVerbose.verboseLevel } : {}),
|
|
574
|
+
},
|
|
506
575
|
});
|
|
576
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
507
577
|
await enqueueFromReply({
|
|
508
578
|
accountId,
|
|
509
579
|
sessionKey,
|
|
510
580
|
route,
|
|
511
581
|
payload: {
|
|
512
|
-
text:
|
|
582
|
+
text: nativeVerbose.text,
|
|
513
583
|
replyToId: msgId || undefined,
|
|
514
584
|
},
|
|
515
585
|
});
|
|
516
586
|
return { handled: true, command: command.command, sessionKey };
|
|
517
587
|
}
|
|
518
588
|
|
|
519
|
-
|
|
589
|
+
const sceneAdmin = parseSceneAdminCommand(command);
|
|
590
|
+
if (sceneAdmin.matched) {
|
|
591
|
+
const directSelfAdminCommand =
|
|
592
|
+
parsed.peer.kind === 'direct' &&
|
|
593
|
+
new Set(['history-help', 'history-limit', 'history-force', 'download-media']).has(
|
|
594
|
+
command.command,
|
|
595
|
+
);
|
|
596
|
+
if (!effectiveIsAdmin && !directSelfAdminCommand) {
|
|
597
|
+
logBncrNativeCommandSummary(
|
|
598
|
+
buildBncrNativeCommandSummary({
|
|
599
|
+
kind: 'scene-admin',
|
|
600
|
+
command: command.command,
|
|
601
|
+
accountId,
|
|
602
|
+
to: displayTo,
|
|
603
|
+
msgId: msgId ?? null,
|
|
604
|
+
result: 'rejected',
|
|
605
|
+
}),
|
|
606
|
+
);
|
|
607
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
608
|
+
storePath,
|
|
609
|
+
sessionKey,
|
|
610
|
+
ctx: ctxPayload,
|
|
611
|
+
patch: sessionIdentityPatch,
|
|
612
|
+
});
|
|
613
|
+
await enqueueFromReply({
|
|
614
|
+
accountId,
|
|
615
|
+
sessionKey,
|
|
616
|
+
route,
|
|
617
|
+
payload: {
|
|
618
|
+
text: 'Admin permission required.',
|
|
619
|
+
replyToId: msgId || undefined,
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
return { handled: true, command: command.command, sessionKey };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (!sceneAdmin.valid) {
|
|
626
|
+
logBncrNativeCommandSummary(
|
|
627
|
+
buildBncrNativeCommandSummary({
|
|
628
|
+
kind: 'scene-admin',
|
|
629
|
+
command: command.command,
|
|
630
|
+
accountId,
|
|
631
|
+
to: displayTo,
|
|
632
|
+
msgId: msgId ?? null,
|
|
633
|
+
result: 'rejected',
|
|
634
|
+
}),
|
|
635
|
+
);
|
|
636
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
637
|
+
storePath,
|
|
638
|
+
sessionKey,
|
|
639
|
+
ctx: ctxPayload,
|
|
640
|
+
patch: sessionIdentityPatch,
|
|
641
|
+
});
|
|
642
|
+
await enqueueFromReply({
|
|
643
|
+
accountId,
|
|
644
|
+
sessionKey,
|
|
645
|
+
route,
|
|
646
|
+
payload: {
|
|
647
|
+
text: sceneAdmin.text,
|
|
648
|
+
replyToId: msgId || undefined,
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
return { handled: true, command: command.command, sessionKey };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const outcome = executeSceneAdminCommand({
|
|
655
|
+
parsed,
|
|
656
|
+
command: sceneAdmin.command,
|
|
657
|
+
sceneRegistry,
|
|
658
|
+
defaultAdminAgentId,
|
|
659
|
+
defaultPublicAgentId,
|
|
660
|
+
now,
|
|
661
|
+
allowNonAdminSelfAdmin: !!directSelfAdminCommand,
|
|
662
|
+
});
|
|
520
663
|
logBncrNativeCommandSummary(
|
|
521
664
|
buildBncrNativeCommandSummary({
|
|
522
665
|
kind: 'scene-admin',
|
|
@@ -524,7 +667,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
524
667
|
accountId,
|
|
525
668
|
to: displayTo,
|
|
526
669
|
msgId: msgId ?? null,
|
|
527
|
-
result: 'rejected',
|
|
670
|
+
result: outcome.ok ? 'handled' : 'rejected',
|
|
528
671
|
}),
|
|
529
672
|
);
|
|
530
673
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -538,47 +681,12 @@ export async function handleBncrNativeCommand(params: {
|
|
|
538
681
|
sessionKey,
|
|
539
682
|
route,
|
|
540
683
|
payload: {
|
|
541
|
-
text:
|
|
684
|
+
text: outcome.text,
|
|
542
685
|
replyToId: msgId || undefined,
|
|
543
686
|
},
|
|
544
687
|
});
|
|
545
688
|
return { handled: true, command: command.command, sessionKey };
|
|
546
689
|
}
|
|
547
|
-
|
|
548
|
-
const outcome = executeSceneAdminCommand({
|
|
549
|
-
parsed,
|
|
550
|
-
command: sceneAdmin.command,
|
|
551
|
-
sceneRegistry,
|
|
552
|
-
defaultAdminAgentId,
|
|
553
|
-
defaultPublicAgentId,
|
|
554
|
-
now,
|
|
555
|
-
});
|
|
556
|
-
logBncrNativeCommandSummary(
|
|
557
|
-
buildBncrNativeCommandSummary({
|
|
558
|
-
kind: 'scene-admin',
|
|
559
|
-
command: command.command,
|
|
560
|
-
accountId,
|
|
561
|
-
to: displayTo,
|
|
562
|
-
msgId: msgId ?? null,
|
|
563
|
-
result: outcome.ok ? 'handled' : 'rejected',
|
|
564
|
-
}),
|
|
565
|
-
);
|
|
566
|
-
await recordAndPatchBncrInboundSessionEntry({
|
|
567
|
-
storePath,
|
|
568
|
-
sessionKey,
|
|
569
|
-
ctx: ctxPayload,
|
|
570
|
-
patch: sessionIdentityPatch,
|
|
571
|
-
});
|
|
572
|
-
await enqueueFromReply({
|
|
573
|
-
accountId,
|
|
574
|
-
sessionKey,
|
|
575
|
-
route,
|
|
576
|
-
payload: {
|
|
577
|
-
text: outcome.text,
|
|
578
|
-
replyToId: msgId || undefined,
|
|
579
|
-
},
|
|
580
|
-
});
|
|
581
|
-
return { handled: true, command: command.command, sessionKey };
|
|
582
690
|
}
|
|
583
691
|
|
|
584
692
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -588,7 +696,11 @@ export async function handleBncrNativeCommand(params: {
|
|
|
588
696
|
patch: sessionIdentityPatch,
|
|
589
697
|
});
|
|
590
698
|
|
|
591
|
-
const
|
|
699
|
+
const dispatchCfgForElevated =
|
|
700
|
+
dispatchToOpenClaw && senderIdForContext
|
|
701
|
+
? mergeBncrOwnerAllowFromIntoConfig({ cfg, senderIdForContext })
|
|
702
|
+
: cfg;
|
|
703
|
+
const effectiveReply = buildBncrReplyConfig(dispatchCfgForElevated);
|
|
592
704
|
const channelPolicy = resolveBncrChannelPolicy(cfg?.channels?.bncr || {});
|
|
593
705
|
const pinnedMainDmOwner =
|
|
594
706
|
peer.kind === 'direct'
|
|
@@ -691,13 +803,35 @@ export async function handleBncrNativeCommand(params: {
|
|
|
691
803
|
});
|
|
692
804
|
|
|
693
805
|
if (!responded) {
|
|
806
|
+
// Self-service commands must never fall back to the agent — doing so
|
|
807
|
+
// would let the agent see a message whose author identity was
|
|
808
|
+
// temporarily elevated, causing role confusion.
|
|
809
|
+
if (BNCR_SELF_SERVICE_COMMANDS.has(command.command)) {
|
|
810
|
+
emitBncrLogLine(
|
|
811
|
+
'warn',
|
|
812
|
+
`[bncr] self-service command=${displayCommand} produced no payload; refusing agent fallback`,
|
|
813
|
+
);
|
|
814
|
+
await enqueueFromReply({
|
|
815
|
+
accountId,
|
|
816
|
+
sessionKey,
|
|
817
|
+
route,
|
|
818
|
+
payload: {
|
|
819
|
+
text: `Command /${command.command} failed.`,
|
|
820
|
+
replyToId: msgId || undefined,
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
return buildNativeCommandHandledResult({
|
|
824
|
+
command: command.command,
|
|
825
|
+
sessionKey,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
694
828
|
logBncrNativeCommandSummary(
|
|
695
|
-
`fallback command=${
|
|
829
|
+
`fallback command=${displayCommand}|accountId=${accountId}|to=${displayTo}|msgId=${msgId || '-'}|reason=no-payload`,
|
|
696
830
|
);
|
|
697
831
|
logBncrNativeCommandEvent(
|
|
698
832
|
'no-payload-fallback-to-agent',
|
|
699
833
|
{
|
|
700
|
-
command:
|
|
834
|
+
command: displayCommand,
|
|
701
835
|
accountId,
|
|
702
836
|
sessionKey,
|
|
703
837
|
to: displayTo,
|