@band-ai/band-sdk-core 0.4.1 → 0.5.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.
package/README.md CHANGED
@@ -54,7 +54,7 @@ exception class.
54
54
 
55
55
  ### Delivery-state runtime classes
56
56
 
57
- `ClaimRegistry`, `RetryTracker`, and `ParticipantRoster` are the
57
+ `ClaimRegistry`, `RetryTracker`, `ParticipantRoster`, and `SubscriptionTracker` are the
58
58
  inbound-delivery runtime state classes — lifecycle and design decisions:
59
59
  [`runtime-state-policy.md`](https://github.com/band-ai/band-sdk-core/blob/main/crates/core/docs/runtime-state-policy.md).
60
60
 
@@ -68,6 +68,45 @@ snapshot names the same `id` twice. `RetryTracker`'s `maxTracked` must be
68
68
  at least `1`; `0` throws. `ClaimRegistry`'s `maxCompleted` must be at
69
69
  least `1` too — `0` also throws.
70
70
 
71
+ `SubscriptionTracker` provides synchronous, transport-independent decisions for
72
+ agent-topic joins and the two-topic room subscription transaction. Its opaque
73
+ tickets are JavaScript `bigint` values and must be supplied when recording a
74
+ completion. Failed rollbacks and failed or unknown leaves require explicit
75
+ reconciliation before a fresh claim is allowed.
76
+
77
+ ### `SubscriptionTracker` lifecycle
78
+
79
+ ```ts
80
+ import { SubscriptionTracker } from "@band-ai/band-sdk-core";
81
+
82
+ const tracker = new SubscriptionTracker();
83
+ const ticket = tracker.beginRoomSubscribe("room-1");
84
+ if (ticket === undefined) throw new Error("room is not claimable");
85
+
86
+ switch (tracker.recordRoomParticipantsJoinFailed("room-1", ticket, false)) {
87
+ case "rollback_failed":
88
+ if (tracker.roomStatus("room-1") !== "needs_reconciliation") {
89
+ throw new Error("rollback state was not retained");
90
+ }
91
+ tracker.acknowledgeRoomReconciled("room-1");
92
+ const retryTicket = tracker.beginRoomSubscribe("room-1");
93
+ if (retryTicket === undefined) throw new Error("reconciled room is not claimable");
94
+ if (tracker.recordBothRoomTopicsJoined("room-1", retryTicket) !== "subscribed") {
95
+ throw new Error("room did not subscribe");
96
+ }
97
+ const leaveTicket = tracker.unsubscribeRoom("room-1");
98
+ if (leaveTicket === undefined || !tracker.markRoomLeaveComplete("room-1", leaveTicket, "left")) {
99
+ throw new Error("room did not leave");
100
+ }
101
+ break;
102
+ case "subscribed":
103
+ case "join_failed":
104
+ case "rolled_back":
105
+ case "stale":
106
+ break;
107
+ }
108
+ ```
109
+
71
110
  ### `validateMemoryTypeForSystem(system, type, traceContext?)`
72
111
 
73
112
  Validates the canonical memory taxonomy — design decisions:
package/band_sdk_core.js CHANGED
@@ -348,6 +348,264 @@ class RetryTracker {
348
348
  if (Symbol.dispose) RetryTracker.prototype[Symbol.dispose] = RetryTracker.prototype.free;
349
349
  exports.RetryTracker = RetryTracker;
350
350
 
351
+ /**
352
+ * Transport-independent subscription decisions for one agent session.
353
+ */
354
+ class SubscriptionTracker {
355
+ __destroy_into_raw() {
356
+ const ptr = this.__wbg_ptr;
357
+ this.__wbg_ptr = 0;
358
+ SubscriptionTrackerFinalization.unregister(this);
359
+ return ptr;
360
+ }
361
+ free() {
362
+ const ptr = this.__destroy_into_raw();
363
+ wasm.__wbg_subscriptiontracker_free(ptr, 0);
364
+ }
365
+ /**
366
+ * @param {any} topic
367
+ * @returns {boolean}
368
+ */
369
+ acknowledgeAgentTopicReconciled(topic) {
370
+ const ret = wasm.subscriptiontracker_acknowledgeAgentTopicReconciled(this.__wbg_ptr, topic);
371
+ if (ret[2]) {
372
+ throw takeFromExternrefTable0(ret[1]);
373
+ }
374
+ return ret[0] !== 0;
375
+ }
376
+ /**
377
+ * @param {any} room_id
378
+ * @returns {boolean}
379
+ */
380
+ acknowledgeRoomReconciled(room_id) {
381
+ const ret = wasm.subscriptiontracker_acknowledgeRoomReconciled(this.__wbg_ptr, room_id);
382
+ if (ret[2]) {
383
+ throw takeFromExternrefTable0(ret[1]);
384
+ }
385
+ return ret[0] !== 0;
386
+ }
387
+ /**
388
+ * @param {any} topic
389
+ * @returns {string}
390
+ */
391
+ agentTopicStatus(topic) {
392
+ const ret = wasm.subscriptiontracker_agentTopicStatus(this.__wbg_ptr, topic);
393
+ if (ret[3]) {
394
+ throw takeFromExternrefTable0(ret[2]);
395
+ }
396
+ return getStringFromWasm0(ret[0], ret[1]);
397
+ }
398
+ /**
399
+ * @param {any} topic
400
+ * @returns {bigint | undefined}
401
+ */
402
+ beginAgentTopicJoin(topic) {
403
+ const ret = wasm.subscriptiontracker_beginAgentTopicJoin(this.__wbg_ptr, topic);
404
+ if (ret[3]) {
405
+ throw takeFromExternrefTable0(ret[2]);
406
+ }
407
+ return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]);
408
+ }
409
+ /**
410
+ * @param {any} room_id
411
+ * @returns {bigint | undefined}
412
+ */
413
+ beginRoomSubscribe(room_id) {
414
+ const ret = wasm.subscriptiontracker_beginRoomSubscribe(this.__wbg_ptr, room_id);
415
+ if (ret[3]) {
416
+ throw takeFromExternrefTable0(ret[2]);
417
+ }
418
+ return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]);
419
+ }
420
+ endSession() {
421
+ wasm.subscriptiontracker_endSession(this.__wbg_ptr);
422
+ }
423
+ /**
424
+ * @param {any} topic
425
+ * @param {bigint} ticket
426
+ * @returns {boolean}
427
+ */
428
+ isAgentTopicClaimCurrent(topic, ticket) {
429
+ const ret = wasm.subscriptiontracker_isAgentTopicClaimCurrent(this.__wbg_ptr, topic, ticket);
430
+ if (ret[2]) {
431
+ throw takeFromExternrefTable0(ret[1]);
432
+ }
433
+ return ret[0] !== 0;
434
+ }
435
+ /**
436
+ * @param {any} topic
437
+ * @returns {boolean}
438
+ */
439
+ isAgentTopicJoined(topic) {
440
+ const ret = wasm.subscriptiontracker_isAgentTopicJoined(this.__wbg_ptr, topic);
441
+ if (ret[2]) {
442
+ throw takeFromExternrefTable0(ret[1]);
443
+ }
444
+ return ret[0] !== 0;
445
+ }
446
+ /**
447
+ * @param {any} room_id
448
+ * @param {bigint} ticket
449
+ * @returns {boolean}
450
+ */
451
+ isRoomClaimCurrent(room_id, ticket) {
452
+ const ret = wasm.subscriptiontracker_isRoomClaimCurrent(this.__wbg_ptr, room_id, ticket);
453
+ if (ret[2]) {
454
+ throw takeFromExternrefTable0(ret[1]);
455
+ }
456
+ return ret[0] !== 0;
457
+ }
458
+ /**
459
+ * @param {any} room_id
460
+ * @returns {boolean}
461
+ */
462
+ isRoomSubscribed(room_id) {
463
+ const ret = wasm.subscriptiontracker_isRoomSubscribed(this.__wbg_ptr, room_id);
464
+ if (ret[2]) {
465
+ throw takeFromExternrefTable0(ret[1]);
466
+ }
467
+ return ret[0] !== 0;
468
+ }
469
+ /**
470
+ * @returns {string[]}
471
+ */
472
+ joinedAgentTopics() {
473
+ const ret = wasm.subscriptiontracker_joinedAgentTopics(this.__wbg_ptr);
474
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
475
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
476
+ return v1;
477
+ }
478
+ /**
479
+ * @param {any} topic
480
+ * @returns {bigint | undefined}
481
+ */
482
+ leaveAgentTopic(topic) {
483
+ const ret = wasm.subscriptiontracker_leaveAgentTopic(this.__wbg_ptr, topic);
484
+ if (ret[3]) {
485
+ throw takeFromExternrefTable0(ret[2]);
486
+ }
487
+ return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]);
488
+ }
489
+ /**
490
+ * @param {any} topic
491
+ * @param {bigint} ticket
492
+ * @param {any} outcome
493
+ * @returns {boolean}
494
+ */
495
+ markAgentTopicLeaveComplete(topic, ticket, outcome) {
496
+ const ret = wasm.subscriptiontracker_markAgentTopicLeaveComplete(this.__wbg_ptr, topic, ticket, outcome);
497
+ if (ret[2]) {
498
+ throw takeFromExternrefTable0(ret[1]);
499
+ }
500
+ return ret[0] !== 0;
501
+ }
502
+ /**
503
+ * @param {any} room_id
504
+ * @param {bigint} ticket
505
+ * @param {any} outcome
506
+ * @returns {boolean}
507
+ */
508
+ markRoomLeaveComplete(room_id, ticket, outcome) {
509
+ const ret = wasm.subscriptiontracker_markRoomLeaveComplete(this.__wbg_ptr, room_id, ticket, outcome);
510
+ if (ret[2]) {
511
+ throw takeFromExternrefTable0(ret[1]);
512
+ }
513
+ return ret[0] !== 0;
514
+ }
515
+ constructor() {
516
+ const ret = wasm.subscriptiontracker_new();
517
+ this.__wbg_ptr = ret;
518
+ SubscriptionTrackerFinalization.register(this, this.__wbg_ptr, this);
519
+ return this;
520
+ }
521
+ onReconnected() {
522
+ wasm.subscriptiontracker_onReconnected(this.__wbg_ptr);
523
+ }
524
+ /**
525
+ * @param {any} topic
526
+ * @param {bigint} ticket
527
+ * @param {boolean} joined
528
+ * @returns {boolean}
529
+ */
530
+ recordAgentTopicJoin(topic, ticket, joined) {
531
+ const ret = wasm.subscriptiontracker_recordAgentTopicJoin(this.__wbg_ptr, topic, ticket, joined);
532
+ if (ret[2]) {
533
+ throw takeFromExternrefTable0(ret[1]);
534
+ }
535
+ return ret[0] !== 0;
536
+ }
537
+ /**
538
+ * @param {any} room_id
539
+ * @param {bigint} ticket
540
+ * @returns {string}
541
+ */
542
+ recordBothRoomTopicsJoined(room_id, ticket) {
543
+ const ret = wasm.subscriptiontracker_recordBothRoomTopicsJoined(this.__wbg_ptr, room_id, ticket);
544
+ if (ret[3]) {
545
+ throw takeFromExternrefTable0(ret[2]);
546
+ }
547
+ return getStringFromWasm0(ret[0], ret[1]);
548
+ }
549
+ /**
550
+ * @param {any} room_id
551
+ * @param {bigint} ticket
552
+ * @returns {string}
553
+ */
554
+ recordChatRoomJoinFailed(room_id, ticket) {
555
+ const ret = wasm.subscriptiontracker_recordChatRoomJoinFailed(this.__wbg_ptr, room_id, ticket);
556
+ if (ret[3]) {
557
+ throw takeFromExternrefTable0(ret[2]);
558
+ }
559
+ return getStringFromWasm0(ret[0], ret[1]);
560
+ }
561
+ /**
562
+ * @param {any} room_id
563
+ * @param {bigint} ticket
564
+ * @param {boolean} chatRoomLeft
565
+ * @returns {string}
566
+ */
567
+ recordRoomParticipantsJoinFailed(room_id, ticket, chatRoomLeft) {
568
+ const ret = wasm.subscriptiontracker_recordRoomParticipantsJoinFailed(this.__wbg_ptr, room_id, ticket, chatRoomLeft);
569
+ if (ret[3]) {
570
+ throw takeFromExternrefTable0(ret[2]);
571
+ }
572
+ return getStringFromWasm0(ret[0], ret[1]);
573
+ }
574
+ /**
575
+ * @param {any} room_id
576
+ * @returns {string}
577
+ */
578
+ roomStatus(room_id) {
579
+ const ret = wasm.subscriptiontracker_roomStatus(this.__wbg_ptr, room_id);
580
+ if (ret[3]) {
581
+ throw takeFromExternrefTable0(ret[2]);
582
+ }
583
+ return getStringFromWasm0(ret[0], ret[1]);
584
+ }
585
+ /**
586
+ * @returns {string[]}
587
+ */
588
+ subscribedRoomIds() {
589
+ const ret = wasm.subscriptiontracker_subscribedRoomIds(this.__wbg_ptr);
590
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
591
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
592
+ return v1;
593
+ }
594
+ /**
595
+ * @param {any} room_id
596
+ * @returns {bigint | undefined}
597
+ */
598
+ unsubscribeRoom(room_id) {
599
+ const ret = wasm.subscriptiontracker_unsubscribeRoom(this.__wbg_ptr, room_id);
600
+ if (ret[3]) {
601
+ throw takeFromExternrefTable0(ret[2]);
602
+ }
603
+ return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]);
604
+ }
605
+ }
606
+ if (Symbol.dispose) SubscriptionTracker.prototype[Symbol.dispose] = SubscriptionTracker.prototype.free;
607
+ exports.SubscriptionTracker = SubscriptionTracker;
608
+
351
609
  /**
352
610
  * Validate and normalize an inbound platform event payload.
353
611
  *
@@ -650,6 +908,9 @@ const ParticipantRosterFinalization = (typeof FinalizationRegistry === 'undefine
650
908
  const RetryTrackerFinalization = (typeof FinalizationRegistry === 'undefined')
651
909
  ? { register: () => {}, unregister: () => {} }
652
910
  : new FinalizationRegistry(ptr => wasm.__wbg_retrytracker_free(ptr, 1));
911
+ const SubscriptionTrackerFinalization = (typeof FinalizationRegistry === 'undefined')
912
+ ? { register: () => {}, unregister: () => {} }
913
+ : new FinalizationRegistry(ptr => wasm.__wbg_subscriptiontracker_free(ptr, 1));
653
914
 
654
915
  function addToExternrefTable0(obj) {
655
916
  const idx = wasm.__externref_table_alloc();
Binary file
package/index.d.ts CHANGED
@@ -145,3 +145,53 @@ export class ParticipantRoster {
145
145
  changed(): boolean;
146
146
  markSent(): void;
147
147
  }
148
+
149
+ export type RoomSubscribeResult =
150
+ | "subscribed"
151
+ | "join_failed"
152
+ | "rolled_back"
153
+ | "rollback_failed"
154
+ | "stale";
155
+
156
+ export type RoomStatus =
157
+ | "absent"
158
+ | "pending"
159
+ | "subscribed"
160
+ | "leaving"
161
+ | "needs_reconciliation";
162
+
163
+ export type AgentTopicStatus =
164
+ | "absent"
165
+ | "pending"
166
+ | "joined"
167
+ | "leaving"
168
+ | "needs_reconciliation";
169
+
170
+ export type LeaveOutcome = "left" | "failed" | "unknown";
171
+
172
+ /** Transport-independent subscription decisions for one agent session. */
173
+ export class SubscriptionTracker {
174
+ constructor();
175
+ beginRoomSubscribe(roomId: string): bigint | undefined;
176
+ recordChatRoomJoinFailed(roomId: string, ticket: bigint): RoomSubscribeResult;
177
+ recordBothRoomTopicsJoined(roomId: string, ticket: bigint): RoomSubscribeResult;
178
+ recordRoomParticipantsJoinFailed(roomId: string, ticket: bigint, chatRoomLeft: boolean): RoomSubscribeResult;
179
+ isRoomSubscribed(roomId: string): boolean;
180
+ isRoomClaimCurrent(roomId: string, ticket: bigint): boolean;
181
+ roomStatus(roomId: string): RoomStatus;
182
+ unsubscribeRoom(roomId: string): bigint | undefined;
183
+ markRoomLeaveComplete(roomId: string, ticket: bigint, outcome: LeaveOutcome): boolean;
184
+ acknowledgeRoomReconciled(roomId: string): boolean;
185
+ subscribedRoomIds(): string[];
186
+ onReconnected(): void;
187
+ endSession(): void;
188
+ beginAgentTopicJoin(topic: string): bigint | undefined;
189
+ recordAgentTopicJoin(topic: string, ticket: bigint, joined: boolean): boolean;
190
+ isAgentTopicJoined(topic: string): boolean;
191
+ isAgentTopicClaimCurrent(topic: string, ticket: bigint): boolean;
192
+ agentTopicStatus(topic: string): AgentTopicStatus;
193
+ leaveAgentTopic(topic: string): bigint | undefined;
194
+ markAgentTopicLeaveComplete(topic: string, ticket: bigint, outcome: LeaveOutcome): boolean;
195
+ acknowledgeAgentTopicReconciled(topic: string): boolean;
196
+ joinedAgentTopics(): string[];
197
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@band-ai/band-sdk-core",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Shared Band event-payload validation",
5
5
  "license": "MIT",
6
6
  "repository": {