@xmoxmo/bncr 0.4.6 → 0.4.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/README.md +8 -0
- package/package.json +1 -1
- package/src/channel.ts +41 -2
- package/src/core/targets.ts +106 -17
- package/src/messaging/inbound/commands.ts +421 -51
- package/src/messaging/inbound/context-facts.ts +126 -14
- package/src/messaging/inbound/contracts.ts +24 -0
- package/src/messaging/inbound/dispatch-prep.ts +214 -39
- package/src/messaging/inbound/dispatch.ts +71 -5
- package/src/messaging/inbound/gate.ts +56 -86
- package/src/messaging/inbound/group-history.ts +189 -0
- package/src/messaging/inbound/native-command-runtime.ts +77 -61
- package/src/messaging/inbound/native-command.ts +192 -8
- package/src/messaging/inbound/parse.ts +113 -8
- package/src/messaging/inbound/reply-dispatch-serial.ts +62 -0
- package/src/messaging/inbound/reply-dispatch.ts +252 -77
- package/src/messaging/inbound/scene-admin.ts +300 -0
- package/src/messaging/inbound/session-label.ts +122 -13
- package/src/messaging/inbound/session-meta-task.ts +17 -0
- package/src/messaging/inbound/turn-context.ts +184 -71
- package/src/openclaw/channel-runtime-contracts.ts +1 -0
- package/src/plugin/channel-components.ts +35 -1
- package/src/plugin/channel-inbound-helpers.ts +9 -2
- package/src/plugin/channel-runtime-builders-delivery.ts +24 -1
- package/src/plugin/channel-runtime-types.ts +42 -0
- package/src/plugin/file-inbound-init.ts +27 -12
- package/src/plugin/file-inbound-runtime.ts +2 -0
- package/src/plugin/inbound-acceptance.ts +82 -1
- package/src/plugin/inbound-handlers.ts +97 -2
- package/src/plugin/inbound-surface-handlers-group.ts +16 -0
- package/src/plugin/messaging.ts +22 -5
- package/src/plugin/scene-registry.ts +171 -0
- package/src/plugin/state-store.ts +133 -0
- package/src/plugin/state-transient-runtime-group.ts +5 -0
- package/src/plugin/target-runtime.ts +2 -2
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
FileSendTransferState,
|
|
8
8
|
OutboxEntry,
|
|
9
9
|
} from '../core/types.ts';
|
|
10
|
+
import type { BncrGroupHistoryMap } from '../messaging/inbound/group-history.ts';
|
|
10
11
|
import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
|
|
11
12
|
import type {
|
|
12
13
|
NormalizedReplyPayload,
|
|
@@ -18,7 +19,11 @@ import type {
|
|
|
18
19
|
buildInboundAcceptedLifecycleDebugInfo as buildInboundAcceptedLifecycleDebugInfoFromRuntime,
|
|
19
20
|
buildInboundResponsePayload as buildInboundResponsePayloadFromRuntime,
|
|
20
21
|
} from './channel-inbound-helpers.ts';
|
|
21
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
BncrChannelConfigRoot,
|
|
24
|
+
BncrSceneRecord,
|
|
25
|
+
BncrVerifiedTarget,
|
|
26
|
+
} from './channel-runtime-types.ts';
|
|
22
27
|
import type { LeaseEventPayload } from './connection-handlers.ts';
|
|
23
28
|
import type { BncrActiveConnectionDebugEntry } from './connection-state.ts';
|
|
24
29
|
import type { FileAckPayloadState, FileAckWaiter } from './file-ack-runtime.ts';
|
|
@@ -121,6 +126,11 @@ export function buildBncrStateTransientRuntime(deps: {
|
|
|
121
126
|
maxDeadLetterEntries: number;
|
|
122
127
|
maxSessionRouteEntries: number;
|
|
123
128
|
maxAccountActivityEntries: number;
|
|
129
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
130
|
+
groupHistories: Map<
|
|
131
|
+
string,
|
|
132
|
+
import('./channel-runtime-types.ts').BncrPersistedGroupHistoryEntry[]
|
|
133
|
+
>;
|
|
124
134
|
outbox: Map<string, OutboxEntry>;
|
|
125
135
|
getDeadLetter: () => OutboxEntry[];
|
|
126
136
|
setDeadLetter: (entries: OutboxEntry[]) => void;
|
|
@@ -446,6 +456,15 @@ export function buildBncrInboundSurfaceRuntime(deps: {
|
|
|
446
456
|
peer?: unknown;
|
|
447
457
|
channelId?: string;
|
|
448
458
|
}) => string;
|
|
459
|
+
defaultAdminAgentId: (args: {
|
|
460
|
+
cfg: BncrChannelConfigRoot;
|
|
461
|
+
accountId: string;
|
|
462
|
+
peer?: unknown;
|
|
463
|
+
channelId?: string;
|
|
464
|
+
}) => string;
|
|
465
|
+
defaultPublicAgentId: () => string;
|
|
466
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
467
|
+
groupHistories: BncrGroupHistoryMap;
|
|
449
468
|
prepareInboundAcceptance: (args: {
|
|
450
469
|
parsed: ReturnType<typeof parseBncrInboundParams>;
|
|
451
470
|
canonicalAgentId: string;
|
|
@@ -456,6 +475,9 @@ export function buildBncrInboundSurfaceRuntime(deps: {
|
|
|
456
475
|
sessionKey: string;
|
|
457
476
|
inboundText: string;
|
|
458
477
|
hasMedia: boolean;
|
|
478
|
+
resolvedAgentId: string;
|
|
479
|
+
shouldDispatch: boolean;
|
|
480
|
+
shouldAccumulate: boolean;
|
|
459
481
|
}
|
|
460
482
|
| {
|
|
461
483
|
ok: false;
|
|
@@ -486,6 +508,7 @@ export function buildBncrInboundSurfaceRuntime(deps: {
|
|
|
486
508
|
}) => Promise<void>;
|
|
487
509
|
setInboundActivity: (accountId: string, at: number) => void;
|
|
488
510
|
scheduleSave: () => void;
|
|
511
|
+
buildCanonicalSessionKey: (route: BncrRoute) => string;
|
|
489
512
|
fileRecvTransfers: Parameters<typeof createBncrFileInboundHandlers>[0]['fileRecvTransfers'];
|
|
490
513
|
inboundFileTransferMaxBytes: number;
|
|
491
514
|
inboundFileTransferMaxChunks: number;
|
|
@@ -44,6 +44,46 @@ export type BncrPersistedLastSession = {
|
|
|
44
44
|
updatedAt: number;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
export type BncrSceneKind = 'direct' | 'group';
|
|
48
|
+
|
|
49
|
+
export type BncrSceneStatus = 'pending' | 'allowed' | 'denied';
|
|
50
|
+
|
|
51
|
+
export type BncrGroupReplyMode = 'admin' | 'mention' | 'hybrid' | 'all';
|
|
52
|
+
|
|
53
|
+
export type BncrSceneRecord = {
|
|
54
|
+
sceneKey: string;
|
|
55
|
+
kind: BncrSceneKind;
|
|
56
|
+
status: BncrSceneStatus;
|
|
57
|
+
platform: string;
|
|
58
|
+
userId?: string;
|
|
59
|
+
userName?: string;
|
|
60
|
+
groupId?: string;
|
|
61
|
+
groupName?: string;
|
|
62
|
+
agentId?: string;
|
|
63
|
+
groupReplyMode?: BncrGroupReplyMode;
|
|
64
|
+
lastSeenAt: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type BncrPersistedGroupHistoryEntry = {
|
|
68
|
+
sender: string;
|
|
69
|
+
body: string;
|
|
70
|
+
timestamp?: number;
|
|
71
|
+
messageId?: string;
|
|
72
|
+
media?: BncrPersistedGroupHistoryMediaEntry[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type BncrPersistedGroupHistoryMediaEntry = {
|
|
76
|
+
path?: string;
|
|
77
|
+
contentType?: string;
|
|
78
|
+
kind?: 'image' | 'video' | 'audio' | 'document' | 'unknown';
|
|
79
|
+
messageId?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type BncrPersistedGroupHistoryBucket = {
|
|
83
|
+
key: string;
|
|
84
|
+
entries: BncrPersistedGroupHistoryEntry[];
|
|
85
|
+
};
|
|
86
|
+
|
|
47
87
|
export type BncrStatusRuntimeSnapshot = {
|
|
48
88
|
connected?: boolean;
|
|
49
89
|
running?: boolean;
|
|
@@ -133,6 +173,8 @@ export type PersistedState = {
|
|
|
133
173
|
outbox: OutboxEntry[];
|
|
134
174
|
deadLetter: OutboxEntry[];
|
|
135
175
|
sessionRoutes: BncrPersistedSessionRoute[];
|
|
176
|
+
sceneRegistry?: BncrSceneRecord[];
|
|
177
|
+
groupHistories?: BncrPersistedGroupHistoryBucket[];
|
|
136
178
|
lastSessionByAccount?: BncrPersistedLastSession[];
|
|
137
179
|
lastActivityByAccount?: BncrPersistedAccountTimestamp[];
|
|
138
180
|
lastInboundByAccount?: BncrPersistedAccountTimestamp[];
|
|
@@ -45,34 +45,49 @@ export function createBncrFileInboundInitHandler(runtime: BncrFileInboundRuntime
|
|
|
45
45
|
const chunkSize = runtime.finiteNonNegativeNumberOrNull(params?.chunkSize ?? 256 * 1024);
|
|
46
46
|
const totalChunks = runtime.finiteNonNegativeNumberOrNull(params?.totalChunks);
|
|
47
47
|
const fileSha256 = runtime.asString(params?.fileSha256 || '').trim();
|
|
48
|
+
const routeFromParams = runtime.parseRouteLike({
|
|
49
|
+
platform: runtime.asString(params?.platform || '').trim(),
|
|
50
|
+
groupId: runtime.asString(params?.groupId || '0').trim() || '0',
|
|
51
|
+
userId: runtime.asString(params?.userId || '0').trim() || '0',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const reject = (error: string) => {
|
|
55
|
+
runtime.logWarn(
|
|
56
|
+
'file',
|
|
57
|
+
`file.init reject accountId=${accountId} connId=${connId} clientId=${clientId || '-'} transferId=${transferId || '-'} error=${error} sessionKey=${sessionKey || '-'} fileSize=${fileSize ?? 'null'} chunkSize=${chunkSize ?? 'null'} totalChunks=${totalChunks ?? 'null'}`,
|
|
58
|
+
);
|
|
59
|
+
respond(false, { error });
|
|
60
|
+
};
|
|
48
61
|
|
|
49
62
|
if (!transferId || !sessionKey || !fileSize || !chunkSize || !totalChunks) {
|
|
50
|
-
|
|
63
|
+
reject('transferId/sessionKey/fileSize/chunkSize/totalChunks required');
|
|
51
64
|
return;
|
|
52
65
|
}
|
|
53
66
|
if (fileSize > runtime.inboundFileTransferMaxBytes) {
|
|
54
|
-
|
|
55
|
-
error: `fileSize too large size=${fileSize} max=${runtime.inboundFileTransferMaxBytes}`,
|
|
56
|
-
});
|
|
67
|
+
reject(`fileSize too large size=${fileSize} max=${runtime.inboundFileTransferMaxBytes}`);
|
|
57
68
|
return;
|
|
58
69
|
}
|
|
59
70
|
if (totalChunks > runtime.inboundFileTransferMaxChunks) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
reject(
|
|
72
|
+
`totalChunks too large total=${totalChunks} max=${runtime.inboundFileTransferMaxChunks}`,
|
|
73
|
+
);
|
|
63
74
|
return;
|
|
64
75
|
}
|
|
65
76
|
const expectedTotalChunks = Math.ceil(fileSize / chunkSize);
|
|
66
77
|
if (totalChunks !== expectedTotalChunks) {
|
|
67
|
-
|
|
68
|
-
error: `totalChunks mismatch total=${totalChunks} expected=${expectedTotalChunks}`,
|
|
69
|
-
});
|
|
78
|
+
reject(`totalChunks mismatch total=${totalChunks} expected=${expectedTotalChunks}`);
|
|
70
79
|
return;
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
let normalized = runtime.normalizeStoredSessionKey(sessionKey);
|
|
83
|
+
if (!normalized && routeFromParams) {
|
|
84
|
+
normalized = {
|
|
85
|
+
sessionKey: runtime.buildCanonicalSessionKey(routeFromParams),
|
|
86
|
+
route: routeFromParams,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
74
89
|
if (!normalized) {
|
|
75
|
-
|
|
90
|
+
reject('invalid sessionKey');
|
|
76
91
|
return;
|
|
77
92
|
}
|
|
78
93
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
|
|
2
|
+
import type { BncrSessionKind } from '../core/targets.ts';
|
|
2
3
|
import type { BncrRoute, FileRecvTransferState } from '../core/types.ts';
|
|
3
4
|
|
|
4
5
|
export type BncrFileInboundLeaseEventKind =
|
|
@@ -40,6 +41,7 @@ export type BncrFileInboundRuntime = {
|
|
|
40
41
|
normalizeStoredSessionKey: (
|
|
41
42
|
sessionKey: string,
|
|
42
43
|
) => { sessionKey: string; route: BncrRoute } | null;
|
|
44
|
+
buildCanonicalSessionKey: (route: BncrRoute, kind?: BncrSessionKind) => string;
|
|
43
45
|
saveInboundMediaBuffer: (args: {
|
|
44
46
|
buffer: Buffer;
|
|
45
47
|
mimeType: string;
|
|
@@ -1,13 +1,66 @@
|
|
|
1
|
+
import { hasControlCommand } from 'openclaw/plugin-sdk/command-auth';
|
|
1
2
|
import { resolveAccount } from '../core/accounts.ts';
|
|
2
3
|
import { checkBncrMessageGate } from '../messaging/inbound/gate.ts';
|
|
4
|
+
import { parseBncrNativeCommand } from '../messaging/inbound/native-command.ts';
|
|
3
5
|
import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
|
|
4
6
|
import type { OpenClawResolvedAgentRoute } from '../openclaw/routing-runtime.ts';
|
|
5
7
|
import type { buildInboundResponsePayload } from './channel-inbound-helpers.ts';
|
|
6
8
|
import { resolveInboundSessionContext } from './channel-inbound-helpers.ts';
|
|
7
|
-
import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
|
|
9
|
+
import type { BncrChannelConfigRoot, BncrSceneRecord } from './channel-runtime-types.ts';
|
|
10
|
+
import { decideSceneAdmission } from './scene-registry.ts';
|
|
8
11
|
|
|
9
12
|
type InboundAcceptanceResponsePayload = ReturnType<typeof buildInboundResponsePayload>;
|
|
10
13
|
|
|
14
|
+
function shouldDispatchForScene(args: {
|
|
15
|
+
parsed: ReturnType<typeof parseBncrInboundParams>;
|
|
16
|
+
admission: ReturnType<typeof decideSceneAdmission>;
|
|
17
|
+
cfg: BncrChannelConfigRoot;
|
|
18
|
+
}) {
|
|
19
|
+
const { parsed, admission, cfg } = args;
|
|
20
|
+
if (parsed.peer.kind === 'direct') return true;
|
|
21
|
+
if (!admission.allowed) return false;
|
|
22
|
+
|
|
23
|
+
const isBncrNativeCommand =
|
|
24
|
+
parseBncrNativeCommand(parsed.extracted.text, {
|
|
25
|
+
allowBareWhoami: parsed.isAdmin !== true,
|
|
26
|
+
}) !== null;
|
|
27
|
+
const isAdminOpenClawNativeCommand =
|
|
28
|
+
parsed.isAdmin === true &&
|
|
29
|
+
!isBncrNativeCommand &&
|
|
30
|
+
hasControlCommand(parsed.extracted.text, cfg as Parameters<typeof hasControlCommand>[1]);
|
|
31
|
+
if (isAdminOpenClawNativeCommand) return true;
|
|
32
|
+
|
|
33
|
+
const mode = admission.scene.groupReplyMode || 'admin';
|
|
34
|
+
if (mode === 'all') return true;
|
|
35
|
+
if (mode === 'admin') return parsed.isAdmin === true;
|
|
36
|
+
if (mode === 'mention') return parsed.shouldRespond === true;
|
|
37
|
+
return parsed.isAdmin === true || parsed.shouldRespond === true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function shouldAccumulateForScene(args: {
|
|
41
|
+
parsed: ReturnType<typeof parseBncrInboundParams>;
|
|
42
|
+
admission: ReturnType<typeof decideSceneAdmission>;
|
|
43
|
+
cfg: BncrChannelConfigRoot;
|
|
44
|
+
}) {
|
|
45
|
+
const { parsed, admission, cfg } = args;
|
|
46
|
+
if (parsed.peer.kind === 'direct') return true;
|
|
47
|
+
if (!admission.allowed) return false;
|
|
48
|
+
|
|
49
|
+
const isBncrNativeCommand =
|
|
50
|
+
parseBncrNativeCommand(parsed.extracted.text, {
|
|
51
|
+
allowBareWhoami: parsed.isAdmin !== true,
|
|
52
|
+
}) !== null;
|
|
53
|
+
const isAdminOpenClawNativeCommand =
|
|
54
|
+
parsed.isAdmin === true &&
|
|
55
|
+
!isBncrNativeCommand &&
|
|
56
|
+
hasControlCommand(parsed.extracted.text, cfg as Parameters<typeof hasControlCommand>[1]);
|
|
57
|
+
if (isAdminOpenClawNativeCommand) return true;
|
|
58
|
+
|
|
59
|
+
const mode = admission.scene.groupReplyMode || 'admin';
|
|
60
|
+
if (mode === 'admin') return parsed.isAdmin === true;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
11
64
|
export async function prepareBncrInboundAcceptance(args: {
|
|
12
65
|
api: unknown;
|
|
13
66
|
parsed: ReturnType<typeof parseBncrInboundParams>;
|
|
@@ -27,6 +80,10 @@ export async function prepareBncrInboundAcceptance(args: {
|
|
|
27
80
|
| { kind: 'gate-denied'; accountId: string; msgId?: string | null; reason: string },
|
|
28
81
|
) => InboundAcceptanceResponsePayload;
|
|
29
82
|
markInboundDedupSeen: (key: string) => boolean;
|
|
83
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
84
|
+
now: () => number;
|
|
85
|
+
defaultAdminAgentId: string;
|
|
86
|
+
defaultPublicAgentId: string;
|
|
30
87
|
}) {
|
|
31
88
|
const { parsed, canonicalAgentId } = args;
|
|
32
89
|
const {
|
|
@@ -83,6 +140,26 @@ export async function prepareBncrInboundAcceptance(args: {
|
|
|
83
140
|
};
|
|
84
141
|
}
|
|
85
142
|
|
|
143
|
+
const admission = decideSceneAdmission({
|
|
144
|
+
parsed,
|
|
145
|
+
now: args.now(),
|
|
146
|
+
sceneRegistry: args.sceneRegistry,
|
|
147
|
+
defaultAdminAgentId: args.defaultAdminAgentId,
|
|
148
|
+
defaultPublicAgentId: args.defaultPublicAgentId,
|
|
149
|
+
});
|
|
150
|
+
if (!admission.allowed) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false as const,
|
|
153
|
+
status: admission.replyPolicy === 'pending',
|
|
154
|
+
payload: args.buildInboundResponsePayload({
|
|
155
|
+
kind: 'gate-denied',
|
|
156
|
+
accountId,
|
|
157
|
+
msgId: msgId ?? null,
|
|
158
|
+
reason: admission.reason,
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
86
163
|
const { sessionKey, inboundText } = resolveInboundSessionContext({
|
|
87
164
|
cfg,
|
|
88
165
|
accountId,
|
|
@@ -90,6 +167,7 @@ export async function prepareBncrInboundAcceptance(args: {
|
|
|
90
167
|
route,
|
|
91
168
|
sessionKeyFromRoute: sessionKeyfromroute,
|
|
92
169
|
canonicalAgentId,
|
|
170
|
+
resolvedAgentId: admission.agentId,
|
|
93
171
|
taskKey: extracted.taskKey ?? undefined,
|
|
94
172
|
text,
|
|
95
173
|
extractedText: extracted.text,
|
|
@@ -103,5 +181,8 @@ export async function prepareBncrInboundAcceptance(args: {
|
|
|
103
181
|
sessionKey,
|
|
104
182
|
inboundText,
|
|
105
183
|
hasMedia: Boolean(mediaBase64 || mediaPathFromTransfer),
|
|
184
|
+
resolvedAgentId: admission.agentId,
|
|
185
|
+
shouldDispatch: shouldDispatchForScene({ parsed, admission, cfg }),
|
|
186
|
+
shouldAccumulate: shouldAccumulateForScene({ parsed, admission, cfg }),
|
|
106
187
|
};
|
|
107
188
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
|
|
2
|
+
import { buildCanonicalBncrSessionKey } from '../core/targets.ts';
|
|
2
3
|
import type { BncrConnection, BncrRoute } from '../core/types.ts';
|
|
4
|
+
import type { BncrGroupHistoryMap } from '../messaging/inbound/group-history.ts';
|
|
3
5
|
import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
|
|
6
|
+
import type { ReplyPayloadInput } from '../messaging/outbound/reply-enqueue.ts';
|
|
4
7
|
import type {
|
|
5
8
|
buildInboundResponsePayload,
|
|
6
9
|
resolveInboundSessionContext,
|
|
7
10
|
} from './channel-inbound-helpers.ts';
|
|
8
|
-
import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
|
|
11
|
+
import type { BncrChannelConfigRoot, BncrSceneRecord } from './channel-runtime-types.ts';
|
|
9
12
|
import { buildBncrGatewayEventContext } from './gateway-event-context.ts';
|
|
10
13
|
|
|
11
14
|
type InboundLifecycleStage = 'accepted';
|
|
@@ -39,6 +42,9 @@ type InboundAcceptanceResult =
|
|
|
39
42
|
sessionKey: string;
|
|
40
43
|
inboundText: string;
|
|
41
44
|
hasMedia: boolean;
|
|
45
|
+
resolvedAgentId: string;
|
|
46
|
+
shouldDispatch: boolean;
|
|
47
|
+
shouldAccumulate: boolean;
|
|
42
48
|
}
|
|
43
49
|
| {
|
|
44
50
|
ok: false;
|
|
@@ -46,6 +52,26 @@ type InboundAcceptanceResult =
|
|
|
46
52
|
payload: Record<string, unknown>;
|
|
47
53
|
};
|
|
48
54
|
|
|
55
|
+
function buildBncrPendingDirectApprovalNotice(args: {
|
|
56
|
+
parsed: ParsedInboundParams;
|
|
57
|
+
reason: string;
|
|
58
|
+
}): string | null {
|
|
59
|
+
if (args.parsed.peer.kind !== 'direct') return null;
|
|
60
|
+
if (args.reason !== 'scene pending approval') return null;
|
|
61
|
+
return `This private chat is pending approval. Ask the administrator to allow your SceneId: ${args.parsed.platform}:${args.parsed.userId}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildBncrPendingDirectApprovalPayload(args: {
|
|
65
|
+
msgId?: string;
|
|
66
|
+
text: string;
|
|
67
|
+
}): ReplyPayloadInput {
|
|
68
|
+
return {
|
|
69
|
+
text: args.text,
|
|
70
|
+
kind: 'final',
|
|
71
|
+
...(args.msgId ? { replyToId: args.msgId } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
export type BncrInboundHandlersRuntime = {
|
|
50
76
|
channelId: string;
|
|
51
77
|
bridgeId: string;
|
|
@@ -98,6 +124,15 @@ export type BncrInboundHandlersRuntime = {
|
|
|
98
124
|
peer: EnsureCanonicalAgentIdPeer;
|
|
99
125
|
channelId: string;
|
|
100
126
|
}) => string;
|
|
127
|
+
defaultAdminAgentId: (args: {
|
|
128
|
+
cfg: BncrChannelConfigRoot;
|
|
129
|
+
accountId: string;
|
|
130
|
+
peer: EnsureCanonicalAgentIdPeer;
|
|
131
|
+
channelId: string;
|
|
132
|
+
}) => string;
|
|
133
|
+
defaultPublicAgentId: () => string;
|
|
134
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
135
|
+
groupHistories: BncrGroupHistoryMap;
|
|
101
136
|
prepareInboundAcceptance: (args: {
|
|
102
137
|
parsed: ParsedInboundParams;
|
|
103
138
|
canonicalAgentId: string;
|
|
@@ -110,12 +145,27 @@ export type BncrInboundHandlersRuntime = {
|
|
|
110
145
|
text: string;
|
|
111
146
|
hasMedia: boolean;
|
|
112
147
|
}) => void;
|
|
148
|
+
enqueueFromReply: (args: {
|
|
149
|
+
accountId: string;
|
|
150
|
+
sessionKey: string;
|
|
151
|
+
route: BncrRoute;
|
|
152
|
+
payload: ReplyPayloadInput;
|
|
153
|
+
mediaLocalRoots?: readonly string[];
|
|
154
|
+
}) => Promise<void>;
|
|
113
155
|
respond: GatewayRequestHandlerOptions['respond'];
|
|
114
156
|
flushOnInboundAccepted: (accountId: string) => void;
|
|
115
157
|
dispatchInbound: (args: {
|
|
116
158
|
cfg: BncrChannelConfigRoot;
|
|
117
159
|
parsed: ParsedInboundParams;
|
|
118
160
|
canonicalAgentId: string;
|
|
161
|
+
resolvedAgentId: string;
|
|
162
|
+
shouldDispatch: boolean;
|
|
163
|
+
shouldAccumulate: boolean;
|
|
164
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
165
|
+
groupHistories: BncrGroupHistoryMap;
|
|
166
|
+
defaultAdminAgentId: string;
|
|
167
|
+
defaultPublicAgentId: string;
|
|
168
|
+
now: () => number;
|
|
119
169
|
}) => Promise<unknown>;
|
|
120
170
|
};
|
|
121
171
|
|
|
@@ -191,13 +241,50 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
|
|
|
191
241
|
peer,
|
|
192
242
|
channelId: runtime.channelId,
|
|
193
243
|
});
|
|
244
|
+
const defaultAdminAgentId = runtime.defaultAdminAgentId({
|
|
245
|
+
cfg,
|
|
246
|
+
accountId,
|
|
247
|
+
peer,
|
|
248
|
+
channelId: runtime.channelId,
|
|
249
|
+
});
|
|
194
250
|
const acceptance = await runtime.prepareInboundAcceptance({ parsed, canonicalAgentId });
|
|
195
251
|
if (!acceptance.ok) {
|
|
252
|
+
const reason = runtime
|
|
253
|
+
.asString((acceptance.payload as { reason?: unknown })?.reason || '')
|
|
254
|
+
.trim();
|
|
255
|
+
runtime.logInfo(
|
|
256
|
+
'inbound',
|
|
257
|
+
`accept reject|accountId=${accountId}|msgId=${msgId ?? '-'}|scope=${runtime.formatDisplayScope(route)}|chatType=${peer.kind}|msgType=${msgType}|status=${acceptance.status}|reason=${reason || '-'}|isAdmin=${parsed.isAdmin === true}|shouldRespond=${parsed.shouldRespond === true}`,
|
|
258
|
+
);
|
|
259
|
+
const pendingNotice = buildBncrPendingDirectApprovalNotice({ parsed, reason });
|
|
260
|
+
if (pendingNotice) {
|
|
261
|
+
const sessionKey = buildCanonicalBncrSessionKey(route, canonicalAgentId);
|
|
262
|
+
await runtime.enqueueFromReply({
|
|
263
|
+
accountId,
|
|
264
|
+
sessionKey,
|
|
265
|
+
route,
|
|
266
|
+
payload: buildBncrPendingDirectApprovalPayload({
|
|
267
|
+
msgId: msgId ?? undefined,
|
|
268
|
+
text: pendingNotice,
|
|
269
|
+
}),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
196
272
|
respond(acceptance.status, acceptance.payload);
|
|
197
273
|
return;
|
|
198
274
|
}
|
|
199
275
|
|
|
200
|
-
const {
|
|
276
|
+
const {
|
|
277
|
+
sessionKey,
|
|
278
|
+
inboundText,
|
|
279
|
+
hasMedia,
|
|
280
|
+
resolvedAgentId,
|
|
281
|
+
shouldDispatch,
|
|
282
|
+
shouldAccumulate,
|
|
283
|
+
} = acceptance;
|
|
284
|
+
runtime.logInfo(
|
|
285
|
+
'inbound',
|
|
286
|
+
`accept ok|accountId=${accountId}|msgId=${msgId ?? '-'}|scope=${runtime.formatDisplayScope(route)}|chatType=${peer.kind}|msgType=${msgType}|sessionKey=${sessionKey}|agent=${resolvedAgentId}|dispatch=${shouldDispatch}|accumulate=${shouldAccumulate}|isAdmin=${parsed.isAdmin === true}|shouldRespond=${parsed.shouldRespond === true}`,
|
|
287
|
+
);
|
|
201
288
|
runtime.logInfo(
|
|
202
289
|
'inbound',
|
|
203
290
|
JSON.stringify({
|
|
@@ -239,6 +326,14 @@ export function createBncrInboundHandlers(runtime: Omit<BncrInboundHandlersRunti
|
|
|
239
326
|
cfg,
|
|
240
327
|
parsed,
|
|
241
328
|
canonicalAgentId,
|
|
329
|
+
resolvedAgentId,
|
|
330
|
+
shouldDispatch,
|
|
331
|
+
shouldAccumulate,
|
|
332
|
+
sceneRegistry: runtime.sceneRegistry,
|
|
333
|
+
groupHistories: runtime.groupHistories,
|
|
334
|
+
defaultAdminAgentId,
|
|
335
|
+
defaultPublicAgentId: runtime.defaultPublicAgentId(),
|
|
336
|
+
now: runtime.now,
|
|
242
337
|
})
|
|
243
338
|
.catch((err) => {
|
|
244
339
|
runtime.logError('inbound', `process failed: ${String(err)}`, { debugOnly: true });
|
|
@@ -53,6 +53,9 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
|
|
|
53
53
|
clientId?: string;
|
|
54
54
|
context: GatewayRequestHandlerOptions['context'];
|
|
55
55
|
}) => void;
|
|
56
|
+
buildCanonicalSessionKey: Parameters<
|
|
57
|
+
typeof createBncrFileInboundHandlersComponent
|
|
58
|
+
>[0]['buildCanonicalSessionKey'];
|
|
56
59
|
refreshLiveConnectionState: (args: {
|
|
57
60
|
accountId: string;
|
|
58
61
|
connId: string;
|
|
@@ -75,6 +78,14 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
|
|
|
75
78
|
ensureCanonicalAgentId: Parameters<
|
|
76
79
|
typeof createBncrInboundHandlersComponent
|
|
77
80
|
>[0]['ensureCanonicalAgentId'];
|
|
81
|
+
defaultAdminAgentId: Parameters<
|
|
82
|
+
typeof createBncrInboundHandlersComponent
|
|
83
|
+
>[0]['defaultAdminAgentId'];
|
|
84
|
+
defaultPublicAgentId: Parameters<
|
|
85
|
+
typeof createBncrInboundHandlersComponent
|
|
86
|
+
>[0]['defaultPublicAgentId'];
|
|
87
|
+
sceneRegistry: Parameters<typeof createBncrInboundHandlersComponent>[0]['sceneRegistry'];
|
|
88
|
+
groupHistories: Parameters<typeof createBncrInboundHandlersComponent>[0]['groupHistories'];
|
|
78
89
|
prepareInboundAcceptance: Parameters<
|
|
79
90
|
typeof createBncrInboundHandlersComponent
|
|
80
91
|
>[0]['prepareInboundAcceptance'];
|
|
@@ -111,6 +122,7 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
|
|
|
111
122
|
matchesTransferOwner: runtime.matchesTransferOwner,
|
|
112
123
|
refreshAcceptedFileTransferLiveState: runtime.refreshAcceptedFileTransferLiveState,
|
|
113
124
|
logWarn: runtime.logWarn,
|
|
125
|
+
buildCanonicalSessionKey: runtime.buildCanonicalSessionKey,
|
|
114
126
|
fileRecvTransfers: runtime.fileRecvTransfers,
|
|
115
127
|
inboundFileTransferMaxBytes: runtime.inboundFileTransferMaxBytes,
|
|
116
128
|
inboundFileTransferMaxChunks: runtime.inboundFileTransferMaxChunks,
|
|
@@ -136,6 +148,10 @@ export function createBncrInboundSurfaceHandlersGroup(runtime: {
|
|
|
136
148
|
buildActiveConnectionDebugList: runtime.buildActiveConnectionDebugList,
|
|
137
149
|
markLastInboundAt: runtime.markLastInboundAt,
|
|
138
150
|
ensureCanonicalAgentId: runtime.ensureCanonicalAgentId,
|
|
151
|
+
defaultAdminAgentId: runtime.defaultAdminAgentId,
|
|
152
|
+
defaultPublicAgentId: runtime.defaultPublicAgentId,
|
|
153
|
+
sceneRegistry: runtime.sceneRegistry,
|
|
154
|
+
groupHistories: runtime.groupHistories,
|
|
139
155
|
prepareInboundAcceptance: runtime.prepareInboundAcceptance,
|
|
140
156
|
logInboundSummary: runtime.logInboundSummary,
|
|
141
157
|
flushPushQueueBestEffort: runtime.flushPushQueueBestEffort,
|
package/src/plugin/messaging.ts
CHANGED
|
@@ -10,6 +10,15 @@ import {
|
|
|
10
10
|
} from '../messaging/outbound/target-resolver.ts';
|
|
11
11
|
import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
|
|
12
12
|
|
|
13
|
+
function formatBncrHumanDisplay(route: BncrRoute): string {
|
|
14
|
+
const platform = asSanitizedString(route?.platform).trim();
|
|
15
|
+
const groupId = asSanitizedString(route?.groupId).trim();
|
|
16
|
+
const userId = asSanitizedString(route?.userId).trim();
|
|
17
|
+
if (platform && groupId && groupId !== '0') return `Bncr:${platform}:Group:${groupId}`;
|
|
18
|
+
if (platform && userId && userId !== '0') return `Bncr:${platform}:User:${userId}`;
|
|
19
|
+
return formatDisplayScope(route);
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
type BncrMessagingRuntimeBridge = {
|
|
14
23
|
canonicalAgentId?: string;
|
|
15
24
|
ensureCanonicalAgentId: (params: { cfg: BncrChannelConfigRoot; accountId: string }) => string;
|
|
@@ -69,16 +78,24 @@ export function normalizeBncrMessagingTarget(raw: string) {
|
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
export function formatBncrMessagingTargetDisplay({ target }: BncrMessagingTargetDisplayInput) {
|
|
72
|
-
if (typeof target === 'string')
|
|
81
|
+
if (typeof target === 'string') {
|
|
82
|
+
const parsed = parseExplicitTarget(asSanitizedString(target).trim());
|
|
83
|
+
return parsed?.route ? formatBncrHumanDisplay(parsed.route) : asSanitizedString(target).trim();
|
|
84
|
+
}
|
|
73
85
|
if (!isDisplayTarget(target)) return '';
|
|
74
86
|
const displayScope = asSanitizedString(target?.displayScope || target?.to).trim();
|
|
75
|
-
if (displayScope)
|
|
87
|
+
if (displayScope) {
|
|
88
|
+
const parsed = parseExplicitTarget(displayScope);
|
|
89
|
+
if (parsed?.route) return formatBncrHumanDisplay(parsed.route);
|
|
90
|
+
return displayScope;
|
|
91
|
+
}
|
|
76
92
|
if (target.platform || target.groupId || target.userId) {
|
|
77
|
-
|
|
93
|
+
const route = {
|
|
78
94
|
platform: asSanitizedString(target.platform).trim(),
|
|
79
95
|
groupId: asSanitizedString(target.groupId).trim(),
|
|
80
96
|
userId: asSanitizedString(target.userId).trim(),
|
|
81
|
-
}
|
|
97
|
+
};
|
|
98
|
+
return formatBncrHumanDisplay(route);
|
|
82
99
|
}
|
|
83
100
|
return '';
|
|
84
101
|
}
|
|
@@ -188,7 +205,7 @@ export function createBncrMessagingTargetResolver(getBridge: () => BncrMessaging
|
|
|
188
205
|
return {
|
|
189
206
|
to: resolved.displayScope,
|
|
190
207
|
kind: resolved.kind,
|
|
191
|
-
display: resolved.
|
|
208
|
+
display: formatBncrHumanDisplay(resolved.route),
|
|
192
209
|
source: 'normalized' as const,
|
|
193
210
|
};
|
|
194
211
|
},
|