@xmoxmo/bncr 0.6.4 → 0.6.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/README.md +146 -307
- package/package.json +7 -7
- package/scripts/check-pack.mjs +7 -1
- package/scripts/selfcheck.mjs +4 -1
- package/src/core/types.ts +1 -1
- package/src/messaging/inbound/command-owner.ts +33 -0
- package/src/messaging/inbound/commands.ts +427 -252
- package/src/messaging/inbound/conversation-history.ts +26 -5
- 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 +92 -22
- package/src/messaging/inbound/outbound-replay-cache.ts +18 -2
- package/src/messaging/inbound/reply-dispatch.ts +25 -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 +125 -0
- package/src/plugin/channel-runtime-types.ts +1 -1
- package/src/plugin/inbound-acceptance.ts +18 -7
- package/src/plugin/sqlite-state.ts +59 -18
- package/src/plugin/state-store.ts +81 -20
|
@@ -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,90 @@ 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
|
+
// Only exact `/stop` is accepted; any other `/stop*` form is rejected.
|
|
124
|
+
const rawStopText = String(extracted.text || '')
|
|
125
|
+
.trim()
|
|
126
|
+
.toLowerCase();
|
|
127
|
+
if (rawStopText.startsWith('/stop')) {
|
|
128
|
+
if (isBncrStopCommandText(extracted.text)) {
|
|
129
|
+
return { handled: false };
|
|
130
|
+
}
|
|
131
|
+
// Non-exact `/stop` forms (extra args, @bot suffix, or other extension)
|
|
132
|
+
// are rejected immediately so they never reach the agent.
|
|
133
|
+
const rejectedRoute = buildBncrNativeCommandResolvedRoute({
|
|
134
|
+
api,
|
|
135
|
+
cfg,
|
|
136
|
+
channelId,
|
|
137
|
+
accountId,
|
|
138
|
+
peer,
|
|
139
|
+
resolvedAgentId,
|
|
140
|
+
});
|
|
141
|
+
const { baseSessionKey, sessionKey } = buildBncrNativeCommandSessionState({
|
|
142
|
+
parsed,
|
|
143
|
+
sessionAgentId: rejectedRoute.agentId || canonicalAgentId,
|
|
144
|
+
resolvedRoute: rejectedRoute,
|
|
145
|
+
});
|
|
146
|
+
logBncrNativeCommandSummary(
|
|
147
|
+
buildBncrNativeCommandSummary({
|
|
148
|
+
kind: 'stop',
|
|
149
|
+
command: rawStopText,
|
|
150
|
+
accountId,
|
|
151
|
+
to: formatDisplayScope(route),
|
|
152
|
+
msgId: msgId || null,
|
|
153
|
+
result: 'rejected',
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
await enqueueFromReply({
|
|
157
|
+
accountId,
|
|
158
|
+
sessionKey: baseSessionKey,
|
|
159
|
+
route,
|
|
160
|
+
payload: {
|
|
161
|
+
text: 'Only exact /stop is supported. Send /stop to stop.',
|
|
162
|
+
replyToId: msgId || undefined,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
return { handled: true, command: 'stop', sessionKey };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const whitelistBareCommand =
|
|
169
|
+
!parsed.isAdmin && parsed.peer.kind === 'direct'
|
|
170
|
+
? isBncrWhitelistBareCommandText(extracted.text)
|
|
171
|
+
: null;
|
|
172
|
+
let dispatchToOpenClaw =
|
|
173
|
+
whitelistBareCommand === 'whoami' ||
|
|
174
|
+
whitelistBareCommand === 'status' ||
|
|
175
|
+
whitelistBareCommand === 'model' ||
|
|
176
|
+
whitelistBareCommand === 'verbose';
|
|
177
|
+
const effectiveIsAdmin = whitelistBareCommand ? true : parsed.isAdmin;
|
|
178
|
+
let command: ReturnType<typeof parseBncrNativeCommand>;
|
|
179
|
+
if (whitelistBareCommand) {
|
|
180
|
+
// Private non-admin whitelist commands are temporarily elevated to admin
|
|
181
|
+
// for the local session-reset handlers or handed to the OpenClaw native
|
|
182
|
+
// parser. They never fall back to the agent so the agent cannot observe
|
|
183
|
+
// a conflicting identity.
|
|
184
|
+
const raw = String(extracted.text || '').trim();
|
|
185
|
+
command = { command: whitelistBareCommand, raw, body: raw, argsText: '' };
|
|
186
|
+
const argsTextStart = raw.indexOf(' ');
|
|
187
|
+
if (argsTextStart !== -1) {
|
|
188
|
+
command.argsText = raw.slice(argsTextStart + 1).trim();
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
command = parseBncrNativeCommand(
|
|
192
|
+
extracted.text,
|
|
193
|
+
resolveBncrNativeCommandParseOptions({
|
|
194
|
+
isAdmin: parsed.isAdmin,
|
|
195
|
+
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (!command) {
|
|
200
|
+
const unsupportedCommand = parseBncrUnsupportedDirectCommand(extracted.text);
|
|
201
|
+
// Unknown /bncr subcommands are rejected by bncr without agent fallback,
|
|
202
|
+
// for both admin and non-admin callers and in private or group chat.
|
|
203
|
+
if (unsupportedCommand?.command.startsWith('bncr ')) {
|
|
119
204
|
const rejectedRoute = buildBncrNativeCommandResolvedRoute({
|
|
120
205
|
api,
|
|
121
206
|
cfg,
|
|
@@ -133,7 +218,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
133
218
|
logBncrNativeCommandSummary(
|
|
134
219
|
buildBncrNativeCommandSummary({
|
|
135
220
|
kind: 'unsupported',
|
|
136
|
-
command:
|
|
221
|
+
command: unsupportedCommand.command,
|
|
137
222
|
accountId,
|
|
138
223
|
to: displayTo,
|
|
139
224
|
msgId: msgId || null,
|
|
@@ -145,20 +230,43 @@ export async function handleBncrNativeCommand(params: {
|
|
|
145
230
|
sessionKey: baseSessionKey,
|
|
146
231
|
route,
|
|
147
232
|
payload: {
|
|
148
|
-
text: `Unsupported
|
|
233
|
+
text: `Unsupported command: /${unsupportedCommand.command}`,
|
|
149
234
|
replyToId: msgId || undefined,
|
|
150
235
|
},
|
|
151
236
|
});
|
|
152
|
-
return { handled: true, command:
|
|
237
|
+
return { handled: true, command: unsupportedCommand.command, sessionKey };
|
|
238
|
+
}
|
|
239
|
+
// Non-bncr bare commands fall through to the OpenClaw parser. Admin
|
|
240
|
+
// callers may fall back to the agent; non-admin callers keep their
|
|
241
|
+
// original identity when they do.
|
|
242
|
+
|
|
243
|
+
// Admin bare OpenClaw native commands are routed to the OpenClaw parser
|
|
244
|
+
// and handled there, preventing a second agent turn from producing a
|
|
245
|
+
// duplicate reply. This mirrors the non-admin whitelist elevation path.
|
|
246
|
+
if (
|
|
247
|
+
!command &&
|
|
248
|
+
parsed.isAdmin &&
|
|
249
|
+
hasControlCommand(extracted.text, cfg as Parameters<typeof hasControlCommand>[1])
|
|
250
|
+
) {
|
|
251
|
+
const raw = String(extracted.text || '').trim();
|
|
252
|
+
command = { command: BNCR_OPENCLAW_NATIVE_COMMAND, raw, body: raw, argsText: '' };
|
|
253
|
+
const argsTextStart = raw.indexOf(' ');
|
|
254
|
+
if (argsTextStart !== -1) {
|
|
255
|
+
command.argsText = raw.slice(argsTextStart + 1).trim();
|
|
256
|
+
}
|
|
257
|
+
dispatchToOpenClaw = true;
|
|
153
258
|
}
|
|
154
259
|
}
|
|
155
260
|
if (!command) return { handled: false };
|
|
156
261
|
const nativeCommandDebugEnabled = resolveNativeCommandDebugEnabled({ cfg, channelId });
|
|
157
262
|
|
|
263
|
+
const displayCommand =
|
|
264
|
+
command.command === BNCR_OPENCLAW_NATIVE_COMMAND ? command.raw : command.command;
|
|
265
|
+
|
|
158
266
|
logBncrNativeCommandEvent(
|
|
159
267
|
'detected',
|
|
160
268
|
{
|
|
161
|
-
command:
|
|
269
|
+
command: displayCommand,
|
|
162
270
|
accountId,
|
|
163
271
|
to: formatDisplayScope(route),
|
|
164
272
|
msgId: msgId || null,
|
|
@@ -198,6 +306,9 @@ export async function handleBncrNativeCommand(params: {
|
|
|
198
306
|
agentId: resolvedRoute.agentId,
|
|
199
307
|
});
|
|
200
308
|
|
|
309
|
+
const dispatchOwnerAllowFrom =
|
|
310
|
+
dispatchToOpenClaw && senderIdForContext ? [senderIdForContext] : undefined;
|
|
311
|
+
|
|
201
312
|
const ctxPayload = await Promise.resolve(
|
|
202
313
|
createNativeCommandTurnContext({
|
|
203
314
|
api,
|
|
@@ -207,6 +318,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
207
318
|
peer,
|
|
208
319
|
resolvedRoute,
|
|
209
320
|
sessionKey,
|
|
321
|
+
ownerAllowFrom: dispatchOwnerAllowFrom,
|
|
210
322
|
displayTo,
|
|
211
323
|
originatingTo,
|
|
212
324
|
senderIdForContext,
|
|
@@ -223,9 +335,16 @@ export async function handleBncrNativeCommand(params: {
|
|
|
223
335
|
senderId: senderIdForContext,
|
|
224
336
|
});
|
|
225
337
|
|
|
226
|
-
const
|
|
338
|
+
const currentVerboseLevel = storePath
|
|
339
|
+
? ((await readBncrSessionEntry({ storePath, sessionKey }))?.verboseLevel as
|
|
340
|
+
| 'on'
|
|
341
|
+
| 'off'
|
|
342
|
+
| 'full'
|
|
343
|
+
| undefined)
|
|
344
|
+
: undefined;
|
|
345
|
+
const nativeVerbose = resolveBncrNativeVerboseCommand(command, currentVerboseLevel);
|
|
227
346
|
const nativeHelp = resolveBncrNativeHelpCommand(command, {
|
|
228
|
-
isAdmin:
|
|
347
|
+
isAdmin: effectiveIsAdmin,
|
|
229
348
|
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
230
349
|
});
|
|
231
350
|
const nativeWhoami = resolveBncrNativeWhoamiCommand({
|
|
@@ -236,7 +355,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
236
355
|
userId: parsed.userId,
|
|
237
356
|
userName: parsed.userName,
|
|
238
357
|
isGroup: parsed.isGroup,
|
|
239
|
-
isAdmin:
|
|
358
|
+
isAdmin: effectiveIsAdmin,
|
|
240
359
|
});
|
|
241
360
|
const nativeStatus = resolveBncrNativeStatusCommand({
|
|
242
361
|
command,
|
|
@@ -251,107 +370,16 @@ export async function handleBncrNativeCommand(params: {
|
|
|
251
370
|
command,
|
|
252
371
|
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
253
372
|
});
|
|
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) {
|
|
373
|
+
if (!dispatchToOpenClaw) {
|
|
374
|
+
if (nativeHelp) {
|
|
347
375
|
logBncrNativeCommandSummary(
|
|
348
376
|
buildBncrNativeCommandSummary({
|
|
349
|
-
kind:
|
|
377
|
+
kind: 'help',
|
|
350
378
|
command: command.command,
|
|
351
379
|
accountId,
|
|
352
380
|
to: displayTo,
|
|
353
381
|
msgId: msgId || null,
|
|
354
|
-
result: '
|
|
382
|
+
result: 'handled',
|
|
355
383
|
}),
|
|
356
384
|
);
|
|
357
385
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -361,63 +389,57 @@ export async function handleBncrNativeCommand(params: {
|
|
|
361
389
|
patch: sessionIdentityPatch,
|
|
362
390
|
});
|
|
363
391
|
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
392
|
+
await enqueueFromReply({
|
|
393
|
+
accountId,
|
|
394
|
+
sessionKey,
|
|
395
|
+
route,
|
|
396
|
+
payload: {
|
|
397
|
+
text: nativeHelp.text,
|
|
398
|
+
replyToId: msgId || undefined,
|
|
399
|
+
},
|
|
400
|
+
});
|
|
364
401
|
return { handled: true, command: command.command, sessionKey };
|
|
365
402
|
}
|
|
366
403
|
|
|
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,
|
|
404
|
+
if (nativeWhoami) {
|
|
405
|
+
logBncrNativeCommandSummary(
|
|
406
|
+
buildBncrNativeCommandSummary({
|
|
407
|
+
kind: 'whoami',
|
|
408
|
+
command: command.command,
|
|
409
|
+
accountId,
|
|
410
|
+
to: displayTo,
|
|
411
|
+
msgId: msgId || null,
|
|
412
|
+
result: 'handled',
|
|
413
|
+
}),
|
|
414
|
+
);
|
|
415
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
416
|
+
storePath,
|
|
417
|
+
sessionKey,
|
|
418
|
+
ctx: ctxPayload,
|
|
419
|
+
patch: sessionIdentityPatch,
|
|
420
|
+
});
|
|
421
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
422
|
+
await enqueueFromReply({
|
|
392
423
|
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
|
-
}
|
|
424
|
+
sessionKey,
|
|
425
|
+
route,
|
|
426
|
+
payload: {
|
|
427
|
+
text: nativeWhoami.text,
|
|
428
|
+
replyToId: msgId || undefined,
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
return { handled: true, command: command.command, sessionKey };
|
|
432
|
+
}
|
|
410
433
|
|
|
411
|
-
|
|
412
|
-
if (!parsed.isAdmin) {
|
|
434
|
+
if (nativeStatus) {
|
|
413
435
|
logBncrNativeCommandSummary(
|
|
414
436
|
buildBncrNativeCommandSummary({
|
|
415
|
-
kind: '
|
|
437
|
+
kind: 'status',
|
|
416
438
|
command: command.command,
|
|
417
439
|
accountId,
|
|
418
440
|
to: displayTo,
|
|
419
441
|
msgId: msgId || null,
|
|
420
|
-
result: '
|
|
442
|
+
result: 'handled',
|
|
421
443
|
}),
|
|
422
444
|
);
|
|
423
445
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -427,96 +449,258 @@ export async function handleBncrNativeCommand(params: {
|
|
|
427
449
|
patch: sessionIdentityPatch,
|
|
428
450
|
});
|
|
429
451
|
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
430
|
-
|
|
452
|
+
await enqueueFromReply({
|
|
453
|
+
accountId,
|
|
454
|
+
sessionKey,
|
|
455
|
+
route,
|
|
456
|
+
payload: {
|
|
457
|
+
text: nativeStatus.text,
|
|
458
|
+
replyToId: msgId || undefined,
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
return { handled: true, command: command.command, sessionKey };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (nativeSessionReset) {
|
|
465
|
+
const allowLocalSessionReset = parsed.peer.kind === 'direct' || effectiveIsAdmin;
|
|
466
|
+
if (!allowLocalSessionReset) {
|
|
467
|
+
logBncrNativeCommandSummary(
|
|
468
|
+
buildBncrNativeCommandSummary({
|
|
469
|
+
kind: nativeSessionReset.reason,
|
|
470
|
+
command: command.command,
|
|
471
|
+
accountId,
|
|
472
|
+
to: displayTo,
|
|
473
|
+
msgId: msgId || null,
|
|
474
|
+
result: 'rejected',
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
477
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
478
|
+
storePath,
|
|
479
|
+
sessionKey,
|
|
480
|
+
ctx: ctxPayload,
|
|
481
|
+
patch: sessionIdentityPatch,
|
|
482
|
+
});
|
|
483
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
484
|
+
return { handled: true, command: command.command, sessionKey };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const requestedAgentId = resolvedRoute.agentId || canonicalAgentId;
|
|
488
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
489
|
+
storePath,
|
|
490
|
+
sessionKey,
|
|
491
|
+
ctx: ctxPayload,
|
|
492
|
+
patch: sessionIdentityPatch,
|
|
493
|
+
});
|
|
494
|
+
let resetResult: { ok?: boolean } | undefined;
|
|
495
|
+
try {
|
|
496
|
+
resetResult = await performBncrGatewaySessionReset({
|
|
497
|
+
key: baseSessionKey,
|
|
498
|
+
reason: nativeSessionReset.reason,
|
|
499
|
+
...(requestedAgentId ? { agentId: requestedAgentId } : {}),
|
|
500
|
+
commandSource: 'bncr:native-command',
|
|
501
|
+
});
|
|
502
|
+
} catch (resetError) {
|
|
503
|
+
emitBncrLogLine(
|
|
504
|
+
'error',
|
|
505
|
+
`[bncr] native-command sessions.reset failed|key=${baseSessionKey}|err=${resetError instanceof Error ? resetError.message : String(resetError)}`,
|
|
506
|
+
);
|
|
431
507
|
await enqueueFromReply({
|
|
432
508
|
accountId,
|
|
433
|
-
sessionKey,
|
|
509
|
+
sessionKey: baseSessionKey,
|
|
434
510
|
route,
|
|
435
511
|
payload: {
|
|
436
|
-
text:
|
|
512
|
+
text: `Session reset failed: ${resetError instanceof Error ? resetError.message : String(resetError)}`,
|
|
437
513
|
replyToId: msgId || undefined,
|
|
438
514
|
},
|
|
439
515
|
});
|
|
516
|
+
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
440
517
|
}
|
|
441
|
-
|
|
518
|
+
if (resetResult?.ok === false) {
|
|
519
|
+
await enqueueFromReply({
|
|
520
|
+
accountId,
|
|
521
|
+
sessionKey: baseSessionKey,
|
|
522
|
+
route,
|
|
523
|
+
payload: {
|
|
524
|
+
text: 'Session reset was rejected by the gateway.',
|
|
525
|
+
replyToId: msgId || undefined,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
529
|
+
}
|
|
530
|
+
logBncrNativeCommandSummary(
|
|
531
|
+
buildBncrNativeCommandSummary({
|
|
532
|
+
kind: nativeSessionReset.reason,
|
|
533
|
+
command: command.command,
|
|
534
|
+
accountId,
|
|
535
|
+
to: displayTo,
|
|
536
|
+
msgId: msgId || null,
|
|
537
|
+
result: 'handled',
|
|
538
|
+
}),
|
|
539
|
+
);
|
|
540
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
541
|
+
await enqueueFromReply({
|
|
542
|
+
accountId,
|
|
543
|
+
sessionKey: baseSessionKey,
|
|
544
|
+
route,
|
|
545
|
+
payload: {
|
|
546
|
+
text: nativeSessionReset.text,
|
|
547
|
+
replyToId: msgId || undefined,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
return { handled: true, command: command.command, sessionKey: baseSessionKey };
|
|
442
551
|
}
|
|
443
552
|
|
|
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
|
-
}
|
|
553
|
+
if (nativeVerbose) {
|
|
554
|
+
if (!effectiveIsAdmin && parsed.peer.kind !== 'direct') {
|
|
555
|
+
logBncrNativeCommandSummary(
|
|
556
|
+
buildBncrNativeCommandSummary({
|
|
557
|
+
kind: 'verbose',
|
|
558
|
+
command: command.command,
|
|
559
|
+
accountId,
|
|
560
|
+
to: displayTo,
|
|
561
|
+
msgId: msgId || null,
|
|
562
|
+
result: 'rejected',
|
|
563
|
+
}),
|
|
564
|
+
);
|
|
565
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
566
|
+
storePath,
|
|
567
|
+
sessionKey,
|
|
568
|
+
ctx: ctxPayload,
|
|
569
|
+
patch: sessionIdentityPatch,
|
|
570
|
+
});
|
|
571
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
572
|
+
if (parsed.peer.kind !== 'group') {
|
|
573
|
+
await enqueueFromReply({
|
|
574
|
+
accountId,
|
|
575
|
+
sessionKey,
|
|
576
|
+
route,
|
|
577
|
+
payload: {
|
|
578
|
+
text: 'Admin permission required.',
|
|
579
|
+
replyToId: msgId || undefined,
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return { handled: true, command: command.command, sessionKey };
|
|
584
|
+
}
|
|
487
585
|
|
|
488
|
-
const sceneAdmin = parseSceneAdminCommand(command);
|
|
489
|
-
if (sceneAdmin.matched) {
|
|
490
|
-
if (!parsed.isAdmin) {
|
|
491
586
|
logBncrNativeCommandSummary(
|
|
492
587
|
buildBncrNativeCommandSummary({
|
|
493
|
-
kind: '
|
|
588
|
+
kind: 'verbose',
|
|
494
589
|
command: command.command,
|
|
495
590
|
accountId,
|
|
496
591
|
to: displayTo,
|
|
497
|
-
msgId: msgId
|
|
498
|
-
result: '
|
|
592
|
+
msgId: msgId || null,
|
|
593
|
+
result: 'handled',
|
|
499
594
|
}),
|
|
500
595
|
);
|
|
596
|
+
logBncrNativeCommandEvent(
|
|
597
|
+
'handled-verbose',
|
|
598
|
+
{
|
|
599
|
+
command: command.command,
|
|
600
|
+
accountId,
|
|
601
|
+
sessionKey,
|
|
602
|
+
to: displayTo,
|
|
603
|
+
msgId: msgId || null,
|
|
604
|
+
fallbackToAgent: false,
|
|
605
|
+
},
|
|
606
|
+
{ debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
|
|
607
|
+
);
|
|
501
608
|
await recordAndPatchBncrInboundSessionEntry({
|
|
502
609
|
storePath,
|
|
503
610
|
sessionKey,
|
|
504
611
|
ctx: ctxPayload,
|
|
505
|
-
patch:
|
|
612
|
+
patch: {
|
|
613
|
+
...sessionIdentityPatch,
|
|
614
|
+
...(nativeVerbose.verboseLevel ? { verboseLevel: nativeVerbose.verboseLevel } : {}),
|
|
615
|
+
},
|
|
506
616
|
});
|
|
617
|
+
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
507
618
|
await enqueueFromReply({
|
|
508
619
|
accountId,
|
|
509
620
|
sessionKey,
|
|
510
621
|
route,
|
|
511
622
|
payload: {
|
|
512
|
-
text:
|
|
623
|
+
text: nativeVerbose.text,
|
|
513
624
|
replyToId: msgId || undefined,
|
|
514
625
|
},
|
|
515
626
|
});
|
|
516
627
|
return { handled: true, command: command.command, sessionKey };
|
|
517
628
|
}
|
|
518
629
|
|
|
519
|
-
|
|
630
|
+
const sceneAdmin = parseSceneAdminCommand(command);
|
|
631
|
+
if (sceneAdmin.matched) {
|
|
632
|
+
const directSelfAdminCommand =
|
|
633
|
+
parsed.peer.kind === 'direct' &&
|
|
634
|
+
new Set(['history-help', 'history-limit', 'history-force', 'download-media']).has(
|
|
635
|
+
command.command,
|
|
636
|
+
);
|
|
637
|
+
if (!effectiveIsAdmin && !directSelfAdminCommand) {
|
|
638
|
+
logBncrNativeCommandSummary(
|
|
639
|
+
buildBncrNativeCommandSummary({
|
|
640
|
+
kind: 'scene-admin',
|
|
641
|
+
command: command.command,
|
|
642
|
+
accountId,
|
|
643
|
+
to: displayTo,
|
|
644
|
+
msgId: msgId ?? null,
|
|
645
|
+
result: 'rejected',
|
|
646
|
+
}),
|
|
647
|
+
);
|
|
648
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
649
|
+
storePath,
|
|
650
|
+
sessionKey,
|
|
651
|
+
ctx: ctxPayload,
|
|
652
|
+
patch: sessionIdentityPatch,
|
|
653
|
+
});
|
|
654
|
+
await enqueueFromReply({
|
|
655
|
+
accountId,
|
|
656
|
+
sessionKey,
|
|
657
|
+
route,
|
|
658
|
+
payload: {
|
|
659
|
+
text: 'Admin permission required.',
|
|
660
|
+
replyToId: msgId || undefined,
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
return { handled: true, command: command.command, sessionKey };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (!sceneAdmin.valid) {
|
|
667
|
+
logBncrNativeCommandSummary(
|
|
668
|
+
buildBncrNativeCommandSummary({
|
|
669
|
+
kind: 'scene-admin',
|
|
670
|
+
command: command.command,
|
|
671
|
+
accountId,
|
|
672
|
+
to: displayTo,
|
|
673
|
+
msgId: msgId ?? null,
|
|
674
|
+
result: 'rejected',
|
|
675
|
+
}),
|
|
676
|
+
);
|
|
677
|
+
await recordAndPatchBncrInboundSessionEntry({
|
|
678
|
+
storePath,
|
|
679
|
+
sessionKey,
|
|
680
|
+
ctx: ctxPayload,
|
|
681
|
+
patch: sessionIdentityPatch,
|
|
682
|
+
});
|
|
683
|
+
await enqueueFromReply({
|
|
684
|
+
accountId,
|
|
685
|
+
sessionKey,
|
|
686
|
+
route,
|
|
687
|
+
payload: {
|
|
688
|
+
text: sceneAdmin.text,
|
|
689
|
+
replyToId: msgId || undefined,
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
return { handled: true, command: command.command, sessionKey };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const outcome = executeSceneAdminCommand({
|
|
696
|
+
parsed,
|
|
697
|
+
command: sceneAdmin.command,
|
|
698
|
+
sceneRegistry,
|
|
699
|
+
defaultAdminAgentId,
|
|
700
|
+
defaultPublicAgentId,
|
|
701
|
+
now,
|
|
702
|
+
allowNonAdminSelfAdmin: !!directSelfAdminCommand,
|
|
703
|
+
});
|
|
520
704
|
logBncrNativeCommandSummary(
|
|
521
705
|
buildBncrNativeCommandSummary({
|
|
522
706
|
kind: 'scene-admin',
|
|
@@ -524,7 +708,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
524
708
|
accountId,
|
|
525
709
|
to: displayTo,
|
|
526
710
|
msgId: msgId ?? null,
|
|
527
|
-
result: 'rejected',
|
|
711
|
+
result: outcome.ok ? 'handled' : 'rejected',
|
|
528
712
|
}),
|
|
529
713
|
);
|
|
530
714
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -538,47 +722,12 @@ export async function handleBncrNativeCommand(params: {
|
|
|
538
722
|
sessionKey,
|
|
539
723
|
route,
|
|
540
724
|
payload: {
|
|
541
|
-
text:
|
|
725
|
+
text: outcome.text,
|
|
542
726
|
replyToId: msgId || undefined,
|
|
543
727
|
},
|
|
544
728
|
});
|
|
545
729
|
return { handled: true, command: command.command, sessionKey };
|
|
546
730
|
}
|
|
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
731
|
}
|
|
583
732
|
|
|
584
733
|
await recordAndPatchBncrInboundSessionEntry({
|
|
@@ -588,7 +737,11 @@ export async function handleBncrNativeCommand(params: {
|
|
|
588
737
|
patch: sessionIdentityPatch,
|
|
589
738
|
});
|
|
590
739
|
|
|
591
|
-
const
|
|
740
|
+
const dispatchCfgForElevated =
|
|
741
|
+
dispatchToOpenClaw && senderIdForContext
|
|
742
|
+
? mergeBncrOwnerAllowFromIntoConfig({ cfg, senderIdForContext })
|
|
743
|
+
: cfg;
|
|
744
|
+
const effectiveReply = buildBncrReplyConfig(dispatchCfgForElevated);
|
|
592
745
|
const channelPolicy = resolveBncrChannelPolicy(cfg?.channels?.bncr || {});
|
|
593
746
|
const pinnedMainDmOwner =
|
|
594
747
|
peer.kind === 'direct'
|
|
@@ -691,13 +844,35 @@ export async function handleBncrNativeCommand(params: {
|
|
|
691
844
|
});
|
|
692
845
|
|
|
693
846
|
if (!responded) {
|
|
847
|
+
// Self-service commands must never fall back to the agent — doing so
|
|
848
|
+
// would let the agent see a message whose author identity was
|
|
849
|
+
// temporarily elevated, causing role confusion.
|
|
850
|
+
if (BNCR_SELF_SERVICE_COMMANDS.has(command.command)) {
|
|
851
|
+
emitBncrLogLine(
|
|
852
|
+
'warn',
|
|
853
|
+
`[bncr] self-service command=${displayCommand} produced no payload; refusing agent fallback`,
|
|
854
|
+
);
|
|
855
|
+
await enqueueFromReply({
|
|
856
|
+
accountId,
|
|
857
|
+
sessionKey,
|
|
858
|
+
route,
|
|
859
|
+
payload: {
|
|
860
|
+
text: `Command /${command.command} failed.`,
|
|
861
|
+
replyToId: msgId || undefined,
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
return buildNativeCommandHandledResult({
|
|
865
|
+
command: command.command,
|
|
866
|
+
sessionKey,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
694
869
|
logBncrNativeCommandSummary(
|
|
695
|
-
`fallback command=${
|
|
870
|
+
`fallback command=${displayCommand}|accountId=${accountId}|to=${displayTo}|msgId=${msgId || '-'}|reason=no-payload`,
|
|
696
871
|
);
|
|
697
872
|
logBncrNativeCommandEvent(
|
|
698
873
|
'no-payload-fallback-to-agent',
|
|
699
874
|
{
|
|
700
|
-
command:
|
|
875
|
+
command: displayCommand,
|
|
701
876
|
accountId,
|
|
702
877
|
sessionKey,
|
|
703
878
|
to: displayTo,
|