borgmcp 3.16.1 → 4.0.0

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.
Files changed (51) hide show
  1. package/README.md +10 -0
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/dist/cli-help.d.ts.map +1 -1
  4. package/dist/cli-help.js +4 -1
  5. package/dist/cli-help.js.map +1 -1
  6. package/dist/direct-log.d.ts +2 -1
  7. package/dist/direct-log.d.ts.map +1 -1
  8. package/dist/direct-log.js +23 -9
  9. package/dist/direct-log.js.map +1 -1
  10. package/dist/docs-sections.js +3 -3
  11. package/dist/docs-sections.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +38 -13
  14. package/dist/index.js.map +1 -1
  15. package/dist/log-audit-core.d.ts.map +1 -1
  16. package/dist/log-audit-core.js +4 -2
  17. package/dist/log-audit-core.js.map +1 -1
  18. package/dist/regen-format.d.ts +6 -2
  19. package/dist/regen-format.d.ts.map +1 -1
  20. package/dist/regen-format.js +37 -13
  21. package/dist/regen-format.js.map +1 -1
  22. package/dist/remote-client.d.ts +12 -5
  23. package/dist/remote-client.d.ts.map +1 -1
  24. package/dist/remote-client.js +32 -33
  25. package/dist/remote-client.js.map +1 -1
  26. package/dist/server-handshake.d.ts +1 -1
  27. package/dist/tool-manifest.d.ts.map +1 -1
  28. package/dist/tool-manifest.js +55 -25
  29. package/dist/tool-manifest.js.map +1 -1
  30. package/dist/tool-scope.d.ts +1 -1
  31. package/dist/tool-scope.d.ts.map +1 -1
  32. package/dist/tool-scope.js +2 -0
  33. package/dist/tool-scope.js.map +1 -1
  34. package/docs/DOCUMENTS.md +4 -2
  35. package/docs/RELEASING.md +4 -2
  36. package/package.json +2 -2
  37. package/src/cli-help.ts +4 -1
  38. package/src/direct-log.ts +25 -8
  39. package/src/docs-sections.ts +3 -3
  40. package/src/index.ts +50 -12
  41. package/src/log-audit-core.ts +4 -2
  42. package/src/regen-format.ts +39 -13
  43. package/src/remote-client.ts +74 -54
  44. package/src/server-handshake.ts +1 -1
  45. package/src/tool-manifest.ts +57 -25
  46. package/src/tool-scope.ts +2 -0
  47. package/dist/local-log-routing.d.ts +0 -19
  48. package/dist/local-log-routing.d.ts.map +0 -1
  49. package/dist/local-log-routing.js +0 -67
  50. package/dist/local-log-routing.js.map +0 -1
  51. package/src/local-log-routing.ts +0 -106
@@ -208,7 +208,8 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
208
208
  'this reads oldest-unread-first from your server cursor and ' +
209
209
  'advances the watermark so bursts are not skipped. Optional `since` is a strict-after ' +
210
210
  'cursor for explicit bounded reads only; do not use it with the same timestamp as a ' +
211
- 'notification preview because it can skip the boundary entry.',
211
+ 'notification preview because it can skip the boundary entry. Use `borg_read-entry` ' +
212
+ 'to fetch one known entry without changing the unread cursor.',
212
213
  inputSchema: {
213
214
  type: 'object',
214
215
  properties: {
@@ -229,6 +230,22 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
229
230
  },
230
231
  },
231
232
  },
233
+ {
234
+ name: 'borg_read-entry',
235
+ description:
236
+ 'Read one complete activity-log entry by its canonical UUID or unique 8-hex prefix without changing the unread cursor. Returns the same entry shape and structured routing recipients as borg_read-log. Use borg_read-log unread_only=true for routine wake drains. Direct routing controls delivery and wakes, not read confidentiality inside the cube.',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ entry_id: {
241
+ type: 'string',
242
+ pattern: '^(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[1-8][0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})$',
243
+ description: 'Canonical activity-log entry UUID or unique 8-hex prefix.',
244
+ },
245
+ },
246
+ required: ['entry_id'],
247
+ },
248
+ },
232
249
  {
233
250
  name: 'borg_ack',
234
251
  description:
@@ -250,6 +267,22 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
250
267
  },
251
268
  },
252
269
  },
270
+ {
271
+ name: 'borg_ack-status',
272
+ description:
273
+ 'Read acknowledgement status for one activity-log entry without changing it. Returns per-recipient acknowledgements and advisory claims separately. Activity-log silence is not evidence that acknowledgement is missing; use this query instead. This read-only query does not acknowledge or claim the entry and preserves unread cursors.',
274
+ inputSchema: {
275
+ type: 'object',
276
+ required: ['entry_id'],
277
+ properties: {
278
+ entry_id: {
279
+ type: 'string',
280
+ format: 'uuid',
281
+ description: 'Full UUID of the activity-log entry to inspect.',
282
+ },
283
+ },
284
+ },
285
+ },
253
286
  {
254
287
  name: 'borg_decide',
255
288
  description:
@@ -369,26 +402,29 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
369
402
  {
370
403
  name: 'borg_log',
371
404
  description:
372
- 'Append a message to the cube\'s activity log. By default entries broadcast to all drones. Cite durable cube documents by passing their full ids in `documents`; citations carry current metadata but do not inline document content. When a cube declares a message taxonomy, borg_log applies class-based smart defaults: prefix-matched directed classes route to their default recipients unless you pass `to:`, `class`, or explicit visibility. Pass `to: [...]` to direct by exact drone label, drone id, the 8-hex short-uuid (the `id:` token shown in roster/read-log a drone_id prefix that is STABLE across label renumber), role name, or role slug.',
405
+ 'Append a message to the cube\'s activity log with an explicit audience. Every call must pass `to: "broadcast"` for all drones or a non-empty selector array for direct delivery. Selectors accept an exact drone label, drone id, the stable 8-hex `id:` token shown in roster/read-log, role name, or role slug. Message text and taxonomy classes never choose the audience; optional `class` records classification/lifecycle metadata only. Cite durable cube documents through `documents`; citations carry current metadata but do not inline document content. Direct routing controls delivery and wakes, not read confidentiality inside the cube.',
373
406
  inputSchema: {
374
407
  type: 'object',
375
408
  properties: {
376
409
  message: { type: 'string', description: 'The log message (max 10KB).' },
377
410
  to: {
378
- type: 'array',
379
- items: { type: 'string' },
411
+ oneOf: [
412
+ { type: 'string', enum: ['broadcast'] },
413
+ {
414
+ type: 'array',
415
+ items: { type: 'string', minLength: 1, maxLength: 120 },
416
+ minItems: 1,
417
+ maxItems: 100,
418
+ uniqueItems: true,
419
+ },
420
+ ],
380
421
  description:
381
- 'Optional direct-message recipients by exact drone label, drone id, the 8-hex short-uuid (`id:` token from roster/read-log; stable across label renumber), role name, or role slug (resolves to all drones in that role). Omit to let class-based routing or broadcast defaults apply.',
422
+ 'Required explicit audience: "broadcast" for every drone, or a non-empty array of exact drone labels, drone ids, stable 8-hex `id:` tokens, role names, or role slugs.',
382
423
  },
383
424
  class: {
384
425
  type: 'string',
385
426
  description:
386
- 'Optional declared message class. Overrides prefix auto-classification when the cube declares a message taxonomy.',
387
- },
388
- visibility: {
389
- type: 'string',
390
- enum: ['broadcast', 'direct'],
391
- description: 'Optional explicit visibility. Overrides class-based routing defaults.',
427
+ 'Optional declared message class for classification/lifecycle metadata. It never changes the required `to` audience.',
392
428
  },
393
429
  documents: {
394
430
  type: 'array',
@@ -399,7 +435,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
399
435
  description: 'Optional full opaque ids of 1-100 same-cube documents to cite atomically. Unknown, duplicate, or foreign ids are refused.',
400
436
  },
401
437
  },
402
- required: ['message'],
438
+ required: ['message', 'to'],
403
439
  },
404
440
  },
405
441
  {
@@ -440,14 +476,12 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
440
476
  cube_directive: { type: 'string', description: 'New cube directive markdown (optional).' },
441
477
  message_taxonomy: {
442
478
  type: 'array',
443
- description: 'New message-class taxonomy (optional). REPLACES the whole taxonomy; the worker re-validates the full array (non-overlapping prefixes, unique class names, directed classes need default_to). Pass [] to clear. To change ONE class without resending the whole array, use borg_patch-taxonomy-class instead. In default_to, pass @human-seat to route to drones in the cube human-seat role(s); literal role names/slugs/labels still work. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection.',
479
+ description: 'New classification/lifecycle taxonomy (optional). REPLACES the whole taxonomy; the server re-validates non-overlapping prefixes and unique class names. Pass [] to clear. To change ONE class without resending the whole array, use borg_patch-taxonomy-class instead. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection. Taxonomy never chooses log recipients.',
444
480
  items: {
445
481
  type: 'object',
446
482
  properties: {
447
483
  class: { type: 'string', description: 'Unique class name.' },
448
- prefixes: { type: 'array', items: { type: 'string' }, description: 'Message prefixes routed by this class.' },
449
- routing: { type: 'string', enum: ['broadcast', 'directed'], description: 'Routing mode.' },
450
- default_to: { type: 'array', items: { type: 'string' }, description: 'Default recipients (role name/slug/label, or @human-seat) for a directed class.' },
484
+ prefixes: { type: 'array', items: { type: 'string' }, description: 'Message prefixes classified by this class.' },
451
485
  lifecycle: { type: 'string', enum: ['dispatch', 'completion'], description: 'Optional lifecycle marker for stuck-dispatch detection.' },
452
486
  },
453
487
  },
@@ -459,7 +493,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
459
493
  {
460
494
  name: 'borg_patch-taxonomy-class',
461
495
  description:
462
- "Patch ONE message-class in a cube's message_taxonomy without resending the whole taxonomy (avoids clobbering). action=add|replace|remove (replace/remove match name case-insensitively). The full taxonomy is re-validated after the patch (non-overlapping prefixes, unique names, directed classes need default_to) — a patch that breaks a rule against an untouched class is rejected. In default_to, @human-seat routes to the cube's human-seat role(s); literal names/slugs/labels also work. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection.",
496
+ "Patch ONE classification/lifecycle class in a cube's message_taxonomy without resending the whole taxonomy (avoids clobbering). action=add|replace|remove (replace/remove match name case-insensitively). The full taxonomy is re-validated for non-overlapping prefixes and unique names. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection. Taxonomy never chooses log recipients.",
463
497
  inputSchema: {
464
498
  type: 'object',
465
499
  properties: {
@@ -467,15 +501,13 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
467
501
  action: { type: 'string', enum: ['add', 'replace', 'remove'], description: 'add / replace / remove a single class.' },
468
502
  class_def: {
469
503
  type: 'object',
470
- description: 'The class definition (for add/replace). Shape: { class, prefixes?, routing: "broadcast"|"directed", default_to?, lifecycle? }.',
504
+ description: 'The class definition (for add/replace). Shape: { class, prefixes?, lifecycle? }.',
471
505
  properties: {
472
506
  class: { type: 'string', description: 'Unique class name.' },
473
- prefixes: { type: 'array', items: { type: 'string' }, description: 'Message prefixes routed by this class.' },
474
- routing: { type: 'string', enum: ['broadcast', 'directed'], description: 'Routing mode.' },
475
- default_to: { type: 'array', items: { type: 'string' }, description: 'Default recipients (required for directed classes): role name/slug/label, or @human-seat.' },
507
+ prefixes: { type: 'array', items: { type: 'string' }, description: 'Message prefixes classified by this class.' },
476
508
  lifecycle: { type: 'string', enum: ['dispatch', 'completion'], description: 'Optional lifecycle marker for stuck-dispatch detection.' },
477
509
  },
478
- required: ['class', 'routing'],
510
+ required: ['class'],
479
511
  },
480
512
  class: { type: 'string', description: 'For remove only: the name of the class to drop (case-insensitive).' },
481
513
  },
@@ -508,7 +540,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
508
540
  is_mandatory: { type: 'boolean', description: 'If true, role-less assimilation prioritizes this unoccupied role before ordinary worker roles. Platform-wide management roles are never auto-assigned; a mandatory human-operator role is selected first until occupied.' },
509
541
  is_human_seat: { type: 'boolean', description: 'If true, this role represents the cube\'s human-occupied seat (where the human Queen sits directly). The class-hierarchy guard in reassign-drone allows promotion FROM a human-seat role TO the platform Queen role; promotion from non-human-seat roles is rejected.' },
510
542
  can_broadcast: { type: 'boolean', description: 'If true, drones in this role may post broadcast log entries when strict broadcast gating is enabled.' },
511
- receives_all_direct: { type: 'boolean', description: 'If true, drones in this role can see direct log entries as observer/audit recipients.' },
543
+ receives_all_direct: { type: 'boolean', description: 'If true, drones in this role are included as observer/audit recipients for every direct route.' },
512
544
  },
513
545
  required: ['cube_id', 'name', 'short_description', 'detailed_description'],
514
546
  },
@@ -527,7 +559,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
527
559
  is_mandatory: { type: 'boolean', description: 'Set true/false to prioritize this unoccupied role during role-less assimilation. Platform-wide management roles are never auto-assigned.' },
528
560
  is_human_seat: { type: 'boolean', description: 'Set true/false to mark/unmark this as the cube\'s human-occupied seat (the elevation source for the platform Queen role).' },
529
561
  can_broadcast: { type: 'boolean', description: 'Set true/false to allow or deny broadcast log entries when strict broadcast gating is enabled.' },
530
- receives_all_direct: { type: 'boolean', description: 'Set true/false to grant or remove observer visibility into direct log entries.' },
562
+ receives_all_direct: { type: 'boolean', description: 'Set true/false to include or remove this role as observer/audit recipients for every direct route.' },
531
563
  },
532
564
  required: ['role_id'],
533
565
  },
@@ -550,7 +582,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
550
582
  },
551
583
  {
552
584
  name: 'borg_delete-role',
553
- description: 'Delete a role using the selected local client\'s cube-management grant. Unknown or inaccessible roles refuse. Also refuses for the default, mandatory, or human-seat role; a role referenced by message-taxonomy routing; or a role assigned to an active drone. Reassign active drones with borg_reassign-drone or remove them with borg_evict-drone first. Evicted drones that held the deleted role are reassigned to the cube\'s default role; their activity-log attribution is unaffected.',
585
+ description: 'Delete a role using the selected local client\'s cube-management grant. Unknown or inaccessible roles refuse. Also refuses for the default, mandatory, or human-seat role, or a role assigned to an active drone. Reassign active drones with borg_reassign-drone or remove them with borg_evict-drone first. Evicted drones that held the deleted role are reassigned to the cube\'s default role; their activity-log attribution is unaffected.',
554
586
  inputSchema: {
555
587
  type: 'object',
556
588
  properties: {
package/src/tool-scope.ts CHANGED
@@ -20,10 +20,12 @@ export const UNIVERSAL_TOOLS = [
20
20
  'borg_regen',
21
21
  'borg_log',
22
22
  'borg_read-log',
23
+ 'borg_read-entry',
23
24
  'borg_roster',
24
25
  'borg_stream-status',
25
26
  'borg_whoami',
26
27
  'borg_ack',
28
+ 'borg_ack-status',
27
29
  'borg_version',
28
30
  'borg_cube',
29
31
  'borg_role',
@@ -1,19 +0,0 @@
1
- export interface LocalRoutingDrone {
2
- id: string;
3
- label?: string | null;
4
- role_id?: string | null;
5
- }
6
- export interface LocalRoutingRole {
7
- id: string;
8
- name: string;
9
- is_human_seat?: boolean;
10
- }
11
- /**
12
- * Resolve the public `borg_log to:` addressing forms against the local
13
- * server's authoritative cube roster. This intentionally mirrors the hosted
14
- * route resolver: exact drone id/label first, then displayed short UUID, then
15
- * role name/slug expansion. Every failure is closed before the log POST so a
16
- * miss can never degrade into a broadcast.
17
- */
18
- export declare function resolveLocalLogRecipients(recipients: readonly string[], drones: readonly LocalRoutingDrone[], roles: readonly LocalRoutingRole[]): string[];
19
- //# sourceMappingURL=local-log-routing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"local-log-routing.d.ts","sourceRoot":"","sources":["../src/local-log-routing.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,KAAK,EAAE,SAAS,gBAAgB,EAAE,GACjC,MAAM,EAAE,CAgFV"}
@@ -1,67 +0,0 @@
1
- import { roleSlug } from './role-resolver.js';
2
- /**
3
- * Resolve the public `borg_log to:` addressing forms against the local
4
- * server's authoritative cube roster. This intentionally mirrors the hosted
5
- * route resolver: exact drone id/label first, then displayed short UUID, then
6
- * role name/slug expansion. Every failure is closed before the log POST so a
7
- * miss can never degrade into a broadcast.
8
- */
9
- export function resolveLocalLogRecipients(recipients, drones, roles) {
10
- const ids = new Set();
11
- for (const recipient of recipients) {
12
- if (recipient === '@human-seat') {
13
- const roleIds = new Set(roles.filter((role) => role.is_human_seat === true).map((role) => role.id));
14
- const matches = drones.filter((drone) => drone.role_id !== null && drone.role_id !== undefined && roleIds.has(drone.role_id));
15
- if (roleIds.size === 0 || matches.length === 0) {
16
- throw new Error('Direct-message role recipient has no active drones: @human-seat. ' +
17
- 'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.');
18
- }
19
- for (const drone of matches)
20
- ids.add(drone.id);
21
- continue;
22
- }
23
- const droneMatches = drones.filter((drone) => drone.id === recipient || drone.label === recipient);
24
- const uniqueDroneIds = [...new Set(droneMatches.map((drone) => drone.id))];
25
- if (uniqueDroneIds.length > 1) {
26
- throw new Error(`Ambiguous direct-message recipient: ${recipient} matches multiple drones`);
27
- }
28
- if (uniqueDroneIds.length === 1) {
29
- ids.add(uniqueDroneIds[0]);
30
- continue;
31
- }
32
- const shortUuid = recipient.replace(/`/g, '').replace(/^id:/i, '').toLowerCase();
33
- if (/^[0-9a-f]{8,}$/.test(shortUuid)) {
34
- const prefixMatches = drones.filter((drone) => drone.id.toLowerCase().startsWith(shortUuid));
35
- const uniquePrefixIds = [...new Set(prefixMatches.map((drone) => drone.id))];
36
- if (uniquePrefixIds.length > 1) {
37
- const listed = prefixMatches
38
- .map((drone) => `${drone.id} (${drone.label ?? 'unlabeled'})`)
39
- .join(', ');
40
- throw new Error(`Ambiguous short-uuid recipient: ${recipient} matches multiple drones — ${listed}. ` +
41
- 'Address by the full drone id.');
42
- }
43
- if (uniquePrefixIds.length === 1) {
44
- ids.add(uniquePrefixIds[0]);
45
- continue;
46
- }
47
- }
48
- const roleMatches = roles.filter((role) => roleSlug(role.name) === roleSlug(recipient));
49
- const uniqueRoleIds = [...new Set(roleMatches.map((role) => role.id))];
50
- if (uniqueRoleIds.length > 1) {
51
- throw new Error(`Ambiguous direct-message recipient: ${recipient} matches multiple roles`);
52
- }
53
- if (uniqueRoleIds.length === 0) {
54
- throw new Error(`Unknown direct-message recipient: ${recipient}. ` +
55
- 'Use an exact drone label, drone id, role name, or role slug.');
56
- }
57
- const roleDrones = drones.filter((drone) => drone.role_id === uniqueRoleIds[0]);
58
- if (roleDrones.length === 0) {
59
- throw new Error(`Direct-message role recipient has no active drones: ${recipient}. ` +
60
- 'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.');
61
- }
62
- for (const drone of roleDrones)
63
- ids.add(drone.id);
64
- }
65
- return [...ids];
66
- }
67
- //# sourceMappingURL=local-log-routing.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"local-log-routing.js","sourceRoot":"","sources":["../src/local-log-routing.ts"],"names":[],"mappings":"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"}
@@ -1,106 +0,0 @@
1
- import { roleSlug } from './role-resolver.js';
2
-
3
- export interface LocalRoutingDrone {
4
- id: string;
5
- label?: string | null;
6
- role_id?: string | null;
7
- }
8
-
9
- export interface LocalRoutingRole {
10
- id: string;
11
- name: string;
12
- is_human_seat?: boolean;
13
- }
14
-
15
- /**
16
- * Resolve the public `borg_log to:` addressing forms against the local
17
- * server's authoritative cube roster. This intentionally mirrors the hosted
18
- * route resolver: exact drone id/label first, then displayed short UUID, then
19
- * role name/slug expansion. Every failure is closed before the log POST so a
20
- * miss can never degrade into a broadcast.
21
- */
22
- export function resolveLocalLogRecipients(
23
- recipients: readonly string[],
24
- drones: readonly LocalRoutingDrone[],
25
- roles: readonly LocalRoutingRole[],
26
- ): string[] {
27
- const ids = new Set<string>();
28
-
29
- for (const recipient of recipients) {
30
- if (recipient === '@human-seat') {
31
- const roleIds = new Set(
32
- roles.filter((role) => role.is_human_seat === true).map((role) => role.id),
33
- );
34
- const matches = drones.filter((drone) =>
35
- drone.role_id !== null && drone.role_id !== undefined && roleIds.has(drone.role_id)
36
- );
37
- if (roleIds.size === 0 || matches.length === 0) {
38
- throw new Error(
39
- 'Direct-message role recipient has no active drones: @human-seat. ' +
40
- 'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.',
41
- );
42
- }
43
- for (const drone of matches) ids.add(drone.id);
44
- continue;
45
- }
46
-
47
- const droneMatches = drones.filter(
48
- (drone) => drone.id === recipient || drone.label === recipient,
49
- );
50
- const uniqueDroneIds = [...new Set(droneMatches.map((drone) => drone.id))];
51
- if (uniqueDroneIds.length > 1) {
52
- throw new Error(
53
- `Ambiguous direct-message recipient: ${recipient} matches multiple drones`,
54
- );
55
- }
56
- if (uniqueDroneIds.length === 1) {
57
- ids.add(uniqueDroneIds[0]!);
58
- continue;
59
- }
60
-
61
- const shortUuid = recipient.replace(/`/g, '').replace(/^id:/i, '').toLowerCase();
62
- if (/^[0-9a-f]{8,}$/.test(shortUuid)) {
63
- const prefixMatches = drones.filter((drone) =>
64
- drone.id.toLowerCase().startsWith(shortUuid)
65
- );
66
- const uniquePrefixIds = [...new Set(prefixMatches.map((drone) => drone.id))];
67
- if (uniquePrefixIds.length > 1) {
68
- const listed = prefixMatches
69
- .map((drone) => `${drone.id} (${drone.label ?? 'unlabeled'})`)
70
- .join(', ');
71
- throw new Error(
72
- `Ambiguous short-uuid recipient: ${recipient} matches multiple drones — ${listed}. ` +
73
- 'Address by the full drone id.',
74
- );
75
- }
76
- if (uniquePrefixIds.length === 1) {
77
- ids.add(uniquePrefixIds[0]!);
78
- continue;
79
- }
80
- }
81
-
82
- const roleMatches = roles.filter((role) => roleSlug(role.name) === roleSlug(recipient));
83
- const uniqueRoleIds = [...new Set(roleMatches.map((role) => role.id))];
84
- if (uniqueRoleIds.length > 1) {
85
- throw new Error(
86
- `Ambiguous direct-message recipient: ${recipient} matches multiple roles`,
87
- );
88
- }
89
- if (uniqueRoleIds.length === 0) {
90
- throw new Error(
91
- `Unknown direct-message recipient: ${recipient}. ` +
92
- 'Use an exact drone label, drone id, role name, or role slug.',
93
- );
94
- }
95
- const roleDrones = drones.filter((drone) => drone.role_id === uniqueRoleIds[0]);
96
- if (roleDrones.length === 0) {
97
- throw new Error(
98
- `Direct-message role recipient has no active drones: ${recipient}. ` +
99
- 'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.',
100
- );
101
- }
102
- for (const drone of roleDrones) ids.add(drone.id);
103
- }
104
-
105
- return [...ids];
106
- }