@xmoxmo/bncr 0.4.6 → 0.4.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/channel.ts +41 -2
- package/src/core/targets.ts +106 -17
- package/src/messaging/inbound/commands.ts +263 -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 +92 -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 +269 -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 +34 -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 +55 -2
- package/src/plugin/inbound-surface-handlers-group.ts +16 -0
- package/src/plugin/messaging.ts +22 -5
- package/src/plugin/scene-registry.ts +155 -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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BncrGroupReplyMode,
|
|
3
|
+
BncrSceneRecord,
|
|
4
|
+
BncrSceneStatus,
|
|
5
|
+
} from '../../plugin/channel-runtime-types.ts';
|
|
6
|
+
import type { ParsedInbound } from './dispatch-prep.ts';
|
|
7
|
+
import type { NativeCommand } from './native-command.ts';
|
|
8
|
+
|
|
9
|
+
export type BncrSceneAdminCommand =
|
|
10
|
+
| { kind: 'allow'; sceneKey?: string }
|
|
11
|
+
| { kind: 'deny'; sceneKey?: string }
|
|
12
|
+
| { kind: 'revoke'; sceneKey?: string }
|
|
13
|
+
| { kind: 'bind'; sceneKey?: string; agentId: string }
|
|
14
|
+
| { kind: 'mode-help' }
|
|
15
|
+
| { kind: 'mode-get'; sceneKey?: string }
|
|
16
|
+
| { kind: 'mode'; sceneKey: string; mode: BncrGroupReplyMode }
|
|
17
|
+
| { kind: 'list'; scope: 'pending' | 'scenes' };
|
|
18
|
+
|
|
19
|
+
const GROUP_REPLY_MODES = new Set<BncrGroupReplyMode>(['admin', 'mention', 'hybrid', 'all']);
|
|
20
|
+
|
|
21
|
+
const MODE_HELP_TEXT = [
|
|
22
|
+
'💬 Group reply modes',
|
|
23
|
+
' • default: admin',
|
|
24
|
+
' • admin: 仅管理员|消息上送并逐条回复',
|
|
25
|
+
' • mention: 全员|消息上送 仅指定消息触发回复',
|
|
26
|
+
' • hybrid: 全员|消息上送 管理员逐条回复 其他人仅指定消息触发回复',
|
|
27
|
+
' • all: 全员|消息上送并逐条回复',
|
|
28
|
+
'',
|
|
29
|
+
'Specified messages include:',
|
|
30
|
+
' • @bot',
|
|
31
|
+
' • reply to bot',
|
|
32
|
+
' • platform-marked should-respond messages',
|
|
33
|
+
'',
|
|
34
|
+
'Usage:',
|
|
35
|
+
' • /bncr mode',
|
|
36
|
+
' • /bncr mode help',
|
|
37
|
+
' • /bncr mode <admin|mention|hybrid|all> [<platform>:<groupId>]',
|
|
38
|
+
].join('\n');
|
|
39
|
+
|
|
40
|
+
export type ParsedSceneAdminCommand =
|
|
41
|
+
| { matched: false }
|
|
42
|
+
| { matched: true; valid: false; text: string }
|
|
43
|
+
| { matched: true; valid: true; command: BncrSceneAdminCommand };
|
|
44
|
+
|
|
45
|
+
function normalizeToken(value: string): string {
|
|
46
|
+
return String(value || '').trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function splitArgs(raw: string): string[] {
|
|
50
|
+
return normalizeToken(raw)
|
|
51
|
+
.split(/\s+/)
|
|
52
|
+
.map((part) => part.trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveCurrentGroupSceneKey(parsed: ParsedInbound): string | null {
|
|
57
|
+
if (parsed.peer.kind !== 'group') return null;
|
|
58
|
+
const platform = normalizeToken(parsed.platform);
|
|
59
|
+
const groupId = normalizeToken(parsed.groupId);
|
|
60
|
+
if (!platform || !groupId || groupId === '0') return null;
|
|
61
|
+
return `${platform}:${groupId}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdminCommand {
|
|
65
|
+
const args = splitArgs(command.argsText);
|
|
66
|
+
switch (command.command) {
|
|
67
|
+
case 'allow':
|
|
68
|
+
return args.length <= 1
|
|
69
|
+
? { matched: true, valid: true, command: { kind: 'allow', sceneKey: args[0] } }
|
|
70
|
+
: { matched: true, valid: false, text: 'Usage: /bncr allow [<sceneKey>]' };
|
|
71
|
+
case 'deny':
|
|
72
|
+
return args.length <= 1
|
|
73
|
+
? { matched: true, valid: true, command: { kind: 'deny', sceneKey: args[0] } }
|
|
74
|
+
: { matched: true, valid: false, text: 'Usage: /bncr deny [<sceneKey>]' };
|
|
75
|
+
case 'revoke':
|
|
76
|
+
return args.length <= 1
|
|
77
|
+
? { matched: true, valid: true, command: { kind: 'revoke', sceneKey: args[0] } }
|
|
78
|
+
: { matched: true, valid: false, text: 'Usage: /bncr revoke [<sceneKey>]' };
|
|
79
|
+
case 'bind':
|
|
80
|
+
if (args.length === 1) {
|
|
81
|
+
return {
|
|
82
|
+
matched: true,
|
|
83
|
+
valid: true,
|
|
84
|
+
command: { kind: 'bind', agentId: args[0] },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (args.length === 2) {
|
|
88
|
+
return {
|
|
89
|
+
matched: true,
|
|
90
|
+
valid: true,
|
|
91
|
+
command: { kind: 'bind', agentId: args[0], sceneKey: args[1] },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
matched: true,
|
|
96
|
+
valid: false,
|
|
97
|
+
text: 'Usage: /bncr bind <agentId> [<sceneKey>]',
|
|
98
|
+
};
|
|
99
|
+
case 'mode':
|
|
100
|
+
if (args.length === 0) {
|
|
101
|
+
return { matched: true, valid: true, command: { kind: 'mode-get' } };
|
|
102
|
+
}
|
|
103
|
+
if (args.length === 1) {
|
|
104
|
+
if (args[0] === 'help') {
|
|
105
|
+
return {
|
|
106
|
+
matched: true,
|
|
107
|
+
valid: true,
|
|
108
|
+
command: { kind: 'mode-help' },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (GROUP_REPLY_MODES.has(args[0] as BncrGroupReplyMode)) {
|
|
112
|
+
return {
|
|
113
|
+
matched: true,
|
|
114
|
+
valid: true,
|
|
115
|
+
command: { kind: 'mode', sceneKey: '', mode: args[0] as BncrGroupReplyMode },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
matched: true,
|
|
120
|
+
valid: true,
|
|
121
|
+
command: { kind: 'mode-get', sceneKey: args[0] },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (GROUP_REPLY_MODES.has(args[0] as BncrGroupReplyMode) && args[1]) {
|
|
125
|
+
return {
|
|
126
|
+
matched: true,
|
|
127
|
+
valid: true,
|
|
128
|
+
command: { kind: 'mode', sceneKey: args[1], mode: args[0] as BncrGroupReplyMode },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
matched: true,
|
|
133
|
+
valid: false,
|
|
134
|
+
text: 'Usage: /bncr mode | /bncr mode <admin|mention|hybrid|all> [<sceneKey>]',
|
|
135
|
+
};
|
|
136
|
+
case 'list':
|
|
137
|
+
if (args[0] === 'pending') {
|
|
138
|
+
return { matched: true, valid: true, command: { kind: 'list', scope: 'pending' } };
|
|
139
|
+
}
|
|
140
|
+
if (args[0] === 'scenes') {
|
|
141
|
+
return { matched: true, valid: true, command: { kind: 'list', scope: 'scenes' } };
|
|
142
|
+
}
|
|
143
|
+
return { matched: true, valid: false, text: 'Usage: /bncr list <pending|scenes>' };
|
|
144
|
+
default:
|
|
145
|
+
return { matched: false };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function formatSceneLine(scene: BncrSceneRecord): string {
|
|
150
|
+
const idPart =
|
|
151
|
+
scene.kind === 'group' ? scene.groupId || scene.sceneKey : scene.userId || scene.sceneKey;
|
|
152
|
+
const namePart = scene.kind === 'group' ? scene.groupName || '' : scene.userName || '';
|
|
153
|
+
const agentPart = scene.agentId ? ` agent=${scene.agentId}` : '';
|
|
154
|
+
const modePart =
|
|
155
|
+
scene.kind === 'group' && scene.groupReplyMode ? ` mode=${scene.groupReplyMode}` : '';
|
|
156
|
+
const labelPart = namePart ? ` name=${namePart}` : '';
|
|
157
|
+
return `${scene.sceneKey} status=${scene.status} kind=${scene.kind} id=${idPart}${labelPart}${agentPart}${modePart}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function applySceneStatus(scene: BncrSceneRecord, status: BncrSceneStatus): BncrSceneRecord {
|
|
161
|
+
return {
|
|
162
|
+
...scene,
|
|
163
|
+
status,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function executeSceneAdminCommand(args: {
|
|
168
|
+
parsed: ParsedInbound;
|
|
169
|
+
command: BncrSceneAdminCommand;
|
|
170
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
171
|
+
defaultAdminAgentId: string;
|
|
172
|
+
defaultPublicAgentId: string;
|
|
173
|
+
now: () => number;
|
|
174
|
+
}): { ok: true; text: string } | { ok: false; text: string } {
|
|
175
|
+
const { parsed, command, sceneRegistry, defaultAdminAgentId, defaultPublicAgentId, now } = args;
|
|
176
|
+
|
|
177
|
+
if (!parsed.isAdmin) {
|
|
178
|
+
return { ok: false, text: 'Admin permission required.' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (command.kind === 'list') {
|
|
182
|
+
const scenes = Array.from(sceneRegistry.values())
|
|
183
|
+
.filter((scene) => (command.scope === 'pending' ? scene.status === 'pending' : true))
|
|
184
|
+
.sort((a, b) => a.lastSeenAt - b.lastSeenAt);
|
|
185
|
+
if (scenes.length === 0) {
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
text: command.scope === 'pending' ? 'No pending scenes.' : 'No scenes recorded.',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { ok: true, text: scenes.map(formatSceneLine).join('\n') };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (command.kind === 'mode-help') {
|
|
195
|
+
return { ok: true, text: MODE_HELP_TEXT };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (command.kind === 'mode-get') {
|
|
199
|
+
const sceneKey = command.sceneKey || resolveCurrentGroupSceneKey(parsed);
|
|
200
|
+
if (!sceneKey) {
|
|
201
|
+
return { ok: false, text: 'Current group mode query only works inside a group chat.' };
|
|
202
|
+
}
|
|
203
|
+
const existingScene = sceneRegistry.get(sceneKey);
|
|
204
|
+
if (!existingScene) {
|
|
205
|
+
return { ok: false, text: `Scene not found: ${sceneKey}` };
|
|
206
|
+
}
|
|
207
|
+
if (existingScene.kind !== 'group') {
|
|
208
|
+
return { ok: false, text: `Scene ${sceneKey} is not a group scene.` };
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
ok: true,
|
|
212
|
+
text: `Current ${sceneKey} reply mode is ${existingScene.groupReplyMode || 'admin'}.`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const sceneKey = command.sceneKey || resolveCurrentGroupSceneKey(parsed);
|
|
217
|
+
if (!sceneKey) {
|
|
218
|
+
return { ok: false, text: 'Current group shortcut only works inside a group chat.' };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const existing = sceneRegistry.get(sceneKey);
|
|
222
|
+
if (!existing) {
|
|
223
|
+
return { ok: false, text: `Scene not found: ${sceneKey}` };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (command.kind === 'revoke') {
|
|
227
|
+
sceneRegistry.delete(sceneKey);
|
|
228
|
+
return { ok: true, text: `Revoked scene ${sceneKey}.` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (command.kind === 'bind') {
|
|
232
|
+
sceneRegistry.set(sceneKey, {
|
|
233
|
+
...existing,
|
|
234
|
+
agentId: command.agentId,
|
|
235
|
+
lastSeenAt: now(),
|
|
236
|
+
});
|
|
237
|
+
return { ok: true, text: `Bound ${sceneKey} to agent ${command.agentId}.` };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (command.kind === 'mode') {
|
|
241
|
+
if (existing.kind !== 'group') {
|
|
242
|
+
return { ok: false, text: `Scene ${sceneKey} is not a group scene.` };
|
|
243
|
+
}
|
|
244
|
+
sceneRegistry.set(sceneKey, {
|
|
245
|
+
...existing,
|
|
246
|
+
groupReplyMode: command.mode,
|
|
247
|
+
lastSeenAt: now(),
|
|
248
|
+
});
|
|
249
|
+
return { ok: true, text: `Set ${sceneKey} reply mode to ${command.mode}.` };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const fallbackAgentId =
|
|
253
|
+
existing.kind === 'group'
|
|
254
|
+
? defaultPublicAgentId
|
|
255
|
+
: parsed.isAdmin
|
|
256
|
+
? defaultAdminAgentId
|
|
257
|
+
: defaultPublicAgentId;
|
|
258
|
+
const next = applySceneStatus(existing, command.kind === 'allow' ? 'allowed' : 'denied');
|
|
259
|
+
sceneRegistry.set(sceneKey, {
|
|
260
|
+
...next,
|
|
261
|
+
...(command.kind === 'allow' ? { agentId: existing.agentId || fallbackAgentId } : {}),
|
|
262
|
+
lastSeenAt: now(),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
ok: true,
|
|
267
|
+
text: command.kind === 'allow' ? `Allowed scene ${sceneKey}.` : `Denied scene ${sceneKey}.`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
@@ -5,6 +5,37 @@ import {
|
|
|
5
5
|
updateBncrSessionStoreEntry,
|
|
6
6
|
} from '../../openclaw/inbound-session-runtime.ts';
|
|
7
7
|
|
|
8
|
+
function formatBncrSessionLabel(displayTo: string): string {
|
|
9
|
+
const raw = String(displayTo || '').trim();
|
|
10
|
+
const parts = raw.split(':');
|
|
11
|
+
if (parts.length === 4 && parts[0] === 'Bncr') {
|
|
12
|
+
const [, platform, groupId, userId] = parts;
|
|
13
|
+
if (platform && groupId && groupId !== '0') return `Bncr:${platform}:Group:${groupId}`;
|
|
14
|
+
if (platform && userId && userId !== '0') return `Bncr:${platform}:User:${userId}`;
|
|
15
|
+
}
|
|
16
|
+
return raw;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function formatBncrSessionRouteTarget(displayTo: string): string {
|
|
20
|
+
const raw = String(displayTo || '').trim();
|
|
21
|
+
const parts = raw.split(':');
|
|
22
|
+
if (parts.length === 4 && parts[0] === 'Bncr') {
|
|
23
|
+
const [, platform, groupId, userId] = parts;
|
|
24
|
+
if (platform && groupId && groupId !== '0') return `Bncr:${platform}:${groupId}:0`;
|
|
25
|
+
if (platform && userId && userId !== '0') return `Bncr:${platform}:0:${userId}`;
|
|
26
|
+
}
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatBncrSessionGroupKey(displayTo: string): string | null {
|
|
31
|
+
const raw = String(displayTo || '').trim();
|
|
32
|
+
const parts = raw.split(':');
|
|
33
|
+
if (parts.length !== 4 || parts[0] !== 'Bncr') return null;
|
|
34
|
+
const [, platform, groupId] = parts;
|
|
35
|
+
if (!platform || !groupId || groupId === '0') return null;
|
|
36
|
+
return `${platform.toLowerCase()}:${groupId}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
8
39
|
type RecordInboundSessionFn = (args: {
|
|
9
40
|
storePath?: string;
|
|
10
41
|
sessionKey?: string;
|
|
@@ -21,30 +52,35 @@ export function buildBncrInboundSessionIdentityPatch(args: {
|
|
|
21
52
|
senderId: string;
|
|
22
53
|
}) {
|
|
23
54
|
const { channelId, accountId, chatType, displayTo, senderId } = args;
|
|
55
|
+
const displayLabel = formatBncrSessionLabel(displayTo);
|
|
56
|
+
const routeTarget = formatBncrSessionRouteTarget(displayTo);
|
|
57
|
+
const groupKey = chatType === 'group' ? formatBncrSessionGroupKey(displayTo) : null;
|
|
24
58
|
return {
|
|
25
|
-
label:
|
|
59
|
+
label: displayLabel,
|
|
60
|
+
displayName: displayLabel,
|
|
26
61
|
channel: channelId,
|
|
27
62
|
chatType,
|
|
63
|
+
...(groupKey ? { groupId: groupKey } : {}),
|
|
28
64
|
origin: {
|
|
29
|
-
label:
|
|
65
|
+
label: displayLabel,
|
|
30
66
|
provider: channelId,
|
|
31
67
|
surface: channelId,
|
|
32
68
|
chatType,
|
|
33
69
|
from: senderId,
|
|
34
|
-
to:
|
|
70
|
+
to: routeTarget,
|
|
35
71
|
accountId,
|
|
36
72
|
},
|
|
37
73
|
deliveryContext: {
|
|
38
74
|
channel: channelId,
|
|
39
|
-
to:
|
|
75
|
+
to: routeTarget,
|
|
40
76
|
accountId,
|
|
41
77
|
},
|
|
42
78
|
route: {
|
|
43
79
|
channel: channelId,
|
|
44
80
|
accountId,
|
|
45
|
-
target: { to:
|
|
81
|
+
target: { to: routeTarget },
|
|
46
82
|
},
|
|
47
|
-
lastTo:
|
|
83
|
+
lastTo: routeTarget,
|
|
48
84
|
};
|
|
49
85
|
}
|
|
50
86
|
|
|
@@ -56,20 +92,93 @@ function normalizeNonEmptyString(value: unknown): string | null {
|
|
|
56
92
|
export async function correctBncrInboundSessionLabel(args: {
|
|
57
93
|
storePath: string;
|
|
58
94
|
sessionKey: string;
|
|
59
|
-
|
|
95
|
+
expectedPatch: Record<string, unknown>;
|
|
60
96
|
}) {
|
|
61
97
|
const storePath = normalizeNonEmptyString(args.storePath);
|
|
62
98
|
const sessionKey = normalizeNonEmptyString(args.sessionKey);
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
if (!storePath || !sessionKey) return;
|
|
100
|
+
|
|
101
|
+
const expectedPatch = args.expectedPatch;
|
|
102
|
+
const expectedLabel = normalizeNonEmptyString(expectedPatch.label);
|
|
103
|
+
const expectedDisplayName = normalizeNonEmptyString(expectedPatch.displayName);
|
|
104
|
+
const expectedGroupId = normalizeNonEmptyString(expectedPatch.groupId);
|
|
105
|
+
const expectedOriginTo = normalizeNonEmptyString(
|
|
106
|
+
(expectedPatch.origin as { to?: unknown } | undefined)?.to,
|
|
107
|
+
);
|
|
108
|
+
const expectedDeliveryTo = normalizeNonEmptyString(
|
|
109
|
+
(expectedPatch.deliveryContext as { to?: unknown } | undefined)?.to,
|
|
110
|
+
);
|
|
111
|
+
const expectedRouteTo = normalizeNonEmptyString(
|
|
112
|
+
(
|
|
113
|
+
(expectedPatch.route as { target?: { to?: unknown } } | undefined)?.target as
|
|
114
|
+
| { to?: unknown }
|
|
115
|
+
| undefined
|
|
116
|
+
)?.to,
|
|
117
|
+
);
|
|
118
|
+
const expectedLastTo = normalizeNonEmptyString(expectedPatch.lastTo);
|
|
119
|
+
if (
|
|
120
|
+
!expectedLabel ||
|
|
121
|
+
!expectedDisplayName ||
|
|
122
|
+
!expectedOriginTo ||
|
|
123
|
+
!expectedDeliveryTo ||
|
|
124
|
+
!expectedRouteTo ||
|
|
125
|
+
!expectedLastTo
|
|
126
|
+
)
|
|
127
|
+
return;
|
|
65
128
|
|
|
66
129
|
try {
|
|
67
130
|
await updateBncrSessionStoreEntry({
|
|
68
131
|
storePath,
|
|
69
132
|
sessionKey,
|
|
70
133
|
update: (entry: SessionStoreEntryLike) => {
|
|
71
|
-
|
|
72
|
-
|
|
134
|
+
const origin =
|
|
135
|
+
entry?.origin && typeof entry.origin === 'object'
|
|
136
|
+
? (entry.origin as Record<string, unknown>)
|
|
137
|
+
: {};
|
|
138
|
+
const deliveryContext =
|
|
139
|
+
entry?.deliveryContext && typeof entry.deliveryContext === 'object'
|
|
140
|
+
? (entry.deliveryContext as Record<string, unknown>)
|
|
141
|
+
: {};
|
|
142
|
+
const route =
|
|
143
|
+
entry?.route && typeof entry.route === 'object'
|
|
144
|
+
? (entry.route as Record<string, unknown>)
|
|
145
|
+
: {};
|
|
146
|
+
const routeTarget =
|
|
147
|
+
route.target && typeof route.target === 'object'
|
|
148
|
+
? (route.target as Record<string, unknown>)
|
|
149
|
+
: {};
|
|
150
|
+
|
|
151
|
+
const unchanged =
|
|
152
|
+
entry?.label === expectedLabel &&
|
|
153
|
+
entry?.displayName === expectedDisplayName &&
|
|
154
|
+
normalizeNonEmptyString(entry?.groupId) === expectedGroupId &&
|
|
155
|
+
normalizeNonEmptyString(origin.to) === expectedOriginTo &&
|
|
156
|
+
normalizeNonEmptyString(deliveryContext.to) === expectedDeliveryTo &&
|
|
157
|
+
normalizeNonEmptyString(routeTarget.to) === expectedRouteTo &&
|
|
158
|
+
normalizeNonEmptyString(entry?.lastTo) === expectedLastTo;
|
|
159
|
+
if (unchanged) return null;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...(expectedGroupId ? { groupId: expectedGroupId } : {}),
|
|
163
|
+
label: expectedLabel,
|
|
164
|
+
displayName: expectedDisplayName,
|
|
165
|
+
origin: {
|
|
166
|
+
...origin,
|
|
167
|
+
...(expectedPatch.origin as Record<string, unknown>),
|
|
168
|
+
},
|
|
169
|
+
deliveryContext: {
|
|
170
|
+
...deliveryContext,
|
|
171
|
+
...(expectedPatch.deliveryContext as Record<string, unknown>),
|
|
172
|
+
},
|
|
173
|
+
route: {
|
|
174
|
+
...route,
|
|
175
|
+
...(expectedPatch.route as Record<string, unknown>),
|
|
176
|
+
target: {
|
|
177
|
+
to: expectedRouteTo,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
lastTo: expectedLastTo,
|
|
181
|
+
};
|
|
73
182
|
},
|
|
74
183
|
});
|
|
75
184
|
} catch (err) {
|
|
@@ -108,7 +217,7 @@ export async function recordAndPatchBncrInboundSessionEntry(args: {
|
|
|
108
217
|
|
|
109
218
|
export function wrapBncrInboundRecordSessionLabelCorrection(args: {
|
|
110
219
|
recordInboundSession: RecordInboundSessionFn;
|
|
111
|
-
|
|
220
|
+
expectedPatch: Record<string, unknown>;
|
|
112
221
|
}): RecordInboundSessionFn {
|
|
113
222
|
return async (recordArgs) => {
|
|
114
223
|
const result = await args.recordInboundSession(recordArgs);
|
|
@@ -116,7 +225,7 @@ export function wrapBncrInboundRecordSessionLabelCorrection(args: {
|
|
|
116
225
|
await correctBncrInboundSessionLabel({
|
|
117
226
|
storePath: recordArgs.storePath,
|
|
118
227
|
sessionKey: recordArgs.sessionKey,
|
|
119
|
-
|
|
228
|
+
expectedPatch: args.expectedPatch,
|
|
120
229
|
});
|
|
121
230
|
return result;
|
|
122
231
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createBncrSessionMetaTaskBarrier() {
|
|
2
|
+
const pending = new Set<Promise<unknown>>();
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
track(task: Promise<unknown>) {
|
|
6
|
+
if (!task || typeof task.then !== 'function') return;
|
|
7
|
+
pending.add(task);
|
|
8
|
+
void task.finally(() => {
|
|
9
|
+
pending.delete(task);
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
async wait() {
|
|
13
|
+
if (pending.size === 0) return;
|
|
14
|
+
await Promise.allSettled([...pending]);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|