@xmoxmo/bncr 0.6.2 → 0.6.4
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 +65 -1
- package/package.json +1 -1
- package/scripts/check-pack.mjs +3 -0
- package/scripts/selfcheck.mjs +3 -0
- package/src/channel.ts +134 -7
- package/src/messaging/inbound/commands.ts +62 -26
- package/src/messaging/inbound/conversation-history-serial.ts +258 -0
- package/src/messaging/inbound/conversation-history.ts +174 -60
- package/src/messaging/inbound/dispatch.ts +557 -137
- package/src/messaging/inbound/history-shard-worker.ts +180 -0
- package/src/messaging/inbound/native-command.ts +89 -33
- package/src/messaging/inbound/outbound-replay-cache.ts +128 -19
- package/src/messaging/inbound/reply-dispatch.ts +7 -6
- package/src/messaging/inbound/scene-admin.ts +17 -12
- package/src/messaging/inbound/turn-context.ts +2 -9
- package/src/plugin/bridge-lifecycle.ts +8 -0
- package/src/plugin/bridge-outbox-facade.ts +6 -1
- package/src/plugin/channel-runtime-constants.ts +2 -0
- package/src/plugin/outbox-drain-post-push.ts +3 -0
- package/src/plugin/sqlite-state.ts +2109 -0
- package/src/plugin/state-store.ts +704 -46
- package/src/plugin/state-transient-runtime-group.ts +14 -0
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
BncrSceneRecord,
|
|
4
4
|
BncrSceneStatus,
|
|
5
5
|
} from '../../plugin/channel-runtime-types.ts';
|
|
6
|
+
import { resolveBncrHistoryLimit } from './conversation-history.ts';
|
|
6
7
|
import type { ParsedInbound } from './dispatch-prep.ts';
|
|
7
8
|
import type { NativeCommand } from './native-command.ts';
|
|
8
9
|
|
|
@@ -52,9 +53,9 @@ export const HISTORY_HELP_TEXT = [
|
|
|
52
53
|
'📋 Bncr Conversation History Configuration',
|
|
53
54
|
'',
|
|
54
55
|
'Commands:',
|
|
55
|
-
' • /bncr history-limit <number
|
|
56
|
-
' Set history limit (default: 50)',
|
|
57
|
-
' • /bncr history-force on|off [<SceneId>]',
|
|
56
|
+
' • /bncr history-limit [<number>|clear] [<SceneId>]',
|
|
57
|
+
' Set history limit (default: 50, minimum: 2)',
|
|
58
|
+
' • /bncr history-force on|off|clear [<SceneId>]',
|
|
58
59
|
' When on, overflow triggers auto context at limit (default: on)',
|
|
59
60
|
' When off, oldest messages trim silently without context',
|
|
60
61
|
].join('\n');
|
|
@@ -258,7 +259,7 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
|
|
|
258
259
|
return {
|
|
259
260
|
matched: true,
|
|
260
261
|
valid: false,
|
|
261
|
-
text: 'Usage: /bncr history-limit [<number
|
|
262
|
+
text: 'Usage: /bncr history-limit [<number>|clear] [<sceneKey>]',
|
|
262
263
|
};
|
|
263
264
|
case 'history-force':
|
|
264
265
|
if (args.length === 0) {
|
|
@@ -353,12 +354,11 @@ function formatSceneDetailsLine(scene: BncrSceneRecord): string {
|
|
|
353
354
|
scene.kind === 'group' ? scene.groupId || scene.sceneKey : scene.userId || scene.sceneKey;
|
|
354
355
|
const namePart = scene.kind === 'group' ? scene.groupName || '' : scene.userName || '';
|
|
355
356
|
const labelPart = namePart ? ` name=${namePart}` : '';
|
|
356
|
-
const historyLimitVal = scene.historyLimit;
|
|
357
|
+
const historyLimitVal = resolveBncrHistoryLimit(scene.historyLimit);
|
|
357
358
|
const historyForceVal = scene.historyForce;
|
|
358
|
-
const hasNonDefaultHistory =
|
|
359
|
-
(typeof historyLimitVal === 'number' && historyLimitVal !== 50) || historyForceVal === false;
|
|
359
|
+
const hasNonDefaultHistory = historyLimitVal !== 50 || historyForceVal === false;
|
|
360
360
|
const historyPart = hasNonDefaultHistory
|
|
361
|
-
? ` historyLimit=${historyLimitVal
|
|
361
|
+
? ` historyLimit=${historyLimitVal} autoFlush=${historyForceVal !== false ? 'on' : 'off'}`
|
|
362
362
|
: '';
|
|
363
363
|
return ` Details: status=${scene.status} id=${idPart}${labelPart}${historyPart}`;
|
|
364
364
|
}
|
|
@@ -496,7 +496,10 @@ export function executeSceneAdminCommand(args: {
|
|
|
496
496
|
}
|
|
497
497
|
const s = sceneRegistry.get(qSceneKey);
|
|
498
498
|
if (!s) return { ok: true, text: 'Default history limit is 50.' };
|
|
499
|
-
return {
|
|
499
|
+
return {
|
|
500
|
+
ok: true,
|
|
501
|
+
text: `Current ${qSceneKey} history limit is ${resolveBncrHistoryLimit(s.historyLimit)}.`,
|
|
502
|
+
};
|
|
500
503
|
}
|
|
501
504
|
|
|
502
505
|
if (command.kind === 'history-limit-set') {
|
|
@@ -518,12 +521,14 @@ export function executeSceneAdminCommand(args: {
|
|
|
518
521
|
let resolvedLimit = rawLimit;
|
|
519
522
|
if (resolvedLimit >= 51) {
|
|
520
523
|
resolvedLimit = Math.min(Math.floor(resolvedLimit), 10000);
|
|
521
|
-
} else if (resolvedLimit
|
|
522
|
-
resolvedLimit = Math.floor(
|
|
524
|
+
} else if (resolvedLimit >= 2) {
|
|
525
|
+
resolvedLimit = Math.floor(resolvedLimit);
|
|
526
|
+
} else if (resolvedLimit < 0 && Math.abs(resolvedLimit) >= 2) {
|
|
527
|
+
resolvedLimit = Math.min(Math.floor(Math.abs(resolvedLimit)), 10000);
|
|
523
528
|
} else {
|
|
524
529
|
return {
|
|
525
530
|
ok: false,
|
|
526
|
-
text: `Value too small, must be >=
|
|
531
|
+
text: `Value too small, must be >= 2, or use negative number (abs >= 2) for hidden override.`,
|
|
527
532
|
};
|
|
528
533
|
}
|
|
529
534
|
sceneRegistry.set(sSceneKey, {
|
|
@@ -9,11 +9,7 @@ import {
|
|
|
9
9
|
buildBncrStructuredContextFactsFromInboundParts,
|
|
10
10
|
} from './context-facts.ts';
|
|
11
11
|
import type { BncrInboundApi, BncrInboundConfig, BncrInboundContextPayload } from './contracts.ts';
|
|
12
|
-
import {
|
|
13
|
-
type BncrConversationHistoryMap,
|
|
14
|
-
type BncrHistoryEntry,
|
|
15
|
-
collectBncrHistoryMediaFromEntries,
|
|
16
|
-
} from './conversation-history.ts';
|
|
12
|
+
import type { BncrConversationHistoryMap, BncrHistoryEntry } from './conversation-history.ts';
|
|
17
13
|
import type {
|
|
18
14
|
BncrInboundConversationResolution,
|
|
19
15
|
BncrPreparedInboundSessionContext,
|
|
@@ -193,9 +189,6 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
193
189
|
const senderIsOwner = parsed.isAdmin === true || Boolean(ownerAllowFrom?.length);
|
|
194
190
|
const senderIsAuthorized = senderIsOwner;
|
|
195
191
|
const inboundHistory = undefined;
|
|
196
|
-
const contextMedia = shouldDispatch
|
|
197
|
-
? collectBncrHistoryMediaFromEntries({ entries: contextEntries })
|
|
198
|
-
: [];
|
|
199
192
|
const structuredContextEntries = shouldDispatch
|
|
200
193
|
? contextEntries.map((entry) => ({
|
|
201
194
|
messageId: entry.messageId,
|
|
@@ -271,7 +264,7 @@ export function buildBncrInboundTurnContext(args: {
|
|
|
271
264
|
kind: item.kind,
|
|
272
265
|
messageId: msgId ?? undefined,
|
|
273
266
|
}));
|
|
274
|
-
const media = dedupeBncrTurnMedia(
|
|
267
|
+
const media = dedupeBncrTurnMedia(currentTurnMedia);
|
|
275
268
|
|
|
276
269
|
return Promise.resolve(
|
|
277
270
|
resolveBncrChannelInboundRuntime(api).buildContext({
|
|
@@ -44,6 +44,7 @@ export function startBncrBridgeService(
|
|
|
44
44
|
options?: { debugOnly?: boolean },
|
|
45
45
|
) => void;
|
|
46
46
|
loadState: () => Promise<void>;
|
|
47
|
+
cutoverToSqlite?: () => Promise<{ backupPath: string | null; storeMode: string }>;
|
|
47
48
|
setDebugFlag: (value: boolean) => void;
|
|
48
49
|
refreshDebugFlagFromConfig: (options?: { forceLog?: boolean }) => Promise<void>;
|
|
49
50
|
buildIntegratedDiagnostics: (accountId: string) => {
|
|
@@ -75,6 +76,13 @@ export function startBncrBridgeService(
|
|
|
75
76
|
// ignore startup canonical agent initialization errors
|
|
76
77
|
}
|
|
77
78
|
await runtime.loadState();
|
|
79
|
+
if (process.env.BNCR_SQLITE_CUTOVER === '1' && runtime.cutoverToSqlite) {
|
|
80
|
+
const cutover = await runtime.cutoverToSqlite();
|
|
81
|
+
runtime.logInfo(
|
|
82
|
+
'sqlite',
|
|
83
|
+
`cutover completed backup=${cutover.backupPath || 'none'} storeMode=${cutover.storeMode}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
78
86
|
if (typeof debug === 'boolean') runtime.setDebugFlag(debug);
|
|
79
87
|
await runtime.refreshDebugFlagFromConfig({ forceLog: true });
|
|
80
88
|
const bootDiag = runtime.buildIntegratedDiagnostics(BNCR_DEFAULT_ACCOUNT_ID);
|
|
@@ -45,7 +45,9 @@ export function createBncrBridgeOutboxFacade(runtime: {
|
|
|
45
45
|
outboundReplayCache?: BncrOutboundReplayCache;
|
|
46
46
|
conversationHistories?: BncrConversationHistoryMap;
|
|
47
47
|
resolveOutboundHistoryLimit?: (entry: OutboxEntry) => number;
|
|
48
|
+
resolveOutboundHistoryForce?: (entry: OutboxEntry) => boolean;
|
|
48
49
|
resolveOutboundSender?: (entry: OutboxEntry) => { sender: string; senderId?: string };
|
|
50
|
+
onConversationHistoryOverflow?: (entry: OutboxEntry, historyVersion?: number) => void;
|
|
49
51
|
isOutboundAckRequired?: (accountId: string) => boolean;
|
|
50
52
|
scheduleSave: () => void;
|
|
51
53
|
flushPushQueueBestEffort: (args?: {
|
|
@@ -65,7 +67,7 @@ export function createBncrBridgeOutboxFacade(runtime: {
|
|
|
65
67
|
sender: entry.accountId,
|
|
66
68
|
senderId: entry.accountId,
|
|
67
69
|
};
|
|
68
|
-
recordBncrOutboundReplay({
|
|
70
|
+
const replayResult = recordBncrOutboundReplay({
|
|
69
71
|
cache: runtime.outboundReplayCache,
|
|
70
72
|
...(runtime.conversationHistories
|
|
71
73
|
? { conversationHistories: runtime.conversationHistories }
|
|
@@ -78,6 +80,9 @@ export function createBncrBridgeOutboxFacade(runtime: {
|
|
|
78
80
|
senderId: senderInfo.senderId,
|
|
79
81
|
status,
|
|
80
82
|
});
|
|
83
|
+
if (replayResult.historyOverflow && runtime.resolveOutboundHistoryForce?.(entry) !== false) {
|
|
84
|
+
runtime.onConversationHistoryOverflow?.(entry, replayResult.historyVersion);
|
|
85
|
+
}
|
|
81
86
|
};
|
|
82
87
|
|
|
83
88
|
const markRecentOutboundAcked = (entry: OutboxEntry) => {
|
|
@@ -12,6 +12,8 @@ export const MAX_SESSION_ROUTE_ENTRIES = 1000;
|
|
|
12
12
|
export const MAX_ACCOUNT_ACTIVITY_ENTRIES = 1000;
|
|
13
13
|
export const PUSH_DRAIN_INTERVAL_MS = 500;
|
|
14
14
|
export const PUSH_DRAIN_ACCOUNT_BUDGET = 5;
|
|
15
|
+
export const HISTORY_SHARD_WORKER_INTERVAL_MS = 1_000;
|
|
16
|
+
export const HISTORY_SHARD_WORKER_BATCH_LIMIT = 5;
|
|
15
17
|
export const PUSH_DRAIN_ACCOUNT_TIME_BUDGET_MS = 2_000;
|
|
16
18
|
export const PUSH_DRAIN_EXCEPTION_RETRY_LIMIT = 3;
|
|
17
19
|
export const PUSH_DRAIN_EXCEPTION_RETRY_DELAY_MS = 1_000;
|
|
@@ -95,6 +95,9 @@ export function createBncrOutboxDrainPostPush(runtime: BncrOutboxDrainPostPushRu
|
|
|
95
95
|
ackTimeoutMs,
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
if (!requireAck && onlineNow) {
|
|
99
|
+
runtime.outbox.delete(entry.messageId);
|
|
100
|
+
}
|
|
98
101
|
if (!runtime.outbox.has(entry.messageId)) {
|
|
99
102
|
await runtime.sleepMs(runtime.pushDrainIntervalMs);
|
|
100
103
|
return { action: 'continue', localNextDelay };
|