@xmoxmo/bncr 0.6.6 → 0.6.8
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/dist/index.js +1386 -206
- package/index.ts +291 -131
- package/package.json +1 -1
- package/scripts/check-pack.mjs +2 -0
- package/scripts/selfcheck.mjs +2 -0
- package/src/bootstrap/channel-plugin-runtime.ts +28 -7
- package/src/bootstrap/register-lifecycle.ts +492 -0
- package/src/bootstrap/register-runtime-gateway.ts +64 -3
- package/src/bootstrap/register-runtime-helpers.ts +8 -44
- package/src/bootstrap/register-runtime-observation.ts +170 -0
- package/src/bootstrap/register-runtime-singleton.ts +44 -40
- package/src/bootstrap/register-runtime.ts +918 -15
- package/src/channel.ts +25 -2
- package/src/messaging/inbound/commands.ts +75 -65
- package/src/messaging/inbound/conversation-history.ts +28 -1
- package/src/plugin/bridge-lifecycle.ts +13 -2
- 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/src/channel.ts
CHANGED
|
@@ -348,6 +348,7 @@ class BncrBridgeRuntime {
|
|
|
348
348
|
// 内置健康/回归计数(替代独立脚本)
|
|
349
349
|
private startedAt = now();
|
|
350
350
|
private stopped = false;
|
|
351
|
+
private serviceStarted = false;
|
|
351
352
|
private connectEventsByAccount = new Map<string, number>();
|
|
352
353
|
private inboundEventsByAccount = new Map<string, number>();
|
|
353
354
|
private activityEventsByAccount = new Map<string, number>();
|
|
@@ -840,8 +841,21 @@ class BncrBridgeRuntime {
|
|
|
840
841
|
return BNCR_DEBUG_VERBOSE;
|
|
841
842
|
}
|
|
842
843
|
|
|
843
|
-
|
|
844
|
-
|
|
844
|
+
getRuntimeObservation() {
|
|
845
|
+
const activeChannelAccounts = this.channelAccountWorkers.size;
|
|
846
|
+
return {
|
|
847
|
+
serviceRunning: this.serviceStarted,
|
|
848
|
+
channelRunning: activeChannelAccounts > 0,
|
|
849
|
+
activeChannelAccounts,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
startService = async (
|
|
854
|
+
ctx: OpenClawPluginServiceContext,
|
|
855
|
+
debug?: boolean,
|
|
856
|
+
isStartCurrent?: () => boolean,
|
|
857
|
+
) => {
|
|
858
|
+
const started = await startBncrBridgeService(
|
|
845
859
|
{
|
|
846
860
|
bridgeId: this.bridgeId,
|
|
847
861
|
setStopped: (value) => {
|
|
@@ -865,11 +879,19 @@ class BncrBridgeRuntime {
|
|
|
865
879
|
(((cfg as BncrChannelConfigRoot | null | undefined)?.channels || null)?.[CHANNEL_ID] as
|
|
866
880
|
| Record<string, unknown>
|
|
867
881
|
| undefined) || {},
|
|
882
|
+
resumeOutboxDrain: () => {
|
|
883
|
+
if (this.outbox.size === 0 || this.connections.size === 0) return;
|
|
884
|
+
this.schedulePushDrain(0);
|
|
885
|
+
},
|
|
886
|
+
isStartCurrent,
|
|
868
887
|
},
|
|
869
888
|
ctx,
|
|
870
889
|
debug,
|
|
871
890
|
);
|
|
891
|
+
if (!started || this.stopped || !(isStartCurrent?.() ?? true)) return false;
|
|
872
892
|
this.startHistoryShardWorker();
|
|
893
|
+
this.serviceStarted = true;
|
|
894
|
+
return true;
|
|
873
895
|
};
|
|
874
896
|
|
|
875
897
|
stopService = async () => {
|
|
@@ -887,6 +909,7 @@ class BncrBridgeRuntime {
|
|
|
887
909
|
}
|
|
888
910
|
|
|
889
911
|
private cleanupRuntimeWaitersAndTimers(reason: string) {
|
|
912
|
+
this.serviceStarted = false;
|
|
890
913
|
this.stopHistoryShardWorker(reason);
|
|
891
914
|
this.clientRpcRuntime.shutdown();
|
|
892
915
|
cleanupBncrBridgeRuntime(
|
|
@@ -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
|
|
@@ -59,13 +59,15 @@ export function startBncrBridgeService(
|
|
|
59
59
|
getChannelConfigRoot: (
|
|
60
60
|
cfg: BncrChannelConfigRoot,
|
|
61
61
|
) => BncrChannelPolicyConfig | null | undefined;
|
|
62
|
+
resumeOutboxDrain?: () => void;
|
|
63
|
+
isStartCurrent?: () => boolean;
|
|
62
64
|
},
|
|
63
65
|
ctx: OpenClawPluginServiceContext,
|
|
64
66
|
debug?: boolean,
|
|
65
67
|
) {
|
|
66
|
-
runtime.setStopped(false);
|
|
67
68
|
runtime.setStatePath(path.join(ctx.stateDir, 'bncr-bridge-state.json'));
|
|
68
69
|
return (async () => {
|
|
70
|
+
const isStartCurrent = () => runtime.isStartCurrent?.() ?? true;
|
|
69
71
|
try {
|
|
70
72
|
const cfg = runtime.getRuntimeConfig();
|
|
71
73
|
runtime.initializeCanonicalAgentId(cfg);
|
|
@@ -76,8 +78,10 @@ export function startBncrBridgeService(
|
|
|
76
78
|
// ignore startup canonical agent initialization errors
|
|
77
79
|
}
|
|
78
80
|
await runtime.loadState();
|
|
81
|
+
if (!isStartCurrent()) return false;
|
|
79
82
|
if (process.env.BNCR_SQLITE_CUTOVER === '1' && runtime.cutoverToSqlite) {
|
|
80
83
|
const cutover = await runtime.cutoverToSqlite();
|
|
84
|
+
if (!isStartCurrent()) return false;
|
|
81
85
|
runtime.logInfo(
|
|
82
86
|
'sqlite',
|
|
83
87
|
`cutover completed backup=${cutover.backupPath || 'none'} storeMode=${cutover.storeMode}`,
|
|
@@ -85,6 +89,8 @@ export function startBncrBridgeService(
|
|
|
85
89
|
}
|
|
86
90
|
if (typeof debug === 'boolean') runtime.setDebugFlag(debug);
|
|
87
91
|
await runtime.refreshDebugFlagFromConfig({ forceLog: true });
|
|
92
|
+
if (!isStartCurrent()) return false;
|
|
93
|
+
runtime.setStopped(false);
|
|
88
94
|
const bootDiag = runtime.buildIntegratedDiagnostics(BNCR_DEFAULT_ACCOUNT_ID);
|
|
89
95
|
runtime.logInfo(
|
|
90
96
|
'startup',
|
|
@@ -95,7 +101,12 @@ export function startBncrBridgeService(
|
|
|
95
101
|
`service started bridge=${runtime.bridgeId} diag.ok=${bootDiag.regression.ok} routes=${bootDiag.regression.totalKnownRoutes} pending=${bootDiag.health.pending} dead=${bootDiag.health.deadLetter}`,
|
|
96
102
|
{ debugOnly: true },
|
|
97
103
|
);
|
|
98
|
-
|
|
104
|
+
runtime.resumeOutboxDrain?.();
|
|
105
|
+
return true;
|
|
106
|
+
})().catch((error) => {
|
|
107
|
+
runtime.setStopped(true);
|
|
108
|
+
throw error;
|
|
109
|
+
});
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
export function cleanupBncrBridgeRuntime(
|
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;
|