@xmoxmo/bncr 0.5.2 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,6 +17,7 @@ export type BncrStructuredContextFactsInput = {
17
17
  reply: {
18
18
  to: string;
19
19
  originatingTo: string;
20
+ rawTo?: string;
20
21
  };
21
22
  sender: {
22
23
  id: string;
@@ -125,6 +126,7 @@ export function buildBncrStructuredContextFacts(input: BncrStructuredContextFact
125
126
  reply: {
126
127
  to: input.reply.to,
127
128
  originatingTo: input.reply.originatingTo,
129
+ ...(input.reply.rawTo ? { rawTo: input.reply.rawTo } : {}),
128
130
  },
129
131
  sender: {
130
132
  id: input.sender.id,
@@ -187,6 +189,7 @@ export function buildBncrPromptVisibleContextFacts(
187
189
  facts: ReturnType<typeof buildBncrStructuredContextFacts>,
188
190
  ) {
189
191
  const result: {
192
+ platform?: string;
190
193
  trigger?: {
191
194
  botName?: string;
192
195
  };
@@ -199,6 +202,7 @@ export function buildBncrPromptVisibleContextFacts(
199
202
  reply?: {
200
203
  to: string;
201
204
  originatingTo: string;
205
+ rawTo?: string;
202
206
  };
203
207
  media?: Array<{
204
208
  path?: string;
@@ -220,6 +224,10 @@ export function buildBncrPromptVisibleContextFacts(
220
224
  }>;
221
225
  } = {};
222
226
 
227
+ if (facts.platform) {
228
+ result.platform = `${facts.channel.id}/${facts.platform}`;
229
+ }
230
+
223
231
  if (
224
232
  facts.sender.isAdmin === true ||
225
233
  facts.sender.isOwner === true ||
@@ -244,10 +252,11 @@ export function buildBncrPromptVisibleContextFacts(
244
252
  };
245
253
  }
246
254
 
247
- if (facts.reply.originatingTo !== facts.reply.to) {
255
+ if (facts.reply.originatingTo !== facts.reply.to || facts.reply.rawTo) {
248
256
  result.reply = {
249
257
  to: facts.reply.to,
250
258
  originatingTo: facts.reply.originatingTo,
259
+ ...(facts.reply.rawTo ? { rawTo: facts.reply.rawTo } : {}),
251
260
  };
252
261
  }
253
262
 
@@ -326,6 +335,7 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
326
335
  resolution: {
327
336
  chatType: string;
328
337
  canonicalTo: string;
338
+ rawTo: string;
329
339
  originatingTo: string;
330
340
  resolvedRoute: {
331
341
  agentId?: string;
@@ -384,6 +394,7 @@ export function buildBncrStructuredContextFactsFromInboundParts(
384
394
  reply: {
385
395
  to: input.resolution.canonicalTo,
386
396
  originatingTo: input.resolution.originatingTo,
397
+ rawTo: input.resolution.rawTo,
387
398
  },
388
399
  sender: {
389
400
  id: input.senderIdForContext,
@@ -48,6 +48,7 @@ export type BncrInboundReplyRouteFact = {
48
48
  sessionKey: string;
49
49
  route: ParsedInbound['route'];
50
50
  canonicalTo: string;
51
+ rawTo: string;
51
52
  originatingTo: string;
52
53
  chatType: 'direct' | 'group';
53
54
  };
@@ -270,7 +271,7 @@ export function resolveBncrInboundConversation(args: {
270
271
  const dispatchSessionKey = taskSessionKey || baseSessionKey;
271
272
  const rawTo = formatRawBncrInboundTarget(route);
272
273
  const canonicalTo = formatDisplayScope(route);
273
- const originatingTo = providedOriginatingTo || rawTo;
274
+ const originatingTo = providedOriginatingTo || canonicalTo;
274
275
 
275
276
  return {
276
277
  accountId,
@@ -419,6 +420,7 @@ export function buildBncrInboundReplyRouteFact(
419
420
  sessionKey: resolution.dispatchSessionKey,
420
421
  route: resolution.route,
421
422
  canonicalTo: resolution.canonicalTo,
423
+ rawTo: resolution.rawTo,
422
424
  originatingTo: resolution.originatingTo,
423
425
  chatType: resolution.chatType,
424
426
  };
@@ -52,8 +52,8 @@ const BNCR_HELP_TEXT = [
52
52
  ' • /bncr mode help',
53
53
  ' • /bncr mode <admin|mention|hybrid|all> [<SceneId>]',
54
54
  ' • /bncr revoke [<SceneId>]',
55
- ' • /bncr list pending',
56
- ' • /bncr list scenes',
55
+ ' • /bncr list pending [filters...]',
56
+ ' • /bncr list scenes [filters...]',
57
57
  '',
58
58
  '📋 Group history',
59
59
  ' • /bncr history-help',
@@ -14,7 +14,7 @@ export type BncrSceneAdminCommand =
14
14
  | { kind: 'mode-help' }
15
15
  | { kind: 'mode-get'; sceneKey?: string }
16
16
  | { kind: 'mode'; sceneKey: string; mode: BncrGroupReplyMode }
17
- | { kind: 'list'; scope: 'pending' | 'scenes' }
17
+ | { kind: 'list'; scope: 'pending' | 'scenes'; filters?: string[] }
18
18
  | { kind: 'history-limit-get'; sceneKey?: string }
19
19
  | { kind: 'history-limit-set'; sceneKey: string; limit: number }
20
20
  | { kind: 'history-force-get'; sceneKey?: string }
@@ -69,6 +69,11 @@ function splitArgs(raw: string): string[] {
69
69
  .filter(Boolean);
70
70
  }
71
71
 
72
+ function normalizeFilters(args: string[]): string[] | undefined {
73
+ const filters = args.map((part) => normalizeToken(part).toLowerCase()).filter(Boolean);
74
+ return filters.length > 0 ? filters : undefined;
75
+ }
76
+
72
77
  function resolveCurrentGroupSceneKey(parsed: ParsedInbound): string | null {
73
78
  if (parsed.peer.kind !== 'group') return null;
74
79
  const platform = normalizeToken(parsed.platform);
@@ -151,12 +156,26 @@ export function parseSceneAdminCommand(command: NativeCommand): ParsedSceneAdmin
151
156
  };
152
157
  case 'list':
153
158
  if (args[0] === 'pending') {
154
- return { matched: true, valid: true, command: { kind: 'list', scope: 'pending' } };
159
+ const filters = normalizeFilters(args.slice(1));
160
+ return {
161
+ matched: true,
162
+ valid: true,
163
+ command: { kind: 'list', scope: 'pending', ...(filters ? { filters } : {}) },
164
+ };
155
165
  }
156
166
  if (args[0] === 'scenes') {
157
- return { matched: true, valid: true, command: { kind: 'list', scope: 'scenes' } };
167
+ const filters = normalizeFilters(args.slice(1));
168
+ return {
169
+ matched: true,
170
+ valid: true,
171
+ command: { kind: 'list', scope: 'scenes', ...(filters ? { filters } : {}) },
172
+ };
158
173
  }
159
- return { matched: true, valid: false, text: 'Usage: /bncr list <pending|scenes>' };
174
+ return {
175
+ matched: true,
176
+ valid: false,
177
+ text: 'Usage: /bncr list <pending|scenes> [filters...]',
178
+ };
160
179
  case 'history-limit':
161
180
  if (args.length === 0) {
162
181
  return { matched: true, valid: true, command: { kind: 'history-limit-get' } };
@@ -271,6 +290,30 @@ function formatSceneGroups(scenes: BncrSceneRecord[]): string {
271
290
  .join('\n\n');
272
291
  }
273
292
 
293
+ function buildSceneSearchHaystack(scene: BncrSceneRecord): string[] {
294
+ const parts: string[] = [
295
+ normalizeToken(scene.sceneKey),
296
+ normalizeToken(scene.platform),
297
+ normalizeToken(scene.status),
298
+ normalizeToken(scene.agentId ?? ''),
299
+ normalizeToken(scene.kind),
300
+ normalizeToken(scene.userId ?? ''),
301
+ normalizeToken(scene.userName ?? ''),
302
+ normalizeToken(scene.groupId ?? ''),
303
+ normalizeToken(scene.groupName ?? ''),
304
+ ];
305
+ if (scene.kind === 'group') {
306
+ parts.push(scene.groupReplyMode || 'admin');
307
+ }
308
+ return parts.map((part) => part.toLowerCase()).filter(Boolean);
309
+ }
310
+
311
+ function matchesSceneFilters(scene: BncrSceneRecord, filters: string[] | undefined): boolean {
312
+ if (!filters?.length) return true;
313
+ const haystack = buildSceneSearchHaystack(scene);
314
+ return filters.every((filter) => haystack.some((part) => part.includes(filter)));
315
+ }
316
+
274
317
  function applySceneStatus(scene: BncrSceneRecord, status: BncrSceneStatus): BncrSceneRecord {
275
318
  return {
276
319
  ...scene,
@@ -299,11 +342,19 @@ export function executeSceneAdminCommand(args: {
299
342
  if (command.kind === 'list') {
300
343
  const scenes = Array.from(sceneRegistry.values())
301
344
  .filter((scene) => (command.scope === 'pending' ? scene.status === 'pending' : true))
345
+ .filter((scene) => matchesSceneFilters(scene, command.filters))
302
346
  .sort((a, b) => a.lastSeenAt - b.lastSeenAt);
303
347
  if (scenes.length === 0) {
304
348
  return {
305
349
  ok: true,
306
- text: command.scope === 'pending' ? 'No pending scenes.' : 'No scenes recorded.',
350
+ text:
351
+ command.scope === 'pending'
352
+ ? command.filters?.length
353
+ ? `No pending scenes matched: ${command.filters.join(' ')}`
354
+ : 'No pending scenes.'
355
+ : command.filters?.length
356
+ ? `No scenes matched: ${command.filters.join(' ')}`
357
+ : 'No scenes recorded.',
307
358
  };
308
359
  }
309
360
  return { ok: true, text: formatSceneGroups(scenes) };
@@ -220,6 +220,7 @@ export function buildBncrInboundTurnContext(args: {
220
220
  reply: {
221
221
  to: resolution.canonicalTo,
222
222
  originatingTo: resolution.originatingTo,
223
+ rawTo: resolution.rawTo,
223
224
  },
224
225
  message: {
225
226
  inboundEventKind: 'user_request',
@@ -265,6 +266,7 @@ export function buildBncrInboundTurnContext(args: {
265
266
  },
266
267
  extra: {
267
268
  OriginatingChannel: channelId,
269
+ ...(parsed.isGroup === true && parsed.groupName ? { GroupSubject: parsed.groupName } : {}),
268
270
  ...(ownerAllowFrom?.length ? { OwnerAllowFrom: ownerAllowFrom } : {}),
269
271
  BncrStructuredContextFacts: structuredContextFacts,
270
272
  StructuredContextFacts: structuredContextFacts,