@ai-matrx/meet 0.7.7 → 0.7.9

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/index.js CHANGED
@@ -23,6 +23,20 @@ var MeetError = class extends Error {
23
23
  return this.code === "permission-denied" || this.code === "device-not-found" || this.code === "device-in-use" || this.code === "insecure-context";
24
24
  }
25
25
  };
26
+ var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
27
+ function isRawServerText(value) {
28
+ const text3 = value.trim();
29
+ return /^internal server error\b/i.test(text3) || /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text3) || /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text3);
30
+ }
31
+ function humanDatabaseSentence(message) {
32
+ const text3 = message.trim();
33
+ if (text3.length === 0 || isRawServerText(text3)) return null;
34
+ if (/permission denied for|violates|relation "|does not exist|syntax error|column "|jwt|pgrst|duplicate key|null value in/i.test(
35
+ text3
36
+ ))
37
+ return null;
38
+ return text3;
39
+ }
26
40
  var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
27
41
  var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
28
42
  var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
@@ -50,8 +64,8 @@ function normalizeMeetError(error, operation) {
50
64
  if (code !== void 0 && FORBIDDEN_CODES.has(code)) {
51
65
  return new MeetError(
52
66
  "forbidden",
53
- `${operation}: denied by the database (${code}: ${message})`,
54
- "Authorization is RLS plus auth-checked RPCs (R5). Fix the policy or the caller's membership \u2014 never route around it with a service-role client in a browser.",
67
+ humanDatabaseSentence(message) ?? "You don't have access to do that in this meeting.",
68
+ "Ask the meeting's host to do it, or to give you access.",
55
69
  error
56
70
  );
57
71
  }
@@ -66,15 +80,16 @@ function normalizeMeetError(error, operation) {
66
80
  if (code !== void 0 && CONFLICT_CODES.has(code)) {
67
81
  return new MeetError(
68
82
  "conflict",
69
- `${operation}: unique violation (${code}: ${message})`,
70
- "A concurrent writer won. Re-read the meeting rather than retrying the insert \u2014 for a call invite this is the idempotency key doing its job.",
83
+ humanDatabaseSentence(message) ?? "This meeting changed a moment ago, so nothing was saved.",
84
+ "Reload to see the latest, then try again.",
71
85
  error
72
86
  );
73
87
  }
88
+ const hint = error?.hint;
74
89
  return new MeetError(
75
90
  "transport",
76
- `${operation}: ${message}`,
77
- "Transient transport failure. Reads retry once; a join failure surfaces to the room phase as `failed` with this message rather than hanging on a spinner.",
91
+ /failed to fetch|networkerror|load failed|network request failed/i.test(message) ? "The server could not be reached." : humanDatabaseSentence(message) ?? SERVER_FAILED_SENTENCE,
92
+ typeof hint === "string" && humanDatabaseSentence(hint) !== null ? hint.trim() : "Nothing was changed \u2014 retry in a moment; if it keeps happening, reload the page.",
78
93
  error
79
94
  );
80
95
  }
@@ -482,6 +497,24 @@ function readText(data) {
482
497
  }
483
498
  return "";
484
499
  }
500
+ async function refusalFromResponse(response, operation, fallbackRemedy) {
501
+ let body = null;
502
+ try {
503
+ body = await response.json();
504
+ } catch {
505
+ body = null;
506
+ }
507
+ const said = readServerRefusal(body);
508
+ const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
509
+ const error = new MeetError(
510
+ code,
511
+ said?.reason ?? SERVER_FAILED_SENTENCE,
512
+ said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
513
+ );
514
+ error.operation = operation;
515
+ error.status = response.status;
516
+ return error;
517
+ }
485
518
  function readServerRefusal(body) {
486
519
  if (typeof body === "string") {
487
520
  return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
@@ -500,7 +533,8 @@ function readServerRefusal(body) {
500
533
  return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
501
534
  }
502
535
  if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
503
- const reason = typeof row["reason"] === "string" ? row["reason"] : typeof row["message"] === "string" ? row["message"] : typeof detail === "string" ? detail : typeof row["error"] === "string" ? row["error"] : null;
536
+ const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
537
+ const reason = humanOrNull(row["reason"]) ?? humanOrNull(row["message"]) ?? humanOrNull(row["user_message"]) ?? (typeof detail === "string" ? humanOrNull(detail) : null) ?? (typeof row["message"] === "string" || typeof row["error"] === "string" || typeof detail === "string" ? SERVER_FAILED_SENTENCE : null);
504
538
  if (reason === null) return null;
505
539
  const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
506
540
  if (remedy.length > 0) return { reason, remedy };
@@ -3183,6 +3217,7 @@ var EMPTY = {
3183
3217
  spotlight: null,
3184
3218
  moderationNotice: null,
3185
3219
  actionNotice: null,
3220
+ privateChatRequest: null,
3186
3221
  effects: DEFAULT_EFFECTS,
3187
3222
  transcript: [],
3188
3223
  notes: [],
@@ -3873,9 +3908,55 @@ function ms(iso) {
3873
3908
  const at = new Date(iso).getTime();
3874
3909
  return Number.isNaN(at) ? null : at;
3875
3910
  }
3911
+ function attendeeKey(person, names = {}) {
3912
+ if (person.userId !== null) return `user:${person.userId}`;
3913
+ if (person.isAgent) return `agent:${person.identity}`;
3914
+ const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
3915
+ return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
3916
+ }
3917
+ function uniqueAttendees(attendees, names = {}) {
3918
+ const byKey = /* @__PURE__ */ new Map();
3919
+ for (const person of attendees) {
3920
+ const key = attendeeKey(person, names);
3921
+ const kept = byKey.get(key);
3922
+ if (kept === void 0) {
3923
+ byKey.set(key, person);
3924
+ continue;
3925
+ }
3926
+ const keptJoin = ms(kept.joinedAt);
3927
+ const join = ms(person.joinedAt);
3928
+ if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
3929
+ }
3930
+ return [...byKey.values()];
3931
+ }
3932
+ var KIND_RANK = {
3933
+ host: 0,
3934
+ cohost: 1,
3935
+ member: 2,
3936
+ "signed-in guest": 3,
3937
+ guest: 4,
3938
+ assistant: 5
3939
+ };
3940
+ function coveredMs(intervals) {
3941
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
3942
+ let total = 0;
3943
+ let from = -Infinity;
3944
+ let to = -Infinity;
3945
+ for (const [start, end] of sorted) {
3946
+ if (start > to) {
3947
+ if (to > from) total += to - from;
3948
+ from = start;
3949
+ to = end;
3950
+ } else if (end > to) {
3951
+ to = end;
3952
+ }
3953
+ }
3954
+ if (to > from) total += to - from;
3955
+ return total;
3956
+ }
3876
3957
  function attendanceReport(meeting, attendees, names = {}) {
3877
3958
  const end = ms(meeting.endedAt);
3878
- const rows = attendees.map((person) => {
3959
+ const perJoin = attendees.map((person) => {
3879
3960
  const joined = ms(person.joinedAt);
3880
3961
  const left = ms(person.leftAt);
3881
3962
  const until = left ?? end;
@@ -3890,17 +3971,45 @@ function attendanceReport(meeting, attendees, names = {}) {
3890
3971
  leftAt: person.leftAt,
3891
3972
  durationMs: duration,
3892
3973
  durationToEnd: duration !== null && left === null,
3893
- admission: person.admissionState
3974
+ admission: person.admissionState,
3975
+ joins: 1
3894
3976
  };
3895
3977
  });
3896
- const rank = {
3897
- host: 0,
3898
- cohost: 1,
3899
- member: 2,
3900
- "signed-in guest": 3,
3901
- guest: 4,
3902
- assistant: 5
3903
- };
3978
+ const groups = /* @__PURE__ */ new Map();
3979
+ attendees.forEach((person, index) => {
3980
+ const key = attendeeKey(person, names);
3981
+ const group = groups.get(key) ?? { rows: [], people: [] };
3982
+ group.rows.push(perJoin[index]);
3983
+ group.people.push(person);
3984
+ groups.set(key, group);
3985
+ });
3986
+ const rows = [...groups.values()].map(({ rows: joins }) => {
3987
+ if (joins.length === 1) return joins[0];
3988
+ const came = joins.filter((row) => row.joinedAt !== null);
3989
+ const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
3990
+ const kind = joins.reduce(
3991
+ (best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
3992
+ first.kind
3993
+ );
3994
+ const open = came.some((row) => row.leftAt === null);
3995
+ const lastLeave = came.reduce((latest, row) => {
3996
+ const left = ms(row.leftAt);
3997
+ return left !== null && (latest === null || left > latest) ? left : latest;
3998
+ }, null);
3999
+ const intervals = came.flatMap((row) => {
4000
+ const joined = ms(row.joinedAt);
4001
+ return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
4002
+ });
4003
+ return {
4004
+ ...first,
4005
+ kind,
4006
+ leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
4007
+ durationMs: intervals.length === 0 ? null : coveredMs(intervals),
4008
+ durationToEnd: came.some((row) => row.durationToEnd),
4009
+ joins: came.length === 0 ? joins.length : came.length
4010
+ };
4011
+ });
4012
+ const rank = KIND_RANK;
3904
4013
  return rows.sort((a, b) => {
3905
4014
  if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
3906
4015
  return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
@@ -4058,7 +4167,10 @@ function bullet(note) {
4058
4167
  function notesDocument(bundle, options = {}) {
4059
4168
  const out = [`# ${bundle.meeting.title}`, ""];
4060
4169
  if (options.when) out.push(`_${options.when}_`, "");
4061
- const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
4170
+ const people = uniqueAttendees(
4171
+ bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
4172
+ bundle.names
4173
+ ).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
4062
4174
  if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
4063
4175
  out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
4064
4176
  out.push("## Decisions", "");
@@ -4122,19 +4234,8 @@ async function loadMeetingChat(api, meetingId, roomToken = null) {
4122
4234
  refusal: null
4123
4235
  };
4124
4236
  }
4125
- async function refusalOf(response, operation) {
4126
- let body = null;
4127
- try {
4128
- body = await response.json();
4129
- } catch {
4130
- body = null;
4131
- }
4132
- const said = readServerRefusal(body);
4133
- return new MeetError(
4134
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
4135
- `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
4136
- said?.remedy || "Nothing was sent. Try again."
4137
- );
4237
+ function refusalOf(response, operation) {
4238
+ return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
4138
4239
  }
4139
4240
  function asRecipient(row) {
4140
4241
  const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
@@ -4195,18 +4296,11 @@ async function sendFollowUp(api, meetingId, input) {
4195
4296
  }
4196
4297
 
4197
4298
  // src/core/management.ts
4198
- async function refusal(response, operation) {
4199
- let body = null;
4200
- try {
4201
- body = await response.json();
4202
- } catch {
4203
- body = null;
4204
- }
4205
- const said = readServerRefusal(body);
4206
- return new MeetError(
4207
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
4208
- `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
4209
- said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
4299
+ function refusal(response, operation) {
4300
+ return refusalFromResponse(
4301
+ response,
4302
+ operation,
4303
+ "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
4210
4304
  );
4211
4305
  }
4212
4306
  function count(body, key) {
@@ -4532,6 +4626,10 @@ function createRoomEngine(options) {
4532
4626
  }
4533
4627
  },
4534
4628
  disconnected(reason) {
4629
+ if (current !== null) {
4630
+ tokens.invalidate(current.roomName);
4631
+ current = null;
4632
+ }
4535
4633
  store.patch({ phase: "left" });
4536
4634
  options.onLeft?.(reason);
4537
4635
  }
@@ -4834,19 +4932,8 @@ function createInRoomClient(options) {
4834
4932
  const token = options.resolveRoomToken();
4835
4933
  return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
4836
4934
  }
4837
- async function refusal2(response, operation, remedy) {
4838
- let body = null;
4839
- try {
4840
- body = await response.json();
4841
- } catch {
4842
- body = null;
4843
- }
4844
- const said = readServerRefusal(body);
4845
- return new MeetError(
4846
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
4847
- said?.reason ?? `${operation}: the server answered ${response.status}`,
4848
- said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
4849
- );
4935
+ function refusal2(response, operation, remedy) {
4936
+ return refusalFromResponse(response, operation, remedy);
4850
4937
  }
4851
4938
  return {
4852
4939
  async moderate(request) {
@@ -5396,6 +5483,7 @@ export {
5396
5483
  attendanceCsv,
5397
5484
  attendanceReport,
5398
5485
  attendanceTotals,
5486
+ attendeeKey,
5399
5487
  callInviteAction,
5400
5488
  callInviteText,
5401
5489
  callStateLabel,
@@ -5499,6 +5587,7 @@ export {
5499
5587
  summarizeMeetingRecordRestrictions,
5500
5588
  transcriptDocument,
5501
5589
  unavailableFrom,
5590
+ uniqueAttendees,
5502
5591
  userTopic,
5503
5592
  wasRefused,
5504
5593
  wrapUpStatus