@pinet/broker-core 0.2.12 → 0.2.13

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.
@@ -2,6 +2,7 @@ import type { TransportJsonObject, TransportRichBlock } from "@pinet/transport-c
2
2
  import type { BrokerMessage, MessageAdapter, NormalizedMessageContent, OutboundAttachmentFile, ThreadInfo } from "./types.js";
3
3
  export interface BrokerMessageSenderDb {
4
4
  getThread(threadId: string): ThreadInfo | null;
5
+ getDocumentRecipients?(documentId: string): string[];
5
6
  createThread(threadId: string, source: string, channel: string, ownerAgent: string | null): ThreadInfo;
6
7
  updateThread(threadId: string, updates: Partial<ThreadInfo>): void;
7
8
  claimThread(threadId: string, agentId: string, source?: string, channel?: string): boolean;
@@ -67,7 +67,14 @@ export async function sendBrokerMessage(deps, input) {
67
67
  ...(input.metadata ? { metadata: input.metadata } : {}),
68
68
  };
69
69
  await adapter.send(outbound);
70
- const message = deps.db.insertMessage(threadId, source, "outbound", input.senderAgentId, messageBody, [], input.metadata);
70
+ const documentId = typeof thread.metadata?.documentId === "string" ? thread.metadata.documentId : null;
71
+ const documentRecipients = documentId
72
+ ? (deps.db.getDocumentRecipients?.(documentId) ?? []).filter((agentId) => agentId !== input.senderAgentId)
73
+ : [];
74
+ const message = deps.db.insertMessage(threadId, source, "outbound", input.senderAgentId, messageBody, [...new Set(documentRecipients)], {
75
+ ...(input.metadata ?? {}),
76
+ ...(documentId ? { documentId } : {}),
77
+ });
71
78
  return {
72
79
  thread,
73
80
  message,
package/dist/router.js CHANGED
@@ -236,6 +236,27 @@ export class MessageRouter {
236
236
  return { action: "deliver", agentId: explicitDirective.agent.id };
237
237
  }
238
238
  }
239
+ const messageDocumentId = typeof msg.metadata?.documentId === "string" ? msg.metadata.documentId : null;
240
+ const threadDocumentId = typeof thread?.metadata?.documentId === "string" ? thread.metadata.documentId : null;
241
+ const documentId = messageDocumentId ?? threadDocumentId;
242
+ if (documentId) {
243
+ const document = this.db.getDocument?.(documentId) ?? null;
244
+ if (thread?.ownerAgent &&
245
+ document &&
246
+ document.ownerAgent !== thread.ownerAgent &&
247
+ this.db.setDocumentOwner) {
248
+ this.db.setDocumentOwner(documentId, thread.ownerAgent);
249
+ }
250
+ const recipientIds = (this.db.getDocumentRecipients?.(documentId) ?? [])
251
+ .map((agentId) => resolveRoutableThreadOwner(this.db, agentId))
252
+ .filter((agent) => agent !== null)
253
+ .map((agent) => agent.id);
254
+ const recipients = [...new Set(recipientIds)];
255
+ if (recipients.length === 1)
256
+ return { action: "deliver", agentId: recipients[0] };
257
+ if (recipients.length > 1)
258
+ return { action: "broadcast", agentIds: recipients };
259
+ }
239
260
  if (thread) {
240
261
  if (thread.ownerBinding === "explicit") {
241
262
  const explicitOwner = resolveRoutableThreadOwner(this.db, thread.ownerAgent);
package/dist/schema.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
2
  import type { PinetMailClass } from "./mail-classification.js";
3
- import type { AgentInfo, ThreadInfo, BrokerMessage, InboxEntry, InboxReadOptions, InboxReadResult, InboxThreadUnreadSummary, DeliveredInboundMessageResult, BacklogEntry, BrokerDBInterface, InboundMessage, ChannelAssignment, TaskAssignmentInfo, TaskAssignmentKind, TaskAssignmentStatus, ScheduledWakeupInfo, ScheduledWakeupDelivery, PortLeaseAcquireInput, PortLeaseInfo, PortLeaseListOptions, PortLeaseReleaseInput, PortLeaseRenewInput, AgentSessionSearchInfo, AgentSessionSearchOptions, PinetLaneInfo, PinetLaneListOptions, PinetLaneParticipantInfo, PinetLaneParticipantUpsertInput, PinetLaneUpsertInput, AgentHibernatePolicy, AgentLifecycleLease, AgentLifecycleOperation, AgentLifecycleTransitionInput, AgentLifecycleRetentionInfo, AgentRuntimeSpec, AgentRuntimeSpecInput, AgentCheckpointReceipt, AgentCheckpointReceiptInput, AgentWakeReservation, AgentWakeAcceptanceReceipt, AcceptRuntimeGenerationInput, RuntimeGenerationAcceptance, AgentWakeQueueEntry, EnqueueWakeInput, AgentLifecycleEvent, AgentLifecycleEventInput } from "./types.js";
3
+ import type { AgentInfo, ThreadInfo, DocumentAliasInfo, DocumentInfo, BrokerMessage, InboxEntry, InboxReadOptions, InboxReadResult, InboxThreadUnreadSummary, DeliveredInboundMessageResult, BacklogEntry, BrokerDBInterface, InboundMessage, ChannelAssignment, TaskAssignmentInfo, TaskAssignmentKind, TaskAssignmentStatus, ScheduledWakeupInfo, ScheduledWakeupDelivery, PortLeaseAcquireInput, PortLeaseInfo, PortLeaseListOptions, PortLeaseReleaseInput, PortLeaseRenewInput, AgentSessionSearchInfo, AgentSessionSearchOptions, PinetLaneInfo, PinetLaneListOptions, PinetLaneParticipantInfo, PinetLaneParticipantUpsertInput, PinetLaneUpsertInput, AgentHibernatePolicy, AgentLifecycleLease, AgentLifecycleOperation, AgentLifecycleTransitionInput, AgentLifecycleRetentionInfo, AgentRuntimeSpec, AgentRuntimeSpecInput, AgentCheckpointReceipt, AgentCheckpointReceiptInput, AgentWakeReservation, AgentWakeAcceptanceReceipt, AcceptRuntimeGenerationInput, RuntimeGenerationAcceptance, AgentWakeQueueEntry, EnqueueWakeInput, AgentLifecycleEvent, AgentLifecycleEventInput } from "./types.js";
4
4
  export interface TaskAssignmentAwaitingReplyInfo {
5
5
  id: number;
6
6
  agentId: string;
@@ -12,7 +12,7 @@ export interface TaskAssignmentAwaitingReplyInfo {
12
12
  export declare function defaultDbPath(): string;
13
13
  export declare const DEFAULT_RESUMABLE_WINDOW_MS = 15000;
14
14
  export declare const DEFAULT_DISCONNECTED_PURGE_GRACE_MS: number;
15
- export declare const CURRENT_BROKER_SCHEMA_VERSION = 24;
15
+ export declare const CURRENT_BROKER_SCHEMA_VERSION = 25;
16
16
  export declare class BrokerDB implements BrokerDBInterface {
17
17
  private db;
18
18
  private readonly dbPath;
@@ -254,6 +254,15 @@ export declare class BrokerDB implements BrokerDBInterface {
254
254
  name: string;
255
255
  } | null;
256
256
  private getAgentRowByStableId;
257
+ getDocument(documentId: string): DocumentInfo | null;
258
+ getDocumentByAlias(source: string, externalId: string): DocumentInfo | null;
259
+ upsertDocument(document: Omit<DocumentInfo, "createdAt" | "updatedAt">): DocumentInfo;
260
+ bindDocumentAlias(source: string, externalId: string, documentId: string, metadata?: DocumentInfo["metadata"]): DocumentAliasInfo;
261
+ setDocumentOwner(documentId: string, ownerAgent: string | null): DocumentInfo;
262
+ subscribeDocument(documentId: string, agentId: string): void;
263
+ unsubscribeDocument(documentId: string, agentId: string): void;
264
+ listDocumentSubscribers(documentId: string): string[];
265
+ getDocumentRecipients(documentId: string): string[];
257
266
  createThread(thread: ThreadInfo): ThreadInfo;
258
267
  createThread(threadId: string, source: string, channel: string, ownerAgent: string | null): ThreadInfo;
259
268
  updateThread(threadId: string, updates: Partial<ThreadInfo>): void;
@@ -303,6 +312,7 @@ export declare class BrokerDB implements BrokerDBInterface {
303
312
  releasedAgentIds: string[];
304
313
  };
305
314
  queueMessage(agentId: string, message: InboundMessage): void;
315
+ queueMessageToAgents(agentIds: string[], message: InboundMessage): void;
306
316
  queueDeliveredMessage(agentId: string, message: InboundMessage): DeliveredInboundMessageResult;
307
317
  private buildInboundMessageMetadata;
308
318
  private withInboundMailClassMetadata;
package/dist/schema.js CHANGED
@@ -65,6 +65,21 @@ function rowToThread(row) {
65
65
  updatedAt: row.updated_at,
66
66
  };
67
67
  }
68
+ function rowToDocument(row) {
69
+ const kind = row.kind === "slack_thread" || row.kind === "slack_channel" ? row.kind : "git_file";
70
+ return {
71
+ documentId: row.document_id,
72
+ kind,
73
+ title: row.title,
74
+ ownerAgent: row.owner_agent,
75
+ ownerBinding: row.owner_binding === "explicit" ? "explicit" : null,
76
+ metadata: row.metadata
77
+ ? JSON.parse(row.metadata)
78
+ : null,
79
+ createdAt: row.created_at,
80
+ updatedAt: row.updated_at,
81
+ };
82
+ }
68
83
  function rowToPortLease(row) {
69
84
  return {
70
85
  id: row.id,
@@ -602,7 +617,7 @@ export function defaultDbPath() {
602
617
  }
603
618
  export const DEFAULT_RESUMABLE_WINDOW_MS = 15_000;
604
619
  export const DEFAULT_DISCONNECTED_PURGE_GRACE_MS = 60 * 60_000;
605
- export const CURRENT_BROKER_SCHEMA_VERSION = 24;
620
+ export const CURRENT_BROKER_SCHEMA_VERSION = 25;
606
621
  /**
607
622
  * Lifecycle states whose durable identity, inbox, thread ownership, and runtime
608
623
  * mapping MUST survive routine maintenance. A hibernation identity is
@@ -1443,6 +1458,41 @@ function widenRuntimeSpecPayload(db) {
1443
1458
  DROP TABLE agent_runtime_specs_v23;
1444
1459
  `);
1445
1460
  }
1461
+ // agent-standards-ignore prefer-inline-single-use-helper: one-function-per-migration-case is the established schema-migration seam.
1462
+ function createDocumentTables(db) {
1463
+ db.exec(`
1464
+ CREATE TABLE IF NOT EXISTS documents (
1465
+ document_id TEXT PRIMARY KEY NOT NULL,
1466
+ kind TEXT NOT NULL CHECK(kind IN ('git_file','slack_thread','slack_channel')),
1467
+ title TEXT NOT NULL,
1468
+ owner_agent TEXT,
1469
+ owner_binding TEXT,
1470
+ metadata TEXT,
1471
+ created_at TEXT NOT NULL,
1472
+ updated_at TEXT NOT NULL
1473
+ );
1474
+ CREATE TABLE IF NOT EXISTS document_aliases (
1475
+ source TEXT NOT NULL,
1476
+ external_id TEXT NOT NULL,
1477
+ document_id TEXT NOT NULL,
1478
+ metadata TEXT,
1479
+ created_at TEXT NOT NULL,
1480
+ updated_at TEXT NOT NULL,
1481
+ PRIMARY KEY(source, external_id),
1482
+ FOREIGN KEY(document_id) REFERENCES documents(document_id) ON DELETE CASCADE
1483
+ );
1484
+ CREATE TABLE IF NOT EXISTS document_subscriptions (
1485
+ document_id TEXT NOT NULL,
1486
+ agent_id TEXT NOT NULL,
1487
+ created_at TEXT NOT NULL,
1488
+ updated_at TEXT NOT NULL,
1489
+ PRIMARY KEY(document_id, agent_id),
1490
+ FOREIGN KEY(document_id) REFERENCES documents(document_id) ON DELETE CASCADE
1491
+ );
1492
+ CREATE INDEX IF NOT EXISTS idx_document_subscriptions_agent
1493
+ ON document_subscriptions(agent_id, document_id);
1494
+ `);
1495
+ }
1446
1496
  function runSchemaMigrations(db) {
1447
1497
  const currentVersion = getUserVersion(db);
1448
1498
  if (currentVersion >= CURRENT_BROKER_SCHEMA_VERSION) {
@@ -1524,6 +1574,9 @@ function runSchemaMigrations(db) {
1524
1574
  case 24:
1525
1575
  widenRuntimeSpecPayload(db);
1526
1576
  break;
1577
+ case 25:
1578
+ createDocumentTables(db);
1579
+ break;
1527
1580
  default:
1528
1581
  throw new Error(`Unsupported broker schema migration target: ${nextVersion}`);
1529
1582
  }
@@ -3298,6 +3351,93 @@ export class BrokerDB {
3298
3351
  const row = db.prepare("SELECT * FROM agents WHERE stable_id = ?").get(stableId);
3299
3352
  return row ?? null;
3300
3353
  }
3354
+ // ─── Documents ───────────────────────────────────────
3355
+ getDocument(documentId) {
3356
+ const row = this.getDb()
3357
+ .prepare("SELECT * FROM documents WHERE document_id = ?")
3358
+ .get(documentId);
3359
+ return row ? rowToDocument(row) : null;
3360
+ }
3361
+ getDocumentByAlias(source, externalId) {
3362
+ const row = this.getDb()
3363
+ .prepare(`SELECT d.* FROM documents d
3364
+ JOIN document_aliases a ON a.document_id = d.document_id
3365
+ WHERE a.source = ? AND a.external_id = ?`)
3366
+ .get(source, externalId);
3367
+ return row ? rowToDocument(row) : null;
3368
+ }
3369
+ upsertDocument(document) {
3370
+ const db = this.getDb();
3371
+ const now = new Date().toISOString();
3372
+ db.prepare(`INSERT INTO documents
3373
+ (document_id, kind, title, owner_agent, owner_binding, metadata, created_at, updated_at)
3374
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3375
+ ON CONFLICT(document_id) DO UPDATE SET
3376
+ kind = excluded.kind,
3377
+ title = excluded.title,
3378
+ metadata = excluded.metadata,
3379
+ updated_at = excluded.updated_at`).run(document.documentId, document.kind, document.title, document.ownerAgent, document.ownerBinding, document.metadata ? JSON.stringify(document.metadata) : null, now, now);
3380
+ return this.getDocument(document.documentId);
3381
+ }
3382
+ bindDocumentAlias(source, externalId, documentId, metadata = null) {
3383
+ const db = this.getDb();
3384
+ const now = new Date().toISOString();
3385
+ db.prepare(`INSERT INTO document_aliases
3386
+ (source, external_id, document_id, metadata, created_at, updated_at)
3387
+ VALUES (?, ?, ?, ?, ?, ?)
3388
+ ON CONFLICT(source, external_id) DO UPDATE SET
3389
+ document_id = excluded.document_id,
3390
+ metadata = excluded.metadata,
3391
+ updated_at = excluded.updated_at`).run(source, externalId, documentId, metadata ? JSON.stringify(metadata) : null, now, now);
3392
+ return {
3393
+ source,
3394
+ externalId,
3395
+ documentId,
3396
+ metadata,
3397
+ createdAt: now,
3398
+ updatedAt: now,
3399
+ };
3400
+ }
3401
+ setDocumentOwner(documentId, ownerAgent) {
3402
+ const db = this.getDb();
3403
+ const result = db
3404
+ .prepare(`UPDATE documents SET owner_agent = ?, owner_binding = 'explicit', updated_at = ?
3405
+ WHERE document_id = ?`)
3406
+ .run(ownerAgent, new Date().toISOString(), documentId);
3407
+ if (Number(result.changes ?? 0) === 0)
3408
+ throw new Error(`Unknown document ${documentId}`);
3409
+ return this.getDocument(documentId);
3410
+ }
3411
+ subscribeDocument(documentId, agentId) {
3412
+ const now = new Date().toISOString();
3413
+ this.getDb()
3414
+ .prepare(`INSERT INTO document_subscriptions (document_id, agent_id, created_at, updated_at)
3415
+ VALUES (?, ?, ?, ?)
3416
+ ON CONFLICT(document_id, agent_id) DO UPDATE SET updated_at = excluded.updated_at`)
3417
+ .run(documentId, agentId, now, now);
3418
+ }
3419
+ unsubscribeDocument(documentId, agentId) {
3420
+ this.getDb()
3421
+ .prepare("DELETE FROM document_subscriptions WHERE document_id = ? AND agent_id = ?")
3422
+ .run(documentId, agentId);
3423
+ }
3424
+ listDocumentSubscribers(documentId) {
3425
+ const rows = this.getDb()
3426
+ .prepare("SELECT agent_id FROM document_subscriptions WHERE document_id = ? ORDER BY agent_id")
3427
+ .all(documentId);
3428
+ return rows.map((row) => row.agent_id);
3429
+ }
3430
+ getDocumentRecipients(documentId) {
3431
+ const document = this.getDocument(documentId);
3432
+ if (!document)
3433
+ return [];
3434
+ return [
3435
+ ...new Set([
3436
+ ...(document.ownerAgent ? [document.ownerAgent] : []),
3437
+ ...this.listDocumentSubscribers(documentId),
3438
+ ]),
3439
+ ];
3440
+ }
3301
3441
  createThread(threadOrId, source, channel, ownerAgent) {
3302
3442
  const db = this.getDb();
3303
3443
  const now = new Date().toISOString();
@@ -4067,6 +4207,15 @@ export class BrokerDB {
4067
4207
  this.reclassifyReferencedMessageFromReaction(message);
4068
4208
  });
4069
4209
  }
4210
+ queueMessageToAgents(agentIds, message) {
4211
+ const recipients = [...new Set(agentIds.filter((agentId) => agentId.length > 0))];
4212
+ if (recipients.length === 0)
4213
+ return;
4214
+ this.withTransaction(() => {
4215
+ this.insertMessage(message.threadId, message.source, "inbound", message.userId, message.text, recipients, this.buildInboundMessageMetadata(message));
4216
+ this.reclassifyReferencedMessageFromReaction(message);
4217
+ });
4218
+ }
4070
4219
  queueDeliveredMessage(agentId, message) {
4071
4220
  return this.withTransaction(() => {
4072
4221
  const db = this.getDb();
package/dist/types.d.ts CHANGED
@@ -372,6 +372,24 @@ export interface ThreadInfo {
372
372
  createdAt: string;
373
373
  updatedAt: string;
374
374
  }
375
+ export interface DocumentInfo {
376
+ documentId: string;
377
+ kind: "git_file" | "slack_thread" | "slack_channel";
378
+ title: string;
379
+ ownerAgent: string | null;
380
+ ownerBinding: "explicit" | null;
381
+ metadata: ThreadInfo["metadata"];
382
+ createdAt: string;
383
+ updatedAt: string;
384
+ }
385
+ export interface DocumentAliasInfo {
386
+ source: string;
387
+ externalId: string;
388
+ documentId: string;
389
+ metadata: ThreadInfo["metadata"];
390
+ createdAt: string;
391
+ updatedAt: string;
392
+ }
375
393
  export interface BrokerMessage {
376
394
  id: number;
377
395
  threadId: string;
@@ -633,6 +651,15 @@ export declare const buildCompatibilityInstanceScope: typeof _buildCompatibility
633
651
  export declare const buildRuntimeScopeCarrier: typeof _buildRuntimeScopeCarrier;
634
652
  export interface BrokerDBInterface {
635
653
  getThread(threadId: string): ThreadInfo | null;
654
+ getDocument?(documentId: string): DocumentInfo | null;
655
+ getDocumentByAlias?(source: string, externalId: string): DocumentInfo | null;
656
+ upsertDocument?(document: Omit<DocumentInfo, "createdAt" | "updatedAt">): DocumentInfo;
657
+ bindDocumentAlias?(source: string, externalId: string, documentId: string, metadata?: ThreadInfo["metadata"]): DocumentAliasInfo;
658
+ setDocumentOwner?(documentId: string, ownerAgent: string | null): DocumentInfo;
659
+ subscribeDocument?(documentId: string, agentId: string): void;
660
+ unsubscribeDocument?(documentId: string, agentId: string): void;
661
+ listDocumentSubscribers?(documentId: string): string[];
662
+ getDocumentRecipients?(documentId: string): string[];
636
663
  getAgentById(agentId: string): AgentInfo | null;
637
664
  getAgentByStableId(stableId: string): AgentInfo | null;
638
665
  getAgents(): AgentInfo[];
@@ -660,5 +687,6 @@ export interface BrokerDBInterface {
660
687
  */
661
688
  claimThread(threadId: string, agentId: string, source?: string, channel?: string): boolean;
662
689
  queueMessage(agentId: string, message: InboundMessage): void;
690
+ queueMessageToAgents?(agentIds: string[], message: InboundMessage): void;
663
691
  }
664
692
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinet/broker-core",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "description": "Transport-neutral broker kernel primitives for pi transports",
6
6
  "author": "Will Porcellini <5994936+gugu91@users.noreply.github.com>",
@@ -43,7 +43,7 @@
43
43
  "test": "vitest run --config ../vitest.config.ts *.test.ts"
44
44
  },
45
45
  "dependencies": {
46
- "@pinet/transport-core": "0.2.12"
46
+ "@pinet/transport-core": "0.2.13"
47
47
  },
48
48
  "types": "./dist/index.d.ts"
49
49
  }