@ai-matrx/meet 0.7.7 → 0.7.8
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 +37 -0
- package/dist/index.cjs +129 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -50
- package/dist/index.d.ts +79 -50
- package/dist/index.js +129 -50
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +827 -701
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +803 -677
- package/dist/react.js.map +1 -1
- package/dist/styles.css +18 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
# Changelog — `@ai-matrx/meet`
|
|
2
2
|
|
|
3
|
+
## 0.7.8
|
|
4
|
+
### (next patch)
|
|
5
|
+
|
|
6
|
+
- **Fix — a refusal never reaches a person as a Python class name** (verifier, 2026-09-27): a
|
|
7
|
+
host's toast read "Internal server error: UnsupportedClientFeatureError". `readServerRefusal`
|
|
8
|
+
now skips raw server text (`Internal server error: …`, a bare `…Error` class, a snake_case
|
|
9
|
+
code) and falls through to the body's `user_message`, or to `SERVER_FAILED_SENTENCE`. The
|
|
10
|
+
three hand-copied refusal builders (`in-room.ts`, `management.ts`, `after.ts`) collapse into
|
|
11
|
+
ONE exported `refusalFromResponse(response, operation, fallbackRemedy)`, whose `message` is
|
|
12
|
+
the server's sentence — no `meet.x:` operation prefix — and whose `remedy` is the server's.
|
|
13
|
+
- **Fix — a refused Remove / Make host keeps its pane open WITH the sentence** : the confirm
|
|
14
|
+
pane now shows the server's refusal inline (`role="alert"`) with its buttons live; before it
|
|
15
|
+
stayed up silently.
|
|
16
|
+
- **Fix — every per-person menu offers "Message privately"**: a tile's menu (Room stage and a
|
|
17
|
+
host layout's strip, e.g. the Board) now opens the private chat through the store
|
|
18
|
+
(`snapshot.privateChatRequest`, answered by the one `ControlBar`); before only the People
|
|
19
|
+
panel could, so two menus for one person disagreed.
|
|
20
|
+
- **Fix — attendance is per PERSON, never per join**: an export listed the same attendee twice
|
|
21
|
+
under "attended". `attendanceReport` merges a person's joins (account = one person across
|
|
22
|
+
devices and rejoins; an accountless guest's rejoin, which gets a fresh `guest:<random>`
|
|
23
|
+
identity, is matched by the typed name) into one row — first arrival, last departure, total
|
|
24
|
+
time with overlaps counted once, and a new `joins` count. `notesDocument`'s "Attended" line
|
|
25
|
+
and the record view's "Who attended" use the new exported `uniqueAttendees` / `attendeeKey`.
|
|
26
|
+
- **Consumer action:** none (a consumer counting `bundle.attendees` itself should count
|
|
27
|
+
`uniqueAttendees(...)` instead).
|
|
28
|
+
|
|
3
29
|
## 0.7.7
|
|
4
30
|
### Meet wave 3: after the meeting
|
|
5
31
|
|
|
32
|
+
- **Fix — the room header on a phone** (2026-09-27, 390px): the header wraps into rows and
|
|
33
|
+
each host control stays one line; before, the title read "Hollo…" and "End meeting for
|
|
34
|
+
everyone" stacked into four underlined words.
|
|
35
|
+
- **Fix — a removed participant reads the server's sentence, not "invalid token: revoked"**
|
|
36
|
+
(2026-09-27, live room): a server-side disconnect now invalidates the cached room token, so
|
|
37
|
+
the next Join mints again and shows "A host removed you from this meeting."
|
|
38
|
+
- **Fix — a host's per-person menu opens beside its button** (2026-09-27, live room): the
|
|
39
|
+
popover was hidden but IN FLOW on its first frame, so the anchor it measured was button +
|
|
40
|
+
menu and it opened a full menu-height lower — Make host, Report and Remove from meeting were
|
|
41
|
+
below the fold. It is now fixed-positioned from the first frame, re-placed when the layout
|
|
42
|
+
moves, and pinned + scrollable when neither side of the button holds it.
|
|
6
43
|
- **Fix — the waiting room for people from another organization and for denied guests**
|
|
7
44
|
(2026-09-27, verified in a real room): a signed-in link-follower now knocks as `user:<id>`, the
|
|
8
45
|
identity the server's lobby row carries — before, the host saw two entries and admitting one
|
package/dist/index.cjs
CHANGED
|
@@ -84,6 +84,7 @@ __export(src_exports, {
|
|
|
84
84
|
attendanceCsv: () => attendanceCsv,
|
|
85
85
|
attendanceReport: () => attendanceReport,
|
|
86
86
|
attendanceTotals: () => attendanceTotals,
|
|
87
|
+
attendeeKey: () => attendeeKey,
|
|
87
88
|
callInviteAction: () => callInviteAction,
|
|
88
89
|
callInviteText: () => callInviteText,
|
|
89
90
|
callStateLabel: () => callStateLabel,
|
|
@@ -187,6 +188,7 @@ __export(src_exports, {
|
|
|
187
188
|
summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
|
|
188
189
|
transcriptDocument: () => transcriptDocument,
|
|
189
190
|
unavailableFrom: () => unavailableFrom,
|
|
191
|
+
uniqueAttendees: () => uniqueAttendees,
|
|
190
192
|
userTopic: () => userTopic,
|
|
191
193
|
wasRefused: () => wasRefused,
|
|
192
194
|
wrapUpStatus: () => wrapUpStatus
|
|
@@ -677,6 +679,29 @@ function readText(data) {
|
|
|
677
679
|
}
|
|
678
680
|
return "";
|
|
679
681
|
}
|
|
682
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
683
|
+
function isRawServerText(value) {
|
|
684
|
+
const text3 = value.trim();
|
|
685
|
+
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);
|
|
686
|
+
}
|
|
687
|
+
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
688
|
+
let body = null;
|
|
689
|
+
try {
|
|
690
|
+
body = await response.json();
|
|
691
|
+
} catch {
|
|
692
|
+
body = null;
|
|
693
|
+
}
|
|
694
|
+
const said = readServerRefusal(body);
|
|
695
|
+
const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
|
|
696
|
+
const error = new MeetError(
|
|
697
|
+
code,
|
|
698
|
+
said?.reason ?? SERVER_FAILED_SENTENCE,
|
|
699
|
+
said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
|
|
700
|
+
);
|
|
701
|
+
error.operation = operation;
|
|
702
|
+
error.status = response.status;
|
|
703
|
+
return error;
|
|
704
|
+
}
|
|
680
705
|
function readServerRefusal(body) {
|
|
681
706
|
if (typeof body === "string") {
|
|
682
707
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -695,7 +720,8 @@ function readServerRefusal(body) {
|
|
|
695
720
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
696
721
|
}
|
|
697
722
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
698
|
-
const
|
|
723
|
+
const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
|
|
724
|
+
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);
|
|
699
725
|
if (reason === null) return null;
|
|
700
726
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
701
727
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -3378,6 +3404,7 @@ var EMPTY = {
|
|
|
3378
3404
|
spotlight: null,
|
|
3379
3405
|
moderationNotice: null,
|
|
3380
3406
|
actionNotice: null,
|
|
3407
|
+
privateChatRequest: null,
|
|
3381
3408
|
effects: DEFAULT_EFFECTS,
|
|
3382
3409
|
transcript: [],
|
|
3383
3410
|
notes: [],
|
|
@@ -4068,9 +4095,55 @@ function ms(iso) {
|
|
|
4068
4095
|
const at = new Date(iso).getTime();
|
|
4069
4096
|
return Number.isNaN(at) ? null : at;
|
|
4070
4097
|
}
|
|
4098
|
+
function attendeeKey(person, names = {}) {
|
|
4099
|
+
if (person.userId !== null) return `user:${person.userId}`;
|
|
4100
|
+
if (person.isAgent) return `agent:${person.identity}`;
|
|
4101
|
+
const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
|
|
4102
|
+
return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
|
|
4103
|
+
}
|
|
4104
|
+
function uniqueAttendees(attendees, names = {}) {
|
|
4105
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
4106
|
+
for (const person of attendees) {
|
|
4107
|
+
const key = attendeeKey(person, names);
|
|
4108
|
+
const kept = byKey.get(key);
|
|
4109
|
+
if (kept === void 0) {
|
|
4110
|
+
byKey.set(key, person);
|
|
4111
|
+
continue;
|
|
4112
|
+
}
|
|
4113
|
+
const keptJoin = ms(kept.joinedAt);
|
|
4114
|
+
const join = ms(person.joinedAt);
|
|
4115
|
+
if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
|
|
4116
|
+
}
|
|
4117
|
+
return [...byKey.values()];
|
|
4118
|
+
}
|
|
4119
|
+
var KIND_RANK = {
|
|
4120
|
+
host: 0,
|
|
4121
|
+
cohost: 1,
|
|
4122
|
+
member: 2,
|
|
4123
|
+
"signed-in guest": 3,
|
|
4124
|
+
guest: 4,
|
|
4125
|
+
assistant: 5
|
|
4126
|
+
};
|
|
4127
|
+
function coveredMs(intervals) {
|
|
4128
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
4129
|
+
let total = 0;
|
|
4130
|
+
let from = -Infinity;
|
|
4131
|
+
let to = -Infinity;
|
|
4132
|
+
for (const [start, end] of sorted) {
|
|
4133
|
+
if (start > to) {
|
|
4134
|
+
if (to > from) total += to - from;
|
|
4135
|
+
from = start;
|
|
4136
|
+
to = end;
|
|
4137
|
+
} else if (end > to) {
|
|
4138
|
+
to = end;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
if (to > from) total += to - from;
|
|
4142
|
+
return total;
|
|
4143
|
+
}
|
|
4071
4144
|
function attendanceReport(meeting, attendees, names = {}) {
|
|
4072
4145
|
const end = ms(meeting.endedAt);
|
|
4073
|
-
const
|
|
4146
|
+
const perJoin = attendees.map((person) => {
|
|
4074
4147
|
const joined = ms(person.joinedAt);
|
|
4075
4148
|
const left = ms(person.leftAt);
|
|
4076
4149
|
const until = left ?? end;
|
|
@@ -4085,17 +4158,45 @@ function attendanceReport(meeting, attendees, names = {}) {
|
|
|
4085
4158
|
leftAt: person.leftAt,
|
|
4086
4159
|
durationMs: duration,
|
|
4087
4160
|
durationToEnd: duration !== null && left === null,
|
|
4088
|
-
admission: person.admissionState
|
|
4161
|
+
admission: person.admissionState,
|
|
4162
|
+
joins: 1
|
|
4089
4163
|
};
|
|
4090
4164
|
});
|
|
4091
|
-
const
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
};
|
|
4165
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4166
|
+
attendees.forEach((person, index) => {
|
|
4167
|
+
const key = attendeeKey(person, names);
|
|
4168
|
+
const group = groups.get(key) ?? { rows: [], people: [] };
|
|
4169
|
+
group.rows.push(perJoin[index]);
|
|
4170
|
+
group.people.push(person);
|
|
4171
|
+
groups.set(key, group);
|
|
4172
|
+
});
|
|
4173
|
+
const rows = [...groups.values()].map(({ rows: joins }) => {
|
|
4174
|
+
if (joins.length === 1) return joins[0];
|
|
4175
|
+
const came = joins.filter((row) => row.joinedAt !== null);
|
|
4176
|
+
const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
|
|
4177
|
+
const kind = joins.reduce(
|
|
4178
|
+
(best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
|
|
4179
|
+
first.kind
|
|
4180
|
+
);
|
|
4181
|
+
const open = came.some((row) => row.leftAt === null);
|
|
4182
|
+
const lastLeave = came.reduce((latest, row) => {
|
|
4183
|
+
const left = ms(row.leftAt);
|
|
4184
|
+
return left !== null && (latest === null || left > latest) ? left : latest;
|
|
4185
|
+
}, null);
|
|
4186
|
+
const intervals = came.flatMap((row) => {
|
|
4187
|
+
const joined = ms(row.joinedAt);
|
|
4188
|
+
return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
|
|
4189
|
+
});
|
|
4190
|
+
return {
|
|
4191
|
+
...first,
|
|
4192
|
+
kind,
|
|
4193
|
+
leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
|
|
4194
|
+
durationMs: intervals.length === 0 ? null : coveredMs(intervals),
|
|
4195
|
+
durationToEnd: came.some((row) => row.durationToEnd),
|
|
4196
|
+
joins: came.length === 0 ? joins.length : came.length
|
|
4197
|
+
};
|
|
4198
|
+
});
|
|
4199
|
+
const rank = KIND_RANK;
|
|
4099
4200
|
return rows.sort((a, b) => {
|
|
4100
4201
|
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
4101
4202
|
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
@@ -4253,7 +4354,10 @@ function bullet(note) {
|
|
|
4253
4354
|
function notesDocument(bundle, options = {}) {
|
|
4254
4355
|
const out = [`# ${bundle.meeting.title}`, ""];
|
|
4255
4356
|
if (options.when) out.push(`_${options.when}_`, "");
|
|
4256
|
-
const people =
|
|
4357
|
+
const people = uniqueAttendees(
|
|
4358
|
+
bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
|
|
4359
|
+
bundle.names
|
|
4360
|
+
).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
4257
4361
|
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
4258
4362
|
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
4259
4363
|
out.push("## Decisions", "");
|
|
@@ -4317,19 +4421,8 @@ async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
|
4317
4421
|
refusal: null
|
|
4318
4422
|
};
|
|
4319
4423
|
}
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
try {
|
|
4323
|
-
body = await response.json();
|
|
4324
|
-
} catch {
|
|
4325
|
-
body = null;
|
|
4326
|
-
}
|
|
4327
|
-
const said = readServerRefusal(body);
|
|
4328
|
-
return new MeetError(
|
|
4329
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4330
|
-
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
4331
|
-
said?.remedy || "Nothing was sent. Try again."
|
|
4332
|
-
);
|
|
4424
|
+
function refusalOf(response, operation) {
|
|
4425
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
4333
4426
|
}
|
|
4334
4427
|
function asRecipient(row) {
|
|
4335
4428
|
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
@@ -4390,18 +4483,11 @@ async function sendFollowUp(api, meetingId, input) {
|
|
|
4390
4483
|
}
|
|
4391
4484
|
|
|
4392
4485
|
// src/core/management.ts
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
body = null;
|
|
4399
|
-
}
|
|
4400
|
-
const said = readServerRefusal(body);
|
|
4401
|
-
return new MeetError(
|
|
4402
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4403
|
-
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
4404
|
-
said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
4486
|
+
function refusal(response, operation) {
|
|
4487
|
+
return refusalFromResponse(
|
|
4488
|
+
response,
|
|
4489
|
+
operation,
|
|
4490
|
+
"The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
4405
4491
|
);
|
|
4406
4492
|
}
|
|
4407
4493
|
function count(body, key) {
|
|
@@ -4727,6 +4813,10 @@ function createRoomEngine(options) {
|
|
|
4727
4813
|
}
|
|
4728
4814
|
},
|
|
4729
4815
|
disconnected(reason) {
|
|
4816
|
+
if (current !== null) {
|
|
4817
|
+
tokens.invalidate(current.roomName);
|
|
4818
|
+
current = null;
|
|
4819
|
+
}
|
|
4730
4820
|
store.patch({ phase: "left" });
|
|
4731
4821
|
options.onLeft?.(reason);
|
|
4732
4822
|
}
|
|
@@ -5029,19 +5119,8 @@ function createInRoomClient(options) {
|
|
|
5029
5119
|
const token = options.resolveRoomToken();
|
|
5030
5120
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
5031
5121
|
}
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
try {
|
|
5035
|
-
body = await response.json();
|
|
5036
|
-
} catch {
|
|
5037
|
-
body = null;
|
|
5038
|
-
}
|
|
5039
|
-
const said = readServerRefusal(body);
|
|
5040
|
-
return new MeetError(
|
|
5041
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
5042
|
-
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
5043
|
-
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
5044
|
-
);
|
|
5122
|
+
function refusal2(response, operation, remedy) {
|
|
5123
|
+
return refusalFromResponse(response, operation, remedy);
|
|
5045
5124
|
}
|
|
5046
5125
|
return {
|
|
5047
5126
|
async moderate(request) {
|