@ai-matrx/meet 0.11.32 → 0.11.33

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/dist/core.d.cts CHANGED
@@ -35,7 +35,12 @@ declare class MeetError extends Error {
35
35
  * renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
36
36
  */
37
37
  readonly reason: string | null;
38
- constructor(code: MeetErrorCode, message: string, remedy: string, cause?: unknown, reason?: string | null);
38
+ /**
39
+ * A refusal that lifts by itself says when: whole seconds from the moment the server answered
40
+ * (a denial's cooldown, `reknock_cooldown_seconds`). Null for every refusal that does not.
41
+ */
42
+ readonly retryAfterSeconds: number | null;
43
+ constructor(code: MeetErrorCode, message: string, remedy: string, cause?: unknown, reason?: string | null, retryAfterSeconds?: number | null);
39
44
  /** True when retrying after the host re-establishes a session is the fix. */
40
45
  get isRetryable(): boolean;
41
46
  /** True when the user, not the code, must act — the UI shows `remedy`. */
@@ -342,6 +347,13 @@ type PhaseEvent =
342
347
  | {
343
348
  readonly type: "connected";
344
349
  }
350
+ /**
351
+ * S7: the server answered the join with a QUESTION — this person is already in the meeting on
352
+ * another tab or device — so `joining` goes back to `prejoin`, which offers the choice.
353
+ */
354
+ | {
355
+ readonly type: "second_device";
356
+ }
345
357
  /** The join itself failed: a gate reason (→ `refused:*` / `ended`) or none (→ `disconnected`). */
346
358
  | {
347
359
  readonly type: "join_failed";
@@ -375,9 +387,15 @@ type PhaseEvent =
375
387
  readonly type: "call_outcome";
376
388
  readonly outcome: CallOutcomePhase;
377
389
  }
378
- /** Back to the start: a different meeting mounted, the call surface was dismissed, or dispose. */
390
+ /**
391
+ * Back to the start: a different meeting mounted or the call surface was dismissed (refused
392
+ * from a live phase — leave first). `teardown: true` is the engine's own dispose: it has
393
+ * already closed the connection, so nothing live remains to protect and the reset applies from
394
+ * any phase.
395
+ */
379
396
  | {
380
397
  readonly type: "reset";
398
+ readonly teardown?: true;
381
399
  };
382
400
  type PhaseEventType = PhaseEvent["type"];
383
401
  /** Phases in which this tab holds (or is getting) a live media connection. */
@@ -582,8 +600,8 @@ interface MeetGrants {
582
600
  declare const NO_GRANTS: MeetGrants;
583
601
  /**
584
602
  * S4 — THIS PERSON'S WAIT, as the server's state read (`GET /v1/meet/state`) last said it
585
- * (CORE-DESIGN §2.6). Set while `phase === "lobby"`; the waiting screens read it, never a
586
- * broadcast. `admission` is the row's own word: `waiting_host` · `knocking` · `denied` ·
603
+ * (CORE-DESIGN §2.6). Set while the phase is a waiting one (`waiting:*`, `denied`,
604
+ * `knock_expired`); the waiting screens read it, never a broadcast. `admission` is the row's own word: `waiting_host` · `knocking` · `denied` ·
587
605
  * `expired` · `withdrawn` · `ended` (`admitted` is never held here — it is a join).
588
606
  */
589
607
  interface MeetWaiting {
@@ -593,6 +611,8 @@ interface MeetWaiting {
593
611
  /** May a denied person ask again (`reknock_after_deny`)? */
594
612
  readonly reknockAllowed: boolean;
595
613
  readonly knockExpiresAt: string | null;
614
+ /** While a denial's cooldown runs: the epoch-ms moment the person may ask again; else null. */
615
+ readonly reknockAt?: number | null;
596
616
  /** How often the core reads while waiting (`waiting_poll_seconds`). */
597
617
  readonly pollSeconds: number;
598
618
  /** The run's `live_version` at the last read. */
@@ -647,6 +667,14 @@ interface MeetSnapshot {
647
667
  readonly waiting: MeetWaiting | null;
648
668
  readonly meeting: MeetingRecord | null;
649
669
  readonly localIdentity: ParticipantIdentity | null;
670
+ /**
671
+ * S7: this person is already in this meeting on another tab or device, and the pre-join offers
672
+ * the meeting's choice — `rule` is the `second_device` knob (`ask_switch_or_join` · Meet: Switch
673
+ * here / Join here too; `ask_transfer_or_add` · Teams: Transfer / Add). Null otherwise.
674
+ */
675
+ readonly secondDevice: {
676
+ readonly rule: string;
677
+ } | null;
650
678
  /** The server's word on what THIS joiner may do. See `MeetGrants`. */
651
679
  readonly grants: MeetGrants;
652
680
  readonly participants: readonly MeetParticipant[];
@@ -756,6 +784,11 @@ interface MeetSnapshot {
756
784
  readonly remedy: string;
757
785
  /** The join gate's reason code, when the server named one (CORE-DESIGN §2.5). */
758
786
  readonly reason?: string | null;
787
+ /**
788
+ * When a refusal lifts by itself (a denial's cooldown): the epoch-ms moment the person may
789
+ * ask again. Absent for every refusal that does not lift (CORE-DESIGN §11 `wr-denied-reknock`).
790
+ */
791
+ readonly retryAt?: number | null;
759
792
  } | null;
760
793
  /**
761
794
  * Meet wave 5: polls, Q&A, the shared whiteboard and the breakout plan — what the
@@ -2347,6 +2380,17 @@ interface MediaSupervisor {
2347
2380
  }
2348
2381
  declare function createMediaSupervisor(options: MediaSupervisorOptions): MediaSupervisor;
2349
2382
 
2383
+ interface TabClaim {
2384
+ /** This page's claimed tab id (resolves once the claim settles). */
2385
+ readonly tabId: Promise<string>;
2386
+ /** Announce that this tab is in `room` as `person` (until `left`); answers presence questions. */
2387
+ inRoom(room: string, person: string): void;
2388
+ left(): void;
2389
+ /** Is `person` in `room` on ANOTHER tab of this browser? Answers within `TAB_ANSWER_MS`. */
2390
+ presentElsewhere(room: string, person: string): Promise<boolean>;
2391
+ dispose(): void;
2392
+ }
2393
+
2350
2394
  /**
2351
2395
  * THE ROOM ENGINE — the object a host mounts once and never reasons about.
2352
2396
  *
@@ -2387,12 +2431,20 @@ interface JoinRequest {
2387
2431
  readonly breakoutRoomId?: string | null | undefined;
2388
2432
  /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */
2389
2433
  readonly observe?: boolean | undefined;
2434
+ /**
2435
+ * S7: the person's answer when they are already in this meeting on another tab or device
2436
+ * (`snapshot.secondDevice`): `switch_here` moves the call here, `join_too` adds this tab as a
2437
+ * companion (no microphone, speaker off).
2438
+ */
2439
+ readonly secondDevice?: SecondDeviceAnswer | undefined;
2390
2440
  }
2391
2441
  interface RoomEngineOptions {
2392
2442
  store: MeetStore;
2393
2443
  tokens: TokenClient;
2394
2444
  identity: MeetIdentity;
2395
2445
  driverFactory: RoomDriverFactory;
2446
+ /** S7: this page's tab claim (`core/tab-claim.ts`); absent = one connection per person. */
2447
+ tabClaim?: TabClaim | undefined;
2396
2448
  /** Canonical public metadata read; room packets alone never authorize teardown. */
2397
2449
  resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
2398
2450
  onDiagnostic?: ((event: {
@@ -2717,7 +2769,18 @@ interface RoomToken {
2717
2769
  readonly token: string;
2718
2770
  readonly serverUrl: string;
2719
2771
  readonly roomName: RoomName;
2772
+ /** S7: the PERSON key (`user:<id>` · `guest:<device>`) — what every roster tile and row uses. */
2720
2773
  readonly identity: ParticipantIdentity;
2774
+ /** S7: this tab's LiveKit identity, `<person>~<tab>` (the person key from an older server). */
2775
+ readonly connectionIdentity?: string | undefined;
2776
+ /** S7: `companion` for a "join here too" connection (no audio publish, speaker off). */
2777
+ readonly connectionKind?: "main" | "companion" | undefined;
2778
+ /**
2779
+ * S7: set when the person is ALREADY in the room on another tab or device and has not chosen
2780
+ * yet — the meeting's `second_device` rule (`ask_switch_or_join` · `ask_transfer_or_add`). No
2781
+ * LiveKit token was issued (`token` is empty); the pre-join offers the choice.
2782
+ */
2783
+ readonly secondDeviceChoice?: string | null | undefined;
2721
2784
  readonly displayName: string;
2722
2785
  /** ISO. The package refreshes at 80% of the remaining life. */
2723
2786
  readonly expiresAt: string;
@@ -2818,7 +2881,13 @@ interface TokenRequest {
2818
2881
  readonly breakoutRoomId?: string | null | undefined;
2819
2882
  /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */
2820
2883
  readonly observe?: boolean | undefined;
2884
+ /** S7: this tab's claimed id (`core/tab-claim.ts`) — the LiveKit identity is `<person>~<tab>`. */
2885
+ readonly tabId?: string | undefined;
2886
+ /** S7: the person's answer when they are already in this meeting on another tab or device. */
2887
+ readonly secondDevice?: SecondDeviceAnswer | undefined;
2821
2888
  }
2889
+ /** S7: what a person already in the meeting elsewhere chose (CORE-DESIGN §2.1). */
2890
+ type SecondDeviceAnswer = "switch_here" | "join_too";
2822
2891
  interface TokenClient {
2823
2892
  /** A currently-valid token, minting or refreshing only when it must. */
2824
2893
  get(request: TokenRequest): Promise<RoomToken>;
package/dist/core.d.ts CHANGED
@@ -35,7 +35,12 @@ declare class MeetError extends Error {
35
35
  * renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
36
36
  */
37
37
  readonly reason: string | null;
38
- constructor(code: MeetErrorCode, message: string, remedy: string, cause?: unknown, reason?: string | null);
38
+ /**
39
+ * A refusal that lifts by itself says when: whole seconds from the moment the server answered
40
+ * (a denial's cooldown, `reknock_cooldown_seconds`). Null for every refusal that does not.
41
+ */
42
+ readonly retryAfterSeconds: number | null;
43
+ constructor(code: MeetErrorCode, message: string, remedy: string, cause?: unknown, reason?: string | null, retryAfterSeconds?: number | null);
39
44
  /** True when retrying after the host re-establishes a session is the fix. */
40
45
  get isRetryable(): boolean;
41
46
  /** True when the user, not the code, must act — the UI shows `remedy`. */
@@ -342,6 +347,13 @@ type PhaseEvent =
342
347
  | {
343
348
  readonly type: "connected";
344
349
  }
350
+ /**
351
+ * S7: the server answered the join with a QUESTION — this person is already in the meeting on
352
+ * another tab or device — so `joining` goes back to `prejoin`, which offers the choice.
353
+ */
354
+ | {
355
+ readonly type: "second_device";
356
+ }
345
357
  /** The join itself failed: a gate reason (→ `refused:*` / `ended`) or none (→ `disconnected`). */
346
358
  | {
347
359
  readonly type: "join_failed";
@@ -375,9 +387,15 @@ type PhaseEvent =
375
387
  readonly type: "call_outcome";
376
388
  readonly outcome: CallOutcomePhase;
377
389
  }
378
- /** Back to the start: a different meeting mounted, the call surface was dismissed, or dispose. */
390
+ /**
391
+ * Back to the start: a different meeting mounted or the call surface was dismissed (refused
392
+ * from a live phase — leave first). `teardown: true` is the engine's own dispose: it has
393
+ * already closed the connection, so nothing live remains to protect and the reset applies from
394
+ * any phase.
395
+ */
379
396
  | {
380
397
  readonly type: "reset";
398
+ readonly teardown?: true;
381
399
  };
382
400
  type PhaseEventType = PhaseEvent["type"];
383
401
  /** Phases in which this tab holds (or is getting) a live media connection. */
@@ -582,8 +600,8 @@ interface MeetGrants {
582
600
  declare const NO_GRANTS: MeetGrants;
583
601
  /**
584
602
  * S4 — THIS PERSON'S WAIT, as the server's state read (`GET /v1/meet/state`) last said it
585
- * (CORE-DESIGN §2.6). Set while `phase === "lobby"`; the waiting screens read it, never a
586
- * broadcast. `admission` is the row's own word: `waiting_host` · `knocking` · `denied` ·
603
+ * (CORE-DESIGN §2.6). Set while the phase is a waiting one (`waiting:*`, `denied`,
604
+ * `knock_expired`); the waiting screens read it, never a broadcast. `admission` is the row's own word: `waiting_host` · `knocking` · `denied` ·
587
605
  * `expired` · `withdrawn` · `ended` (`admitted` is never held here — it is a join).
588
606
  */
589
607
  interface MeetWaiting {
@@ -593,6 +611,8 @@ interface MeetWaiting {
593
611
  /** May a denied person ask again (`reknock_after_deny`)? */
594
612
  readonly reknockAllowed: boolean;
595
613
  readonly knockExpiresAt: string | null;
614
+ /** While a denial's cooldown runs: the epoch-ms moment the person may ask again; else null. */
615
+ readonly reknockAt?: number | null;
596
616
  /** How often the core reads while waiting (`waiting_poll_seconds`). */
597
617
  readonly pollSeconds: number;
598
618
  /** The run's `live_version` at the last read. */
@@ -647,6 +667,14 @@ interface MeetSnapshot {
647
667
  readonly waiting: MeetWaiting | null;
648
668
  readonly meeting: MeetingRecord | null;
649
669
  readonly localIdentity: ParticipantIdentity | null;
670
+ /**
671
+ * S7: this person is already in this meeting on another tab or device, and the pre-join offers
672
+ * the meeting's choice — `rule` is the `second_device` knob (`ask_switch_or_join` · Meet: Switch
673
+ * here / Join here too; `ask_transfer_or_add` · Teams: Transfer / Add). Null otherwise.
674
+ */
675
+ readonly secondDevice: {
676
+ readonly rule: string;
677
+ } | null;
650
678
  /** The server's word on what THIS joiner may do. See `MeetGrants`. */
651
679
  readonly grants: MeetGrants;
652
680
  readonly participants: readonly MeetParticipant[];
@@ -756,6 +784,11 @@ interface MeetSnapshot {
756
784
  readonly remedy: string;
757
785
  /** The join gate's reason code, when the server named one (CORE-DESIGN §2.5). */
758
786
  readonly reason?: string | null;
787
+ /**
788
+ * When a refusal lifts by itself (a denial's cooldown): the epoch-ms moment the person may
789
+ * ask again. Absent for every refusal that does not lift (CORE-DESIGN §11 `wr-denied-reknock`).
790
+ */
791
+ readonly retryAt?: number | null;
759
792
  } | null;
760
793
  /**
761
794
  * Meet wave 5: polls, Q&A, the shared whiteboard and the breakout plan — what the
@@ -2347,6 +2380,17 @@ interface MediaSupervisor {
2347
2380
  }
2348
2381
  declare function createMediaSupervisor(options: MediaSupervisorOptions): MediaSupervisor;
2349
2382
 
2383
+ interface TabClaim {
2384
+ /** This page's claimed tab id (resolves once the claim settles). */
2385
+ readonly tabId: Promise<string>;
2386
+ /** Announce that this tab is in `room` as `person` (until `left`); answers presence questions. */
2387
+ inRoom(room: string, person: string): void;
2388
+ left(): void;
2389
+ /** Is `person` in `room` on ANOTHER tab of this browser? Answers within `TAB_ANSWER_MS`. */
2390
+ presentElsewhere(room: string, person: string): Promise<boolean>;
2391
+ dispose(): void;
2392
+ }
2393
+
2350
2394
  /**
2351
2395
  * THE ROOM ENGINE — the object a host mounts once and never reasons about.
2352
2396
  *
@@ -2387,12 +2431,20 @@ interface JoinRequest {
2387
2431
  readonly breakoutRoomId?: string | null | undefined;
2388
2432
  /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */
2389
2433
  readonly observe?: boolean | undefined;
2434
+ /**
2435
+ * S7: the person's answer when they are already in this meeting on another tab or device
2436
+ * (`snapshot.secondDevice`): `switch_here` moves the call here, `join_too` adds this tab as a
2437
+ * companion (no microphone, speaker off).
2438
+ */
2439
+ readonly secondDevice?: SecondDeviceAnswer | undefined;
2390
2440
  }
2391
2441
  interface RoomEngineOptions {
2392
2442
  store: MeetStore;
2393
2443
  tokens: TokenClient;
2394
2444
  identity: MeetIdentity;
2395
2445
  driverFactory: RoomDriverFactory;
2446
+ /** S7: this page's tab claim (`core/tab-claim.ts`); absent = one connection per person. */
2447
+ tabClaim?: TabClaim | undefined;
2396
2448
  /** Canonical public metadata read; room packets alone never authorize teardown. */
2397
2449
  resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
2398
2450
  onDiagnostic?: ((event: {
@@ -2717,7 +2769,18 @@ interface RoomToken {
2717
2769
  readonly token: string;
2718
2770
  readonly serverUrl: string;
2719
2771
  readonly roomName: RoomName;
2772
+ /** S7: the PERSON key (`user:<id>` · `guest:<device>`) — what every roster tile and row uses. */
2720
2773
  readonly identity: ParticipantIdentity;
2774
+ /** S7: this tab's LiveKit identity, `<person>~<tab>` (the person key from an older server). */
2775
+ readonly connectionIdentity?: string | undefined;
2776
+ /** S7: `companion` for a "join here too" connection (no audio publish, speaker off). */
2777
+ readonly connectionKind?: "main" | "companion" | undefined;
2778
+ /**
2779
+ * S7: set when the person is ALREADY in the room on another tab or device and has not chosen
2780
+ * yet — the meeting's `second_device` rule (`ask_switch_or_join` · `ask_transfer_or_add`). No
2781
+ * LiveKit token was issued (`token` is empty); the pre-join offers the choice.
2782
+ */
2783
+ readonly secondDeviceChoice?: string | null | undefined;
2721
2784
  readonly displayName: string;
2722
2785
  /** ISO. The package refreshes at 80% of the remaining life. */
2723
2786
  readonly expiresAt: string;
@@ -2818,7 +2881,13 @@ interface TokenRequest {
2818
2881
  readonly breakoutRoomId?: string | null | undefined;
2819
2882
  /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */
2820
2883
  readonly observe?: boolean | undefined;
2884
+ /** S7: this tab's claimed id (`core/tab-claim.ts`) — the LiveKit identity is `<person>~<tab>`. */
2885
+ readonly tabId?: string | undefined;
2886
+ /** S7: the person's answer when they are already in this meeting on another tab or device. */
2887
+ readonly secondDevice?: SecondDeviceAnswer | undefined;
2821
2888
  }
2889
+ /** S7: what a person already in the meeting elsewhere chose (CORE-DESIGN §2.1). */
2890
+ type SecondDeviceAnswer = "switch_here" | "join_too";
2822
2891
  interface TokenClient {
2823
2892
  /** A currently-valid token, minting or refreshing only when it must. */
2824
2893
  get(request: TokenRequest): Promise<RoomToken>;