@ai-matrx/meet 0.11.44 → 0.11.46

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog — `@ai-matrx/meet`
2
2
 
3
+ ## 0.11.46
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/meet/v0.11.45..npm/meet/v0.11.46 -- apps/shared/meet`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.11.45
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/meet/v0.11.44..npm/meet/v0.11.45 -- apps/shared/meet`).
13
+ No source changes intended and no consumer action required.
14
+
3
15
  ## 0.11.44
4
16
 
5
17
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -107,6 +107,11 @@ interface LobbyEntry {
107
107
  readonly knockExpiresAt?: string | null | undefined;
108
108
  readonly decidedAt: string | null;
109
109
  readonly decidedBy: UserId | null;
110
+ /**
111
+ * The server's flag on this knock (`mod-removed-bypass`): `previously_removed` when its coarse
112
+ * signals match someone a host removed from this meeting. Advice for the host, never a block.
113
+ */
114
+ readonly flag?: string | null | undefined;
110
115
  }
111
116
  type ConnectionQuality = "excellent" | "good" | "poor" | "lost" | "unknown";
112
117
  /** One person in the room, as the UI needs them. Media state is a FACT here. */
@@ -403,6 +408,8 @@ interface MeetingAttendee {
403
408
  /** When this person knocked, and when an unanswered knock lapses (null = never). */
404
409
  readonly knockedAt?: string | null | undefined;
405
410
  readonly knockExpiresAt?: string | null | undefined;
411
+ /** The server's flag on a knock (`metadata.knock_flag`), e.g. `previously_removed`. */
412
+ readonly knockFlag?: string | null | undefined;
406
413
  readonly isAgent: boolean;
407
414
  /** FIRST arrival in the room — a rejoin never moves it. */
408
415
  readonly joinedAt: string | null;
@@ -107,6 +107,11 @@ interface LobbyEntry {
107
107
  readonly knockExpiresAt?: string | null | undefined;
108
108
  readonly decidedAt: string | null;
109
109
  readonly decidedBy: UserId | null;
110
+ /**
111
+ * The server's flag on this knock (`mod-removed-bypass`): `previously_removed` when its coarse
112
+ * signals match someone a host removed from this meeting. Advice for the host, never a block.
113
+ */
114
+ readonly flag?: string | null | undefined;
110
115
  }
111
116
  type ConnectionQuality = "excellent" | "good" | "poor" | "lost" | "unknown";
112
117
  /** One person in the room, as the UI needs them. Media state is a FACT here. */
@@ -403,6 +408,8 @@ interface MeetingAttendee {
403
408
  /** When this person knocked, and when an unanswered knock lapses (null = never). */
404
409
  readonly knockedAt?: string | null | undefined;
405
410
  readonly knockExpiresAt?: string | null | undefined;
411
+ /** The server's flag on a knock (`metadata.knock_flag`), e.g. `previously_removed`. */
412
+ readonly knockFlag?: string | null | undefined;
406
413
  readonly isAgent: boolean;
407
414
  /** FIRST arrival in the room — a rejoin never moves it. */
408
415
  readonly joinedAt: string | null;
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-BNJNgXFB.js';
2
+ import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-D1TX5uf9.js';
3
3
 
4
4
  /**
5
5
  * THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-BNJNgXFB.cjs';
2
+ import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-D1TX5uf9.cjs';
3
3
 
4
4
  /**
5
5
  * THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to
package/dist/core.cjs CHANGED
@@ -607,7 +607,8 @@ function createWebResumeStorage() {
607
607
  }
608
608
  };
609
609
  }
610
- var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
610
+ var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase === "denied" || phase === "knock_expired" || phase.startsWith("refused:");
611
+ var WAITING_RESUMES = (phase) => phase.startsWith("waiting:");
611
612
  function autoRejoinDelayMs(attempt, random = Math.random) {
612
613
  const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
613
614
  return Math.round(base * (0.5 + random()));
@@ -739,6 +740,10 @@ function createConnectionSupervisor(options) {
739
740
  rejoinAttempts = 0;
740
741
  stopRejoin();
741
742
  saveMarker(true);
743
+ } else if (WAITING_RESUMES(phase)) {
744
+ stopRejoin();
745
+ driverRegion = null;
746
+ saveMarker(true);
742
747
  } else if (phase === "disconnected") {
743
748
  driverRegion = null;
744
749
  saveMarker(true);
@@ -914,10 +919,10 @@ var MEET_ROUTES = {
914
919
  recordingStop: "/api/v1/meet/recording/stop",
915
920
  noteTakerStart: "/api/v1/meet/agent/start",
916
921
  noteTakerStop: "/api/v1/meet/agent/stop",
917
- /** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
918
- intelligenceAsk: "/api/v1/meet/intelligence/ask",
919
922
  /** Anyone in the meeting (guests with the room pass included) asks for captions for themselves. */
920
923
  noteTakerCaptions: "/api/v1/meet/agent/captions",
924
+ /** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
925
+ intelligenceAsk: "/api/v1/meet/intelligence/ask",
921
926
  /** A host ends the meeting for everyone. Served by MRI-A6. */
922
927
  meetingEnd: "/api/v1/meet/end",
923
928
  /**
@@ -1042,6 +1047,7 @@ function parseRoomToken(body, roomName) {
1042
1047
  connectionIdentity: readString(payload, "connection_identity") ?? personKey,
1043
1048
  connectionKind: readString(payload, "connection_kind") === "companion" ? "companion" : "main",
1044
1049
  secondDeviceChoice,
1050
+ secondDeviceRule: readString(payload, "second_device_rule"),
1045
1051
  displayName: readString(payload, "display_name") ?? readString(payload, "displayName") ?? identity,
1046
1052
  expiresAt,
1047
1053
  canPublish: readBoolean(payload, "can_publish", true),
@@ -1586,6 +1592,7 @@ var STATES = [
1586
1592
  "ended"
1587
1593
  ];
1588
1594
  function readState(value) {
1595
+ if (value === "dispatching") return "joining";
1589
1596
  return typeof value === "string" && STATES.includes(value) ? value : null;
1590
1597
  }
1591
1598
  function isNoteTaker(participant) {
@@ -1780,6 +1787,12 @@ var JOINABLE_FROM = /* @__PURE__ */ new Set([
1780
1787
  // allows the press; the server's gate decides whether it knocks or is refused again.
1781
1788
  "refused:lobby_rejects"
1782
1789
  ]);
1790
+ var RETRYABLE_REFUSALS = /* @__PURE__ */ new Set([
1791
+ "refused:locked",
1792
+ "refused:too_early",
1793
+ "refused:meeting_full",
1794
+ "refused:platform_capacity"
1795
+ ]);
1783
1796
  var NOT_ENDABLE = /* @__PURE__ */ new Set([
1784
1797
  "ended",
1785
1798
  "record",
@@ -1845,7 +1858,7 @@ function nextPhase(from, event) {
1845
1858
  case "link_state":
1846
1859
  return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
1847
1860
  case "join":
1848
- return JOINABLE_FROM.has(from) ? "joining" : null;
1861
+ return JOINABLE_FROM.has(from) || RETRYABLE_REFUSALS.has(from) ? "joining" : null;
1849
1862
  case "auto_rejoin":
1850
1863
  return from === "disconnected" ? "joining" : null;
1851
1864
  case "resumed":
@@ -2270,6 +2283,10 @@ var RPCS = {
2270
2283
  admitAll: "meet_admit_all",
2271
2284
  setLock: "meet_set_lock",
2272
2285
  recordConsent: "meet_record_consent",
2286
+ /** One meeting rule as the gate obeys it (the knob ladder, this meeting's rung included). */
2287
+ meetingRule: "meet_policy",
2288
+ /** A host sets one rule for THIS meeting (the `meet_meeting` rung); refusals come back as `{ok:false}`. */
2289
+ setMeetingRule: "meet_policy_set",
2273
2290
  /*
2274
2291
  * ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
2275
2292
  * authorized by the meeting's host or a real iam grant (editor to change,
@@ -2316,7 +2333,7 @@ var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsv
2316
2333
  var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
2317
2334
  var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
2318
2335
  var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
2319
- var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions";
2336
+ var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions,knock_flag:metadata->>knock_flag";
2320
2337
  function projectStays(value) {
2321
2338
  if (!Array.isArray(value)) return [];
2322
2339
  const stays = [];
@@ -2716,6 +2733,8 @@ function createMeetRepository(options) {
2716
2733
  admissionState: states.includes(admission) ? admission : "admitted",
2717
2734
  knockedAt: text(row, "knocked_at"),
2718
2735
  knockExpiresAt: text(row, "knock_expires_at"),
2736
+ // The list read selects `metadata->>knock_flag`; a Postgres Changes row carries `metadata`.
2737
+ knockFlag: text(row, "knock_flag") ?? (typeof row["metadata"] === "object" && row["metadata"] !== null ? text(row["metadata"], "knock_flag") : null),
2719
2738
  isAgent: bool(row, "is_agent", false),
2720
2739
  joinedAt: text(row, "joined_at"),
2721
2740
  leftAt: text(row, "left_at"),
@@ -3039,6 +3058,25 @@ function createMeetRepository(options) {
3039
3058
  );
3040
3059
  return projectMeeting(row);
3041
3060
  },
3061
+ async meetingRule(meetingId, key) {
3062
+ const response = await db.rpc(RPCS.meetingRule, { p_meeting_id: meetingId, p_key: key });
3063
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.meetingRule");
3064
+ return response.data;
3065
+ },
3066
+ async setMeetingRule(meetingId, key, value) {
3067
+ const response = await db.rpc(RPCS.setMeetingRule, { p_meeting_id: meetingId, p_key: key, p_value: value });
3068
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.setMeetingRule");
3069
+ const answer = response.data ?? {};
3070
+ if (answer["ok"] !== true) {
3071
+ throw new MeetError(
3072
+ "forbidden",
3073
+ typeof answer["detail"] === "string" ? answer["detail"] : "This meeting's rule was not changed.",
3074
+ "Only the meeting's host or a co-host sets its rules; an organization can lock a rule for everyone.",
3075
+ answer
3076
+ );
3077
+ }
3078
+ return answer["effective_value"];
3079
+ },
3042
3080
  async transcript(meetingId, limit = 1e3) {
3043
3081
  const response = await db.from(TABLES.transcripts).select(SEGMENT_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: true }).order("id", { ascending: true }).limit(limit);
3044
3082
  if (response.error !== null) throw normalizeMeetError(response.error, "meet.transcript");
@@ -5204,7 +5242,8 @@ function lobbyEntryFrom(attendee, now = Date.now()) {
5204
5242
  knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5205
5243
  knockExpiresAt: attendee.knockExpiresAt ?? null,
5206
5244
  decidedAt: null,
5207
- decidedBy: null
5245
+ decidedBy: null,
5246
+ flag: attendee.knockFlag ?? null
5208
5247
  };
5209
5248
  }
5210
5249
  function deniedEntryFrom(attendee) {
@@ -5376,11 +5415,15 @@ function createMeetingSession(options) {
5376
5415
  }
5377
5416
  let policyTimer = null;
5378
5417
  let policyReading = false;
5418
+ let policyInFlight = null;
5379
5419
  async function readLivePolicy() {
5380
5420
  const meeting = store.snapshot().meeting;
5381
5421
  const pass = options.resolvePass?.() ?? null;
5382
- if (meeting === null || pass === null || policyReading) return;
5422
+ if (meeting === null || pass === null) return;
5423
+ if (policyReading) return policyInFlight ?? void 0;
5383
5424
  policyReading = true;
5425
+ let done = () => void 0;
5426
+ policyInFlight = new Promise((resolve) => done = resolve);
5384
5427
  try {
5385
5428
  const response = await options.api.request({
5386
5429
  path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
@@ -5401,6 +5444,8 @@ function createMeetingSession(options) {
5401
5444
  } catch {
5402
5445
  } finally {
5403
5446
  policyReading = false;
5447
+ policyInFlight = null;
5448
+ done();
5404
5449
  }
5405
5450
  }
5406
5451
  function syncLivePolicyTimer() {
@@ -5699,6 +5744,7 @@ function createMeetingSession(options) {
5699
5744
  },
5700
5745
  stopWaiting,
5701
5746
  readState: readState2,
5747
+ readLivePolicy,
5702
5748
  async readExit(connection, held) {
5703
5749
  const meeting = store.snapshot().meeting;
5704
5750
  const pass = held ?? options.resolvePass?.() ?? null;
@@ -5736,6 +5782,17 @@ function createMeetingSession(options) {
5736
5782
  async askAgain() {
5737
5783
  await options.rejoin?.();
5738
5784
  },
5785
+ async meetingRule(key) {
5786
+ const meeting = requireMeeting("meet.meetingRule");
5787
+ return repository.meetingRule(meeting.id, key);
5788
+ },
5789
+ async setMeetingRule(key, value) {
5790
+ requireHostUser("meet.setMeetingRule");
5791
+ const meeting = requireMeeting("meet.setMeetingRule");
5792
+ const effective = await repository.setMeetingRule(meeting.id, key, value);
5793
+ hint();
5794
+ return effective;
5795
+ },
5739
5796
  async setLock(locked) {
5740
5797
  const by = requireHostUser("meet.setLock");
5741
5798
  const meeting = requireMeeting("meet.setLock");
@@ -7127,7 +7184,7 @@ function createRoomEngine(options) {
7127
7184
  const { secondDevice: _answered, ...rest } = lastRequest;
7128
7185
  lastRequest = rest;
7129
7186
  }
7130
- options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
7187
+ options.tabClaim?.inRoom(String(request.roomName), String(token.identity), token.secondDeviceRule ?? null);
7131
7188
  current = token;
7132
7189
  lastServerUrl = token.serverUrl;
7133
7190
  connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
@@ -7439,10 +7496,12 @@ function createRoomEngine(options) {
7439
7496
  if (claim === void 0 || userId === null) return false;
7440
7497
  const before = store.snapshot();
7441
7498
  if (before.phase !== "prejoin" || before.secondDevice !== null) return false;
7442
- if (!await claim.presentElsewhere(String(roomName), `user:${String(userId)}`)) return false;
7499
+ const person = `user:${String(userId)}`;
7500
+ const found = claim.presenceElsewhere !== void 0 ? await claim.presenceElsewhere(String(roomName), person) : await claim.presentElsewhere(String(roomName), person) ? { rule: null } : null;
7501
+ if (found === null || found.rule === "allow_both") return false;
7443
7502
  const now2 = store.snapshot();
7444
7503
  if (now2.phase !== "prejoin" || now2.secondDevice !== null) return false;
7445
- store.patch({ secondDevice: { rule: "ask_switch_or_join" } });
7504
+ store.patch({ secondDevice: { rule: found.rule ?? "ask_switch_or_join" } });
7446
7505
  report("info", "meet: this browser already has you in this meeting \u2014 offering the choice before Join");
7447
7506
  return true;
7448
7507
  },
@@ -7698,6 +7757,7 @@ function createInRoomClient(options) {
7698
7757
  action: request.action,
7699
7758
  ...request.identity != null ? { identity: request.identity } : {},
7700
7759
  ...request.name !== void 0 ? { name: request.name } : {},
7760
+ ...request.value !== void 0 ? { value: request.value } : {},
7701
7761
  ...request.reason !== void 0 ? { reason: request.reason } : {}
7702
7762
  },
7703
7763
  ...hdrs !== void 0 ? { headers: hdrs } : {}