@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
|
@@ -2,6 +2,13 @@ export type NativeCommand = {
|
|
|
2
2
|
command: string;
|
|
3
3
|
raw: string;
|
|
4
4
|
body: string;
|
|
5
|
+
argsText: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ParseBncrNativeCommandOptions = {
|
|
9
|
+
allowBareWhoami?: boolean;
|
|
10
|
+
allowBareStatus?: boolean;
|
|
11
|
+
allowBareSessionReset?: boolean;
|
|
5
12
|
};
|
|
6
13
|
|
|
7
14
|
export type NativeVerboseCommand = {
|
|
@@ -10,27 +17,204 @@ export type NativeVerboseCommand = {
|
|
|
10
17
|
text: string;
|
|
11
18
|
};
|
|
12
19
|
|
|
13
|
-
export
|
|
20
|
+
export type NativeHelpCommand = {
|
|
21
|
+
handled: true;
|
|
22
|
+
text: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type NativeWhoamiCommand = {
|
|
26
|
+
handled: true;
|
|
27
|
+
text: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type NativeStatusCommand = {
|
|
31
|
+
handled: true;
|
|
32
|
+
text: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type NativeSessionResetCommand = {
|
|
36
|
+
handled: true;
|
|
37
|
+
reason: 'new' | 'reset';
|
|
38
|
+
text: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const BNCR_HELP_TEXT = [
|
|
42
|
+
'🦞 Bncr command usage',
|
|
43
|
+
'',
|
|
44
|
+
'📌 Bncr builtins',
|
|
45
|
+
' • /bncr whoami',
|
|
46
|
+
' • /bncr verbose on|off|full',
|
|
47
|
+
'',
|
|
48
|
+
'🛡 Scene approval',
|
|
49
|
+
' • /bncr allow [<SceneId>]',
|
|
50
|
+
' • /bncr deny [<SceneId>]',
|
|
51
|
+
' • /bncr bind <agentId> [<SceneId>]',
|
|
52
|
+
' • /bncr mode',
|
|
53
|
+
' • /bncr mode help',
|
|
54
|
+
' • /bncr mode <admin|mention|hybrid|all> [<SceneId>]',
|
|
55
|
+
' • /bncr revoke [<SceneId>]',
|
|
56
|
+
' • /bncr list pending',
|
|
57
|
+
' • /bncr list scenes',
|
|
58
|
+
].join('\n');
|
|
59
|
+
|
|
60
|
+
const BNCR_NATIVE_COMMANDS = new Set([
|
|
61
|
+
'help',
|
|
62
|
+
'new',
|
|
63
|
+
'reset',
|
|
64
|
+
'status',
|
|
65
|
+
'whoami',
|
|
66
|
+
'verbose',
|
|
67
|
+
'allow',
|
|
68
|
+
'deny',
|
|
69
|
+
'bind',
|
|
70
|
+
'mode',
|
|
71
|
+
'revoke',
|
|
72
|
+
'list',
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
export const BNCR_DIRECT_ALLOWED_BARE_COMMANDS = new Set(['whoami', 'status', 'new', 'reset']);
|
|
76
|
+
|
|
77
|
+
export const BNCR_DIRECT_ALLOWED_SUBCOMMANDS = new Set([
|
|
78
|
+
'help',
|
|
79
|
+
'whoami',
|
|
80
|
+
'status',
|
|
81
|
+
'new',
|
|
82
|
+
'reset',
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
export function parseBncrNativeCommand(
|
|
86
|
+
text: string,
|
|
87
|
+
options?: ParseBncrNativeCommandOptions,
|
|
88
|
+
): NativeCommand | null {
|
|
14
89
|
const raw = String(text || '').trim();
|
|
15
|
-
|
|
16
|
-
const
|
|
90
|
+
const allowBareWhoami = options?.allowBareWhoami !== false;
|
|
91
|
+
const allowBareStatus = options?.allowBareStatus === true;
|
|
92
|
+
const allowBareSessionReset = options?.allowBareSessionReset === true;
|
|
93
|
+
if (allowBareSessionReset && raw.toLowerCase() === '/new') {
|
|
94
|
+
return { command: 'new', raw, body: '/new', argsText: '' };
|
|
95
|
+
}
|
|
96
|
+
if (allowBareSessionReset && raw.toLowerCase() === '/reset') {
|
|
97
|
+
return { command: 'reset', raw, body: '/reset', argsText: '' };
|
|
98
|
+
}
|
|
99
|
+
if (allowBareStatus && raw.toLowerCase() === '/status') {
|
|
100
|
+
return { command: 'status', raw, body: '/status', argsText: '' };
|
|
101
|
+
}
|
|
102
|
+
if (allowBareWhoami && raw.toLowerCase() === '/whoami') {
|
|
103
|
+
return { command: 'whoami', raw, body: '/whoami', argsText: '' };
|
|
104
|
+
}
|
|
105
|
+
if (!raw.startsWith('/bncr')) return null;
|
|
106
|
+
const match = raw.match(/^\/bncr(?:@[A-Za-z0-9_]+)?(?:\s+([^\s]+)(?:\s+([\s\S]*))?)?$/i);
|
|
17
107
|
if (!match) return null;
|
|
18
108
|
|
|
19
|
-
const command = String(match[1] || '')
|
|
109
|
+
const command = String(match[1] || 'help')
|
|
20
110
|
.trim()
|
|
21
111
|
.toLowerCase();
|
|
22
112
|
if (!command) return null;
|
|
113
|
+
if (!BNCR_NATIVE_COMMANDS.has(command)) return null;
|
|
114
|
+
|
|
115
|
+
const argsText = String(match[2] || '').trim();
|
|
116
|
+
const body =
|
|
117
|
+
command === 'help' ? '/commands' : [`/${command}`, argsText].filter(Boolean).join(' ');
|
|
118
|
+
return { command, raw, body, argsText };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parseBncrUnsupportedDirectCommand(text: string): {
|
|
122
|
+
command: string;
|
|
123
|
+
raw: string;
|
|
124
|
+
} | null {
|
|
125
|
+
const raw = String(text || '').trim();
|
|
126
|
+
if (!raw.startsWith('/')) return null;
|
|
127
|
+
|
|
128
|
+
const bareMatch = raw.match(/^\/([A-Za-z0-9_]+)(?:@[A-Za-z0-9_]+)?(?:\s+.*)?$/);
|
|
129
|
+
if (!bareMatch) return null;
|
|
130
|
+
const bareCommand = String(bareMatch[1] || '')
|
|
131
|
+
.trim()
|
|
132
|
+
.toLowerCase();
|
|
133
|
+
if (!bareCommand) return null;
|
|
134
|
+
|
|
135
|
+
if (bareCommand === 'bncr') {
|
|
136
|
+
const subMatch = raw.match(/^\/bncr(?:@[A-Za-z0-9_]+)?(?:\s+([A-Za-z0-9_-]+))?/i);
|
|
137
|
+
const subCommand =
|
|
138
|
+
String(subMatch?.[1] || '')
|
|
139
|
+
.trim()
|
|
140
|
+
.toLowerCase() || 'help';
|
|
141
|
+
if (BNCR_DIRECT_ALLOWED_SUBCOMMANDS.has(subCommand)) return null;
|
|
142
|
+
return { command: `bncr ${subCommand}`, raw };
|
|
143
|
+
}
|
|
23
144
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
145
|
+
if (BNCR_DIRECT_ALLOWED_BARE_COMMANDS.has(bareCommand)) return null;
|
|
146
|
+
return { command: bareCommand, raw };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveBncrNativeHelpCommand(command: NativeCommand): NativeHelpCommand | null {
|
|
150
|
+
if (command.command !== 'help') return null;
|
|
151
|
+
return { handled: true, text: BNCR_HELP_TEXT };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function resolveBncrNativeWhoamiCommand(args: {
|
|
155
|
+
command: NativeCommand;
|
|
156
|
+
platform: string;
|
|
157
|
+
groupId: string;
|
|
158
|
+
groupName?: string;
|
|
159
|
+
userId: string;
|
|
160
|
+
userName?: string;
|
|
161
|
+
isGroup: boolean;
|
|
162
|
+
isAdmin: boolean;
|
|
163
|
+
}): NativeWhoamiCommand | null {
|
|
164
|
+
if (args.command.command !== 'whoami') return null;
|
|
165
|
+
const lines = ['🧭 Bncr Identity', ''];
|
|
166
|
+
lines.push(`Platform: ${args.platform || '(unknown)'}`);
|
|
167
|
+
lines.push(`User: ${args.userName || '(unknown)'} (${args.userId || '0'})`);
|
|
168
|
+
if (args.isGroup) {
|
|
169
|
+
lines.push(`Group: ${args.groupName || '(unknown)'} (${args.groupId || '0'})`);
|
|
170
|
+
lines.push(`Scene: ${args.platform || '(unknown)'}:${args.groupId || '0'}`);
|
|
171
|
+
} else {
|
|
172
|
+
lines.push(`Scene: ${args.platform || '(unknown)'}:${args.userId || '0'}`);
|
|
173
|
+
}
|
|
174
|
+
lines.push(`Admin: ${args.isAdmin ? 'true' : 'false'}`);
|
|
175
|
+
return { handled: true, text: lines.join('\n') };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function resolveBncrNativeStatusCommand(args: {
|
|
179
|
+
command: NativeCommand;
|
|
180
|
+
accountId: string;
|
|
181
|
+
platform: string;
|
|
182
|
+
userId: string;
|
|
183
|
+
userName?: string;
|
|
184
|
+
resolvedAgentId: string;
|
|
185
|
+
sessionKey: string;
|
|
186
|
+
}): NativeStatusCommand | null {
|
|
187
|
+
if (args.command.command !== 'status') return null;
|
|
188
|
+
const lines = ['🦞 Bncr Status', ''];
|
|
189
|
+
lines.push(`Channel: bncr`);
|
|
190
|
+
lines.push(`Account: ${args.accountId || 'Primary'}`);
|
|
191
|
+
lines.push(`User: ${args.userName || '(unknown)'} (${args.userId || '0'})`);
|
|
192
|
+
lines.push(`Scene: ${args.platform || '(unknown)'}:${args.userId || '0'}`);
|
|
193
|
+
lines.push(`Agent: ${args.resolvedAgentId || '(unknown)'}`);
|
|
194
|
+
lines.push(`SessionKey: ${args.sessionKey || '(unknown)'}`);
|
|
195
|
+
return { handled: true, text: lines.join('\n') };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function resolveBncrNativeSessionResetCommand(args: {
|
|
199
|
+
command: NativeCommand;
|
|
200
|
+
}): NativeSessionResetCommand | null {
|
|
201
|
+
if (args.command.command !== 'new' && args.command.command !== 'reset') return null;
|
|
202
|
+
const reason = args.command.command === 'new' ? 'new' : 'reset';
|
|
203
|
+
return {
|
|
204
|
+
handled: true,
|
|
205
|
+
reason,
|
|
206
|
+
text:
|
|
207
|
+
reason === 'new'
|
|
208
|
+
? 'Started a new session for this private chat.'
|
|
209
|
+
: 'Reset the current session for this private chat.',
|
|
210
|
+
};
|
|
27
211
|
}
|
|
28
212
|
|
|
29
213
|
export function resolveBncrNativeVerboseCommand(
|
|
30
214
|
command: NativeCommand,
|
|
31
215
|
): NativeVerboseCommand | null {
|
|
32
216
|
if (command.command !== 'verbose') return null;
|
|
33
|
-
const rawLevel = String(command.
|
|
217
|
+
const rawLevel = String(command.argsText || '')
|
|
34
218
|
.trim()
|
|
35
219
|
.toLowerCase();
|
|
36
220
|
if (!rawLevel || rawLevel === 'status') {
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
3
3
|
import { extractInlineTaskKey } from '../../core/targets.ts';
|
|
4
4
|
import type { BncrRoute } from '../../core/types.ts';
|
|
5
|
-
import type { BncrInboundParamsInput } from './contracts.ts';
|
|
5
|
+
import type { BncrInboundMediaItem, BncrInboundParamsInput } from './contracts.ts';
|
|
6
6
|
|
|
7
7
|
function asString(v: unknown, fallback = ''): string {
|
|
8
8
|
if (typeof v === 'string') return v;
|
|
@@ -10,6 +10,84 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
10
10
|
return String(v);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function asBoolean(v: unknown, fallback = false): boolean {
|
|
14
|
+
if (typeof v === 'boolean') return v;
|
|
15
|
+
if (typeof v === 'number') return v !== 0;
|
|
16
|
+
if (typeof v === 'string') {
|
|
17
|
+
const raw = v.trim().toLowerCase();
|
|
18
|
+
if (!raw) return fallback;
|
|
19
|
+
if (['true', '1', 'yes', 'y', 'on'].includes(raw)) return true;
|
|
20
|
+
if (['false', '0', 'no', 'n', 'off'].includes(raw)) return false;
|
|
21
|
+
}
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function asStringArray(v: unknown): string[] {
|
|
26
|
+
if (Array.isArray(v)) {
|
|
27
|
+
return v.map((item) => asString(item).trim()).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
if (typeof v === 'string') {
|
|
30
|
+
return v
|
|
31
|
+
.split(',')
|
|
32
|
+
.map((item) => item.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function asInboundMediaItems(params: BncrInboundParamsInput): BncrInboundMediaItem[] {
|
|
39
|
+
const normalized: BncrInboundMediaItem[] = [];
|
|
40
|
+
const rawList = Array.isArray(params?.mediaList) ? params.mediaList : [];
|
|
41
|
+
for (const item of rawList) {
|
|
42
|
+
if (!item || typeof item !== 'object') continue;
|
|
43
|
+
const path = asString((item as { path?: unknown }).path || '').trim();
|
|
44
|
+
const base64 = asString((item as { base64?: unknown }).base64 || '').trim();
|
|
45
|
+
const mimeType = asString((item as { mimeType?: unknown }).mimeType || '').trim();
|
|
46
|
+
const fileName = asString((item as { fileName?: unknown }).fileName || '').trim();
|
|
47
|
+
const type = asString((item as { type?: unknown }).type || '').trim();
|
|
48
|
+
const transferId = asString((item as { transferId?: unknown }).transferId || '').trim();
|
|
49
|
+
if (!path && !base64) continue;
|
|
50
|
+
normalized.push({
|
|
51
|
+
...(path ? { path } : {}),
|
|
52
|
+
...(base64 ? { base64 } : {}),
|
|
53
|
+
...(mimeType ? { mimeType } : {}),
|
|
54
|
+
...(fileName ? { fileName } : {}),
|
|
55
|
+
...(type ? { type } : {}),
|
|
56
|
+
...(transferId ? { transferId } : {}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (normalized.length > 0) return normalized;
|
|
61
|
+
|
|
62
|
+
const legacyPath = asString(params?.path || '').trim();
|
|
63
|
+
const legacyBase64 = asString(params?.base64 || '').trim();
|
|
64
|
+
const legacyMimeType = asString(params?.mimeType || '').trim();
|
|
65
|
+
const legacyFileName = asString(params?.fileName || '').trim();
|
|
66
|
+
const legacyType = asString(params?.type || '').trim();
|
|
67
|
+
const legacyTransferId = asString((params as { transferId?: unknown })?.transferId || '').trim();
|
|
68
|
+
if (legacyPath || legacyBase64) {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
...(legacyPath ? { path: legacyPath } : {}),
|
|
72
|
+
...(legacyBase64 ? { base64: legacyBase64 } : {}),
|
|
73
|
+
...(legacyMimeType ? { mimeType: legacyMimeType } : {}),
|
|
74
|
+
...(legacyFileName ? { fileName: legacyFileName } : {}),
|
|
75
|
+
...(legacyType ? { type: legacyType } : {}),
|
|
76
|
+
...(legacyTransferId ? { transferId: legacyTransferId } : {}),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const legacyPaths = asStringArray(params?.paths);
|
|
82
|
+
return legacyPaths.map((item) => ({
|
|
83
|
+
path: item,
|
|
84
|
+
...(legacyMimeType ? { mimeType: legacyMimeType } : {}),
|
|
85
|
+
...(legacyFileName ? { fileName: legacyFileName } : {}),
|
|
86
|
+
...(legacyType ? { type: legacyType } : {}),
|
|
87
|
+
...(legacyTransferId ? { transferId: legacyTransferId } : {}),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
13
91
|
export function inboundDedupKey(params: {
|
|
14
92
|
accountId: string;
|
|
15
93
|
platform: string;
|
|
@@ -36,24 +114,30 @@ export function inboundDedupKey(params: {
|
|
|
36
114
|
return `${accountId}|${platform}|${groupId}|${userId}|hash:${digest}`;
|
|
37
115
|
}
|
|
38
116
|
|
|
39
|
-
export function resolveChatType(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// group semantics without updating session routing, reply target policy, and requireMention
|
|
43
|
-
// behavior together.
|
|
117
|
+
export function resolveChatType(route: BncrRoute, isGroup: boolean): 'direct' | 'group' {
|
|
118
|
+
if (isGroup) return 'group';
|
|
119
|
+
if (route.groupId !== '0') return 'group';
|
|
44
120
|
return 'direct';
|
|
45
121
|
}
|
|
46
122
|
|
|
47
123
|
export function parseBncrInboundParams(params: BncrInboundParamsInput) {
|
|
48
124
|
const accountId = normalizeAccountId(asString(params?.accountId || ''));
|
|
125
|
+
const protocolVersion = asString(params?.protocolVersion || '').trim() || undefined;
|
|
126
|
+
const capabilities = asStringArray(params?.capabilities);
|
|
49
127
|
const platform = asString(params?.platform || '').trim();
|
|
50
128
|
const groupId = asString(params?.groupId || '0').trim() || '0';
|
|
129
|
+
const groupName = asString(params?.groupName || '').trim();
|
|
51
130
|
const userId = asString(params?.userId || '').trim();
|
|
131
|
+
const userName = asString(params?.userName || '').trim();
|
|
52
132
|
const sessionKeyfromroute = asString(params?.sessionKey || '').trim();
|
|
53
133
|
const providedOriginatingTo =
|
|
54
134
|
asString(params?.originatingTo || params?.providedOriginatingTo || params?.to || '').trim() ||
|
|
55
135
|
undefined;
|
|
56
136
|
const clientId = asString(params?.clientId || '').trim() || undefined;
|
|
137
|
+
const bridgeId = asString(params?.bridgeId || params?.clientId || '').trim() || undefined;
|
|
138
|
+
const bridgeName = asString(params?.bridgeName || 'Bncr').trim() || 'Bncr';
|
|
139
|
+
const isGroup = asBoolean(params?.isGroup, groupId !== '0');
|
|
140
|
+
const isAdmin = asBoolean(params?.isAdmin, false);
|
|
57
141
|
|
|
58
142
|
const route: BncrRoute = {
|
|
59
143
|
platform,
|
|
@@ -63,11 +147,17 @@ export function parseBncrInboundParams(params: BncrInboundParamsInput) {
|
|
|
63
147
|
|
|
64
148
|
const text = asString(params?.msg || '');
|
|
65
149
|
const msgType = asString(params?.type || 'text') || 'text';
|
|
150
|
+
const mediaItems = asInboundMediaItems(params);
|
|
66
151
|
const mediaBase64 = asString(params?.base64 || '');
|
|
67
152
|
const mediaPathFromTransfer = asString(params?.path || '').trim();
|
|
68
153
|
const mimeType = asString(params?.mimeType || '').trim() || undefined;
|
|
69
154
|
const fileName = asString(params?.fileName || '').trim() || undefined;
|
|
70
155
|
const msgId = asString(params?.msgId || '').trim() || undefined;
|
|
156
|
+
const shouldRespond = asBoolean(params?.shouldRespond, false);
|
|
157
|
+
const triggerKind = asString(params?.triggerKind || 'none').trim() || 'none';
|
|
158
|
+
const botName = asString(params?.botName || '').trim();
|
|
159
|
+
const isBotMentioned = asBoolean(params?.isBotMentioned, false);
|
|
160
|
+
const isReplyToBot = asBoolean(params?.isReplyToBot, false);
|
|
71
161
|
|
|
72
162
|
const dedupKey = inboundDedupKey({
|
|
73
163
|
accountId,
|
|
@@ -79,29 +169,44 @@ export function parseBncrInboundParams(params: BncrInboundParamsInput) {
|
|
|
79
169
|
mediaBase64,
|
|
80
170
|
});
|
|
81
171
|
|
|
172
|
+
const peerKind = resolveChatType(route, isGroup);
|
|
82
173
|
const peer = {
|
|
83
|
-
kind:
|
|
84
|
-
id:
|
|
174
|
+
kind: peerKind,
|
|
175
|
+
id: peerKind === 'group' ? route.groupId : route.userId,
|
|
85
176
|
} as const;
|
|
86
177
|
|
|
87
178
|
const extracted = extractInlineTaskKey(text);
|
|
88
179
|
|
|
89
180
|
return {
|
|
90
181
|
accountId,
|
|
182
|
+
protocolVersion,
|
|
183
|
+
capabilities,
|
|
91
184
|
platform,
|
|
92
185
|
groupId,
|
|
186
|
+
groupName,
|
|
93
187
|
userId,
|
|
188
|
+
userName,
|
|
94
189
|
sessionKeyfromroute,
|
|
95
190
|
providedOriginatingTo,
|
|
96
191
|
clientId,
|
|
192
|
+
bridgeId,
|
|
193
|
+
bridgeName,
|
|
194
|
+
isGroup,
|
|
195
|
+
isAdmin,
|
|
97
196
|
route,
|
|
98
197
|
text,
|
|
99
198
|
msgType,
|
|
199
|
+
mediaItems,
|
|
100
200
|
mediaBase64,
|
|
101
201
|
mediaPathFromTransfer,
|
|
102
202
|
mimeType,
|
|
103
203
|
fileName,
|
|
104
204
|
msgId,
|
|
205
|
+
shouldRespond,
|
|
206
|
+
triggerKind,
|
|
207
|
+
botName,
|
|
208
|
+
isBotMentioned,
|
|
209
|
+
isReplyToBot,
|
|
105
210
|
dedupKey,
|
|
106
211
|
peer,
|
|
107
212
|
extracted,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
2
|
+
|
|
3
|
+
const bncrReplyDispatchChains = new Map<string, Promise<void>>();
|
|
4
|
+
|
|
5
|
+
export async function runBncrReplyDispatchSerial<T>(
|
|
6
|
+
sessionKey: string,
|
|
7
|
+
task: () => Promise<T>,
|
|
8
|
+
meta?: { msgId?: string | null; to?: string | null; debugEnabled?: boolean },
|
|
9
|
+
) {
|
|
10
|
+
const key = String(sessionKey || '').trim();
|
|
11
|
+
if (!key) return await task();
|
|
12
|
+
|
|
13
|
+
const metaSuffix = `|msgId=${String(meta?.msgId || '-')}|to=${String(meta?.to || '-')}`;
|
|
14
|
+
|
|
15
|
+
const previous = bncrReplyDispatchChains.get(key) || Promise.resolve();
|
|
16
|
+
let release!: () => void;
|
|
17
|
+
const current = new Promise<void>((resolve) => {
|
|
18
|
+
release = resolve;
|
|
19
|
+
});
|
|
20
|
+
const chain = previous.then(() => current);
|
|
21
|
+
bncrReplyDispatchChains.set(key, chain);
|
|
22
|
+
|
|
23
|
+
const debugGate = () => meta?.debugEnabled === true;
|
|
24
|
+
|
|
25
|
+
emitBncrLogLine(
|
|
26
|
+
'info',
|
|
27
|
+
`[bncr] reply-serial queued|sessionKey=${key}${metaSuffix}`,
|
|
28
|
+
{ debugOnly: true },
|
|
29
|
+
debugGate,
|
|
30
|
+
);
|
|
31
|
+
await previous;
|
|
32
|
+
emitBncrLogLine(
|
|
33
|
+
'info',
|
|
34
|
+
`[bncr] reply-serial acquired|sessionKey=${key}${metaSuffix}`,
|
|
35
|
+
{ debugOnly: true },
|
|
36
|
+
debugGate,
|
|
37
|
+
);
|
|
38
|
+
try {
|
|
39
|
+
return await task();
|
|
40
|
+
} finally {
|
|
41
|
+
emitBncrLogLine(
|
|
42
|
+
'info',
|
|
43
|
+
`[bncr] reply-serial releasing|sessionKey=${key}${metaSuffix}`,
|
|
44
|
+
{ debugOnly: true },
|
|
45
|
+
debugGate,
|
|
46
|
+
);
|
|
47
|
+
release();
|
|
48
|
+
if (bncrReplyDispatchChains.get(key) === chain) {
|
|
49
|
+
bncrReplyDispatchChains.delete(key);
|
|
50
|
+
}
|
|
51
|
+
emitBncrLogLine(
|
|
52
|
+
'info',
|
|
53
|
+
`[bncr] reply-serial released|sessionKey=${key}${metaSuffix}`,
|
|
54
|
+
{ debugOnly: true },
|
|
55
|
+
debugGate,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resetBncrReplyDispatchSerialForTest() {
|
|
61
|
+
bncrReplyDispatchChains.clear();
|
|
62
|
+
}
|