@xmoxmo/bncr 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/messaging/inbound/commands.ts +75 -65
- package/src/messaging/inbound/conversation-history.ts +28 -1
- package/src/plugin/config.ts +18 -0
- package/src/plugin/inbound-handlers.ts +11 -5
- package/src/plugin/sqlite-state.ts +6 -23
- package/src/plugin/state-store.ts +3 -30
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { dispatchOpenClawReplyWithBufferedBlockDispatcher } from '../../openclaw/reply-runtime.ts';
|
|
12
12
|
import { resolveOpenClawAgentRoute } from '../../openclaw/routing-runtime.ts';
|
|
13
13
|
import { performBncrGatewaySessionReset } from '../../openclaw/session-reset-runtime.ts';
|
|
14
|
+
import { addNativeElevation, removeNativeElevation } from '../../plugin/config.ts';
|
|
14
15
|
import { mergeBncrOwnerAllowFromIntoConfig } from './command-owner.ts';
|
|
15
16
|
import type {
|
|
16
17
|
BncrEnqueueFromReply,
|
|
@@ -774,74 +775,83 @@ export async function handleBncrNativeCommand(params: {
|
|
|
774
775
|
},
|
|
775
776
|
{ debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
|
|
776
777
|
);
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
},
|
|
813
|
-
runDispatch: async () => {
|
|
814
|
-
await sessionMetaBarrier.wait();
|
|
815
|
-
return dispatchOpenClawReplyWithBufferedBlockDispatcher(api, {
|
|
816
|
-
ctx: ctxPayload,
|
|
817
|
-
cfg: effectiveReply.replyCfg,
|
|
818
|
-
dispatcherOptions: {
|
|
819
|
-
deliver: createNativeCommandReplyDeliverer({
|
|
820
|
-
command: command.command,
|
|
821
|
-
accountId,
|
|
822
|
-
sessionKey,
|
|
823
|
-
to: displayTo,
|
|
824
|
-
msgId: msgId || undefined,
|
|
825
|
-
effectiveReply,
|
|
826
|
-
route,
|
|
827
|
-
enqueueFromReply,
|
|
828
|
-
nativeCommandDebugEnabled,
|
|
829
|
-
onResponded: () => responded,
|
|
830
|
-
markResponded: () => {
|
|
831
|
-
responded = true;
|
|
832
|
-
},
|
|
833
|
-
}),
|
|
778
|
+
if (dispatchToOpenClaw && senderIdForContext) {
|
|
779
|
+
addNativeElevation(senderIdForContext);
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
await resolveBncrChannelInboundRuntime(api).run({
|
|
783
|
+
channel: channelId,
|
|
784
|
+
accountId,
|
|
785
|
+
raw: parsed,
|
|
786
|
+
adapter: {
|
|
787
|
+
ingest: () => ({
|
|
788
|
+
id: msgId ?? `${displayTo}:${Date.now()}`,
|
|
789
|
+
timestamp: Date.now(),
|
|
790
|
+
rawText: body,
|
|
791
|
+
textForAgent: ctxPayload.BodyForAgent,
|
|
792
|
+
textForCommands: ctxPayload.CommandBody,
|
|
793
|
+
raw: parsed,
|
|
794
|
+
}),
|
|
795
|
+
resolveTurn: () => {
|
|
796
|
+
const sessionMetaBarrier = createBncrSessionMetaTaskBarrier();
|
|
797
|
+
return {
|
|
798
|
+
channel: channelId,
|
|
799
|
+
accountId,
|
|
800
|
+
routeSessionKey: resolvedRoute.sessionKey,
|
|
801
|
+
storePath,
|
|
802
|
+
ctxPayload,
|
|
803
|
+
recordInboundSession: wrapBncrInboundRecordSessionLabelCorrection({
|
|
804
|
+
recordInboundSession: recordBncrInboundSession as (
|
|
805
|
+
...args: unknown[]
|
|
806
|
+
) => Promise<unknown> | unknown,
|
|
807
|
+
expectedPatch: sessionIdentityPatch,
|
|
808
|
+
}),
|
|
809
|
+
record: {
|
|
810
|
+
updateLastRoute,
|
|
811
|
+
onRecordError: (err: unknown) => {
|
|
812
|
+
buildNativeCommandRecordErrorLogger(err);
|
|
834
813
|
},
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
shouldEmitToolResult: effectiveReply.allowTool ? () => true : undefined,
|
|
814
|
+
trackSessionMetaTask: (task: Promise<unknown>) => {
|
|
815
|
+
sessionMetaBarrier.track(task);
|
|
838
816
|
},
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
817
|
+
},
|
|
818
|
+
runDispatch: async () => {
|
|
819
|
+
await sessionMetaBarrier.wait();
|
|
820
|
+
return dispatchOpenClawReplyWithBufferedBlockDispatcher(api, {
|
|
821
|
+
ctx: ctxPayload,
|
|
822
|
+
cfg: effectiveReply.replyCfg,
|
|
823
|
+
dispatcherOptions: {
|
|
824
|
+
deliver: createNativeCommandReplyDeliverer({
|
|
825
|
+
command: command.command,
|
|
826
|
+
accountId,
|
|
827
|
+
sessionKey,
|
|
828
|
+
to: displayTo,
|
|
829
|
+
msgId: msgId || undefined,
|
|
830
|
+
effectiveReply,
|
|
831
|
+
route,
|
|
832
|
+
enqueueFromReply,
|
|
833
|
+
nativeCommandDebugEnabled,
|
|
834
|
+
onResponded: () => responded,
|
|
835
|
+
markResponded: () => {
|
|
836
|
+
responded = true;
|
|
837
|
+
},
|
|
838
|
+
}),
|
|
839
|
+
},
|
|
840
|
+
replyOptions: {
|
|
841
|
+
disableBlockStreaming: !effectiveReply.blockStreaming,
|
|
842
|
+
shouldEmitToolResult: effectiveReply.allowTool ? () => true : undefined,
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
},
|
|
846
|
+
};
|
|
847
|
+
},
|
|
842
848
|
},
|
|
843
|
-
}
|
|
844
|
-
}
|
|
849
|
+
});
|
|
850
|
+
} finally {
|
|
851
|
+
if (dispatchToOpenClaw && senderIdForContext && !parsed.isAdmin) {
|
|
852
|
+
removeNativeElevation(senderIdForContext);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
845
855
|
|
|
846
856
|
if (!responded) {
|
|
847
857
|
// Self-service commands must never fall back to the agent — doing so
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type HistoryEntry,
|
|
6
6
|
type HistoryMediaEntry,
|
|
7
7
|
} from 'openclaw/plugin-sdk/reply-history';
|
|
8
|
-
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
8
|
+
import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../../core/accounts.ts';
|
|
9
9
|
import { formatOpenClawAgentEnvelope } from '../../openclaw/reply-runtime.ts';
|
|
10
10
|
import type { BncrInboundApi } from './contracts.ts';
|
|
11
11
|
import type { ParsedInbound } from './dispatch-prep.ts';
|
|
@@ -121,6 +121,33 @@ export function buildBncrConversationHistoryKeyFromRoute(args: {
|
|
|
121
121
|
return null;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Migrate legacy scene-only history keys (`tgBot:10001`) to the current
|
|
126
|
+
* account-scoped format (`Primary:tgBot:10001`). Keys that already carry an
|
|
127
|
+
* accountId prefix are returned unchanged.
|
|
128
|
+
*/
|
|
129
|
+
export function normalizePersistedHistoryKey(key: string): string {
|
|
130
|
+
const raw = String(key || '').trim();
|
|
131
|
+
if (!raw) return raw;
|
|
132
|
+
const firstColon = raw.indexOf(':');
|
|
133
|
+
const secondColon = raw.indexOf(':', firstColon + 1);
|
|
134
|
+
if (firstColon <= 0 || secondColon < 0) return `${BNCR_DEFAULT_ACCOUNT_ID}:${raw}`;
|
|
135
|
+
return `${normalizeAccountId(raw.slice(0, firstColon))}:${raw.slice(firstColon + 1)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function resolvePersistedHistoryAccountId(key: string): string {
|
|
139
|
+
const normalized = normalizePersistedHistoryKey(key);
|
|
140
|
+
const separator = normalized.indexOf(':');
|
|
141
|
+
return normalizeAccountId(separator > 0 ? normalized.slice(0, separator) : undefined);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function resolvePersistedHistoryKey(key: string, accountId: string): string {
|
|
145
|
+
const normalized = normalizePersistedHistoryKey(key);
|
|
146
|
+
const separator = normalized.indexOf(':');
|
|
147
|
+
const sceneKey = separator > 0 ? normalized.slice(separator + 1) : normalized;
|
|
148
|
+
return `${normalizeAccountId(accountId)}:${sceneKey}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
/**
|
|
125
152
|
* Extract the scene key (platform:userId or platform:groupId) from a history
|
|
126
153
|
* key that may contain an accountId prefix. Legacy scene-only keys pass
|
package/src/plugin/config.ts
CHANGED
|
@@ -7,6 +7,17 @@ import {
|
|
|
7
7
|
import { resolveBncrChannelPolicy } from '../core/policy.ts';
|
|
8
8
|
import { setOpenClawAccountEnabledInConfigSection } from '../openclaw/sdk-helpers.ts';
|
|
9
9
|
|
|
10
|
+
// 临时提权集合:非管理员用户执行原生命令时动态注入,命令完成后移除
|
|
11
|
+
const pendingNativeElevation = new Set<string>();
|
|
12
|
+
|
|
13
|
+
export function addNativeElevation(senderId: string): void {
|
|
14
|
+
if (senderId) pendingNativeElevation.add(senderId);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function removeNativeElevation(senderId: string): void {
|
|
18
|
+
if (senderId) pendingNativeElevation.delete(senderId);
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
type BncrConfigSurfaceRoot = {
|
|
11
22
|
channels?: {
|
|
12
23
|
[CHANNEL_ID]?: {
|
|
@@ -53,4 +64,11 @@ export const BNCR_CONFIG_SURFACE = {
|
|
|
53
64
|
configured: true,
|
|
54
65
|
};
|
|
55
66
|
},
|
|
67
|
+
resolveAllowFrom: ({ cfg }: { cfg: BncrConfigSurfaceRoot; accountId?: string | null }) => {
|
|
68
|
+
const policy = resolveBncrChannelPolicy(cfg?.channels?.[CHANNEL_ID] || {});
|
|
69
|
+
const base = policy.allowFrom;
|
|
70
|
+
if (pendingNativeElevation.size === 0) return base.length ? base : undefined;
|
|
71
|
+
const merged = [...base, ...pendingNativeElevation];
|
|
72
|
+
return merged.length ? merged : undefined;
|
|
73
|
+
},
|
|
56
74
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
|
|
2
|
+
import { runDetachedWebhookWork } from 'openclaw/plugin-sdk/webhook-request-guards';
|
|
2
3
|
import { buildCanonicalBncrSessionKey } from '../core/targets.ts';
|
|
3
4
|
import type { BncrConnection, BncrRoute } from '../core/types.ts';
|
|
4
5
|
import type { BncrConversationHistoryMap } from '../messaging/inbound/conversation-history.ts';
|
|
@@ -171,6 +172,7 @@ export type BncrInboundHandlersRuntime = {
|
|
|
171
172
|
defaultPublicAgentId: string;
|
|
172
173
|
now: () => number;
|
|
173
174
|
}) => Promise<unknown>;
|
|
175
|
+
runInboundDetached?: (run: () => Promise<unknown>) => Promise<unknown>;
|
|
174
176
|
};
|
|
175
177
|
|
|
176
178
|
export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRuntime, 'respond'>) {
|
|
@@ -326,8 +328,12 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
|
|
|
326
328
|
);
|
|
327
329
|
runtime.flushOnInboundAccepted(accountId);
|
|
328
330
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
+
const runInboundDetached = runtime.runInboundDetached ?? runDetachedWebhookWork;
|
|
332
|
+
// Ack-first handlers must run follow-up work on an independent admitted
|
|
333
|
+
// root. Otherwise the host releases this request admission after respond,
|
|
334
|
+
// and later native/agent queue submissions are mistaken for draining.
|
|
335
|
+
void runInboundDetached(async () => {
|
|
336
|
+
await runtime.dispatchInbound({
|
|
331
337
|
cfg,
|
|
332
338
|
parsed,
|
|
333
339
|
canonicalAgentId,
|
|
@@ -340,10 +346,10 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
|
|
|
340
346
|
defaultAdminAgentId,
|
|
341
347
|
defaultPublicAgentId: runtime.defaultPublicAgentId(),
|
|
342
348
|
now: runtime.now,
|
|
343
|
-
})
|
|
344
|
-
.catch((err) => {
|
|
345
|
-
runtime.logError('inbound', `process failed: ${String(err)}`, { debugOnly: true });
|
|
346
349
|
});
|
|
350
|
+
}).catch((err) => {
|
|
351
|
+
runtime.logError('inbound', `process failed: ${String(err)}`, { debugOnly: true });
|
|
352
|
+
});
|
|
347
353
|
},
|
|
348
354
|
};
|
|
349
355
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeAccountId } from '../core/accounts.ts';
|
|
4
4
|
import {
|
|
5
5
|
dumpRegisterDriftSnapshot,
|
|
6
6
|
normalizeRegisterDriftSnapshot,
|
|
7
7
|
type RegisterDriftSnapshot,
|
|
8
8
|
} from '../core/register-trace.ts';
|
|
9
9
|
import type { BncrRoute, OutboxEntry } from '../core/types.ts';
|
|
10
|
+
import {
|
|
11
|
+
normalizePersistedHistoryKey,
|
|
12
|
+
resolvePersistedHistoryAccountId,
|
|
13
|
+
resolvePersistedHistoryKey,
|
|
14
|
+
} from '../messaging/inbound/conversation-history.ts';
|
|
10
15
|
import type {
|
|
11
16
|
BncrGroupReplyMode,
|
|
12
17
|
BncrPersistedAccountTimestamp,
|
|
@@ -792,28 +797,6 @@ function deriveHistoryKeyFromRoute(args: {
|
|
|
792
797
|
return null;
|
|
793
798
|
}
|
|
794
799
|
|
|
795
|
-
function normalizePersistedHistoryKey(key: string): string {
|
|
796
|
-
const raw = String(key || '').trim();
|
|
797
|
-
if (!raw) return raw;
|
|
798
|
-
const firstColon = raw.indexOf(':');
|
|
799
|
-
const secondColon = raw.indexOf(':', firstColon + 1);
|
|
800
|
-
if (firstColon <= 0 || secondColon < 0) return `${BNCR_DEFAULT_ACCOUNT_ID}:${raw}`;
|
|
801
|
-
return `${normalizeAccountId(raw.slice(0, firstColon))}:${raw.slice(firstColon + 1)}`;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function resolvePersistedHistoryAccountId(key: string): string {
|
|
805
|
-
const normalized = normalizePersistedHistoryKey(key);
|
|
806
|
-
const separator = normalized.indexOf(':');
|
|
807
|
-
return normalizeAccountId(separator > 0 ? normalized.slice(0, separator) : undefined);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function resolvePersistedHistoryKey(key: string, accountId: string): string {
|
|
811
|
-
const normalized = normalizePersistedHistoryKey(key);
|
|
812
|
-
const separator = normalized.indexOf(':');
|
|
813
|
-
const sceneKey = separator > 0 ? normalized.slice(separator + 1) : normalized;
|
|
814
|
-
return `${normalizeAccountId(accountId)}:${sceneKey}`;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
800
|
function parseMediaJson(value: string) {
|
|
818
801
|
try {
|
|
819
802
|
const parsed = JSON.parse(value ?? '[]');
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { readFile, rename } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, join } from 'node:path';
|
|
4
|
-
import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
|
|
5
4
|
import {
|
|
6
5
|
dumpRegisterDriftSnapshot,
|
|
7
6
|
normalizeRegisterDriftSnapshot,
|
|
@@ -11,9 +10,12 @@ import {
|
|
|
11
10
|
type BncrConversationHistoryMap,
|
|
12
11
|
buildBncrBotReplyMessageId,
|
|
13
12
|
buildBncrConversationHistoryKeyFromRoute,
|
|
13
|
+
normalizePersistedHistoryKey,
|
|
14
14
|
resetConversationHistoryVersions,
|
|
15
15
|
resolveBncrHistoryLimit,
|
|
16
16
|
resolveBncrSceneKeyFromHistoryKey,
|
|
17
|
+
resolvePersistedHistoryAccountId,
|
|
18
|
+
resolvePersistedHistoryKey,
|
|
17
19
|
} from '../messaging/inbound/conversation-history.ts';
|
|
18
20
|
import {
|
|
19
21
|
getConversationHistorySerialOwner,
|
|
@@ -53,35 +55,6 @@ import type {
|
|
|
53
55
|
const GROUP_REPLY_MODES = new Set<BncrGroupReplyMode>(['admin', 'mention', 'hybrid', 'all']);
|
|
54
56
|
const DEFAULT_PERSISTED_CONVERSATION_HISTORY_LIMIT = 50;
|
|
55
57
|
|
|
56
|
-
/**
|
|
57
|
-
* Migrate legacy scene-only history keys (`tgBot:10001`) to the current
|
|
58
|
-
* account-scoped format (`Primary:tgBot:10001`). Keys that already carry an
|
|
59
|
-
* accountId prefix are returned unchanged.
|
|
60
|
-
*/
|
|
61
|
-
function normalizePersistedHistoryKey(key: string): string {
|
|
62
|
-
const raw = String(key || '').trim();
|
|
63
|
-
if (!raw) return raw;
|
|
64
|
-
const firstColon = raw.indexOf(':');
|
|
65
|
-
const secondColon = raw.indexOf(':', firstColon + 1);
|
|
66
|
-
if (firstColon <= 0 || secondColon < 0) return `${BNCR_DEFAULT_ACCOUNT_ID}:${raw}`;
|
|
67
|
-
return `${normalizeAccountId(raw.slice(0, firstColon))}:${raw.slice(firstColon + 1)}`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function resolvePersistedHistoryAccountId(key: string): string {
|
|
71
|
-
const normalized = normalizePersistedHistoryKey(key);
|
|
72
|
-
const separator = normalized.indexOf(':');
|
|
73
|
-
return separator > 0
|
|
74
|
-
? normalized.slice(0, separator).trim() || BNCR_DEFAULT_ACCOUNT_ID
|
|
75
|
-
: BNCR_DEFAULT_ACCOUNT_ID;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function resolvePersistedHistoryKey(key: string, accountId: string): string {
|
|
79
|
-
const normalized = normalizePersistedHistoryKey(key);
|
|
80
|
-
const separator = normalized.indexOf(':');
|
|
81
|
-
const sceneKey = separator > 0 ? normalized.slice(separator + 1) : normalized;
|
|
82
|
-
return `${normalizeAccountId(accountId)}:${sceneKey}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
58
|
type BncrPersistedStateStoreInput = {
|
|
86
59
|
outbox?: unknown;
|
|
87
60
|
deadLetter?: unknown;
|