accelr8 3.8.1 → 4.0.0-next.1

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.
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AuthLockTimeout,
4
+ credentialsKey,
5
+ deleteCredentials,
6
+ loadCredentials,
7
+ loadLegacyIdentity,
8
+ loadTokens,
9
+ saveCredentials,
10
+ withAuthLock
11
+ } from "./chunk-FVC2UPMV.js";
12
+ import {
13
+ PORT,
14
+ VERSION
15
+ } from "./chunk-5QXGOLQ3.js";
16
+
17
+ // src/authclient.ts
18
+ import os from "node:os";
19
+ import { createHash } from "node:crypto";
20
+ var sha256hex = (s) => createHash("sha256").update(s).digest("hex");
21
+ function legacyProofs(serverUrl, baseDir) {
22
+ const server = credentialsKey(serverUrl);
23
+ const out = [];
24
+ for (const [key, token] of Object.entries(loadTokens(baseDir))) {
25
+ const i = key.indexOf(":");
26
+ if (i <= 0 || !token) continue;
27
+ out.push({ projectId: key.slice(0, i), handle: key.slice(i + 1), proof: sha256hex(sha256hex(token) + "\n" + server) });
28
+ if (out.length >= 20) break;
29
+ }
30
+ return out;
31
+ }
32
+ var REFRESH_EARLY_MS = 6e4;
33
+ function httpBase(serverUrl) {
34
+ const key = credentialsKey(serverUrl);
35
+ return key.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
36
+ }
37
+ function approveUrl(serverUrl) {
38
+ return httpBase(serverUrl) + "/approve";
39
+ }
40
+ var AuthNetworkError = class extends Error {
41
+ };
42
+ async function call(url, init = {}) {
43
+ let res;
44
+ try {
45
+ res = await fetch(url, {
46
+ method: init.method ?? (init.body === void 0 ? "GET" : "POST"),
47
+ headers: {
48
+ ...init.body === void 0 ? {} : { "content-type": "application/json" },
49
+ ...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
50
+ },
51
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
52
+ signal: AbortSignal.timeout(15e3)
53
+ });
54
+ } catch (e) {
55
+ throw new AuthNetworkError(`could not reach ${new URL(url).origin}: ${e instanceof Error ? e.message : e}`);
56
+ }
57
+ let data = null;
58
+ try {
59
+ data = await res.json();
60
+ } catch {
61
+ }
62
+ return { status: res.status, data };
63
+ }
64
+ function toCredentials(serverUrl, r, now = Date.now()) {
65
+ return {
66
+ server: credentialsKey(serverUrl),
67
+ user: { id: r.user.id, name: r.user.name, color: r.user.color },
68
+ sessionId: r.sessionId,
69
+ accessToken: r.accessToken,
70
+ accessExpiresAt: now + r.accessExpiresIn * 1e3,
71
+ refreshToken: r.refreshToken,
72
+ refreshExpiresAt: now + r.refreshExpiresIn * 1e3,
73
+ signedInAt: now
74
+ };
75
+ }
76
+ var isTokens = (d) => !!d && typeof d.accessToken === "string" && typeof d.refreshToken === "string" && !!d.user;
77
+ function defaultName() {
78
+ try {
79
+ return os.userInfo().username;
80
+ } catch {
81
+ return process.env.USER || "guest";
82
+ }
83
+ }
84
+ var OLDER_SERVER = /* @__PURE__ */ new Set([404, 405, 426]);
85
+ function olderServerMessage(serverUrl) {
86
+ return [
87
+ `${credentialsKey(serverUrl)} runs an older accelr8 with no sign-in yet, so this version can't use it.`,
88
+ "To try this version, start your own server in another terminal:",
89
+ ` accelr8 serve --host 127.0.0.1`,
90
+ "then open accelr8 on it:",
91
+ ` accelr8 --server ws://127.0.0.1:${PORT}`
92
+ ].join("\n ");
93
+ }
94
+ async function deviceLogin(serverUrl, opts = {}) {
95
+ const print = opts.print ?? ((l) => console.log(l));
96
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
97
+ const base = httpBase(serverUrl);
98
+ const legacy = loadLegacyIdentity(opts.baseDir);
99
+ const startRes = await call(`${base}/auth/device`, {
100
+ body: {
101
+ machine: (opts.machine ?? os.hostname()).slice(0, 64),
102
+ platform: process.platform,
103
+ name: opts.name || legacy.name || defaultName(),
104
+ legacyId: legacy.id,
105
+ legacyName: legacy.name,
106
+ // old ids were public: the old projects come along only with proof
107
+ legacyProof: legacy.id ? legacyProofs(serverUrl, opts.baseDir) : void 0,
108
+ version: VERSION
109
+ }
110
+ });
111
+ if (startRes.status !== 200 || typeof startRes.data?.deviceCode !== "string") {
112
+ if (!startRes.data?.message && OLDER_SERVER.has(startRes.status)) throw new Error(olderServerMessage(serverUrl));
113
+ throw new Error(startRes.data?.message || `the server refused to start a sign-in (HTTP ${startRes.status})`);
114
+ }
115
+ const start = startRes.data;
116
+ print("");
117
+ print(` To sign in, open this page in your browser:`);
118
+ print(` ${approveUrl(serverUrl)}`);
119
+ print(` and type this code:`);
120
+ print(` ${start.userCode}`);
121
+ print("");
122
+ print(` Waiting for you to approve it. The code expires in ${Math.round(start.expiresIn / 60)} minutes.`);
123
+ await opts.onCode?.(start);
124
+ let interval = Math.max(1, start.interval) * 1e3;
125
+ const deadline = Date.now() + start.expiresIn * 1e3;
126
+ for (; ; ) {
127
+ const r = await call(`${base}/auth/device/token`, { body: { deviceCode: start.deviceCode } });
128
+ if (r.status === 200 && isTokens(r.data)) {
129
+ const creds = toCredentials(serverUrl, r.data);
130
+ saveCredentials(creds, opts.baseDir);
131
+ return creds;
132
+ }
133
+ if (r.status === 200 && r.data?.pending) {
134
+ } else if (r.data?.error === "slow_down") interval += 5e3;
135
+ else throw new Error(r.data?.message || `sign-in failed (HTTP ${r.status})`);
136
+ if (Date.now() + interval > deadline) throw new Error("The code expired. Run accelr8 login again for a new one.");
137
+ await sleep(interval);
138
+ }
139
+ }
140
+ async function refreshCredentials(serverUrl, opts = {}) {
141
+ const before = loadCredentials(serverUrl, opts.baseDir);
142
+ try {
143
+ return await withAuthLock(async () => {
144
+ const creds = loadCredentials(serverUrl, opts.baseDir);
145
+ if (!creds) return null;
146
+ if (!opts.force && creds.accessExpiresAt - Date.now() > REFRESH_EARLY_MS) return creds;
147
+ if (opts.force && before && creds.refreshToken !== before.refreshToken) return creds;
148
+ const r = await call(`${httpBase(serverUrl)}/auth/refresh`, { body: { refreshToken: creds.refreshToken } });
149
+ if (r.status === 200 && isTokens(r.data)) {
150
+ const next = { ...toCredentials(serverUrl, r.data), signedInAt: creds.signedInAt };
151
+ saveCredentials(next, opts.baseDir);
152
+ return next;
153
+ }
154
+ if (r.data?.error === "invalid_grant") {
155
+ deleteCredentials(serverUrl, opts.baseDir, (saved) => saved.refreshToken === creds.refreshToken);
156
+ return null;
157
+ }
158
+ throw new AuthNetworkError(`refresh failed (HTTP ${r.status})`);
159
+ }, opts.baseDir, opts.lock);
160
+ } catch (e) {
161
+ if (!(e instanceof AuthLockTimeout)) throw e;
162
+ const now = loadCredentials(serverUrl, opts.baseDir);
163
+ if (now && (!before || now.refreshToken !== before.refreshToken)) return now;
164
+ throw new AuthNetworkError("another accelr8 process is refreshing this sign-in; trying again shortly");
165
+ }
166
+ }
167
+ async function getAccessToken(serverUrl, opts = {}) {
168
+ const creds = loadCredentials(serverUrl, opts.baseDir);
169
+ if (!creds) return null;
170
+ if (!opts.force && creds.accessExpiresAt - Date.now() > REFRESH_EARLY_MS) return { token: creds.accessToken, creds };
171
+ try {
172
+ const next = await refreshCredentials(serverUrl, opts);
173
+ return next ? { token: next.accessToken, creds: next } : null;
174
+ } catch (e) {
175
+ if (e instanceof AuthNetworkError) return { token: creds.accessToken, creds };
176
+ throw e;
177
+ }
178
+ }
179
+ async function logout(serverUrl, opts = {}) {
180
+ const creds = loadCredentials(serverUrl, opts.baseDir);
181
+ if (!creds) return { wasSignedIn: false, revoked: null };
182
+ let revoked = null;
183
+ try {
184
+ const r = await call(`${httpBase(serverUrl)}/auth/logout`, { body: { refreshToken: creds.refreshToken, all: opts.all === true }, bearer: creds.accessToken });
185
+ if (r.status === 200) revoked = Number(r.data?.revoked ?? 0);
186
+ } catch {
187
+ }
188
+ deleteCredentials(serverUrl, opts.baseDir);
189
+ return { wasSignedIn: true, user: creds.user, revoked };
190
+ }
191
+ async function whoami(serverUrl, opts = {}) {
192
+ const local = loadCredentials(serverUrl, opts.baseDir);
193
+ if (!local) return null;
194
+ let tok;
195
+ try {
196
+ tok = await getAccessToken(serverUrl, opts);
197
+ } catch {
198
+ tok = { token: local.accessToken, creds: local };
199
+ }
200
+ if (!tok) return null;
201
+ try {
202
+ let r = await call(`${httpBase(serverUrl)}/auth/me`, { bearer: tok.token });
203
+ if (r.status === 401) {
204
+ const again = await getAccessToken(serverUrl, { ...opts, force: true });
205
+ if (!again) return null;
206
+ tok = again;
207
+ r = await call(`${httpBase(serverUrl)}/auth/me`, { bearer: tok.token });
208
+ }
209
+ if (r.status === 200 && r.data?.user) {
210
+ const user = r.data.user;
211
+ if (user.name !== tok.creds.user.name || user.color !== tok.creds.user.color) {
212
+ saveCredentials({ ...tok.creds, user: { id: user.id, name: user.name, color: user.color } }, opts.baseDir);
213
+ }
214
+ return { server: tok.creds.server, user, machine: r.data.session?.machine, since: r.data.session?.createdAt, verified: true };
215
+ }
216
+ return { server: tok.creds.server, user: tok.creds.user, since: tok.creds.signedInAt, verified: false, note: `server answered HTTP ${r.status}` };
217
+ } catch (e) {
218
+ return { server: tok.creds.server, user: tok.creds.user, since: tok.creds.signedInAt, verified: false, note: e instanceof Error ? e.message : String(e) };
219
+ }
220
+ }
221
+ async function signedInCall(serverUrl, route, body, baseDir) {
222
+ const tok = await getAccessToken(serverUrl, { baseDir });
223
+ if (!tok) throw new Error(`This machine is not signed in to ${credentialsKey(serverUrl)}. Run accelr8 login first.`);
224
+ return call(`${httpBase(serverUrl)}${route}`, { body, bearer: tok.token });
225
+ }
226
+ async function pendingSignIn(serverUrl, userCode, opts = {}) {
227
+ const r = await signedInCall(serverUrl, "/auth/device/pending", { userCode }, opts.baseDir);
228
+ if (r.status !== 200 || typeof r.data?.machine !== "string") throw new Error(r.data?.message || `the server refused (HTTP ${r.status})`);
229
+ return r.data;
230
+ }
231
+ async function approveSignIn(serverUrl, userCode, opts = {}) {
232
+ const r = await signedInCall(serverUrl, "/auth/device/approve", { userCode }, opts.baseDir);
233
+ if (r.status !== 200 || !r.data?.user) throw new Error(r.data?.message || `the server refused (HTTP ${r.status})`);
234
+ return r.data.user;
235
+ }
236
+ async function ensureSignedIn(serverUrl, opts = {}) {
237
+ const have = loadCredentials(serverUrl, opts.baseDir);
238
+ if (have) return have;
239
+ const print = opts.print ?? ((l) => console.log(l));
240
+ print(`You are not signed in to ${credentialsKey(serverUrl)} yet.`);
241
+ try {
242
+ const creds = await deviceLogin(serverUrl, opts);
243
+ print(` Signed in as ${creds.user.name}. Opening accelr8...`);
244
+ return creds;
245
+ } catch (e) {
246
+ print(` ${e instanceof Error ? e.message : e}`);
247
+ return null;
248
+ }
249
+ }
250
+
251
+ export {
252
+ legacyProofs,
253
+ httpBase,
254
+ approveUrl,
255
+ AuthNetworkError,
256
+ olderServerMessage,
257
+ deviceLogin,
258
+ refreshCredentials,
259
+ getAccessToken,
260
+ logout,
261
+ whoami,
262
+ pendingSignIn,
263
+ approveSignIn,
264
+ ensureSignedIn
265
+ };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/protocol.ts
4
+ var VERSION = true ? "4.0.0-next.1" : "4.0.0-next.1";
5
+ var PROTO = 4;
6
+ var PORT = 8787;
7
+ var USER_COLORS = [39, 213, 220, 46, 208, 141, 51, 203, 118, 165];
8
+ function colorForId(id) {
9
+ let h = 0;
10
+ for (const ch of id) h = h * 31 + ch.charCodeAt(0) >>> 0;
11
+ return USER_COLORS[h % USER_COLORS.length];
12
+ }
13
+ var DEFAULT_SERVER = "wss://accelr8-canvas.patonchain.workers.dev";
14
+ var CANVAS_W = 100;
15
+ var CANVAS_H = 30;
16
+ var LIMITS = {
17
+ chatMaxCp: 4e3,
18
+ // code points per chat message
19
+ chatMaxJson: 8192,
20
+ // serialized bytes per chat message
21
+ chatPerWindow: 10,
22
+ // messages per 10s per session
23
+ chatTail: 50,
24
+ // messages delivered in `joined`
25
+ chatPage: 50,
26
+ // default history page (max 100)
27
+ chatSyncMaxGap: 200,
28
+ // bigger gap -> full resync
29
+ chatRetain: 2e3,
30
+ // messages kept per project
31
+ draftMinIntervalMs: 100,
32
+ // server drops faster arrivals
33
+ draftMaxCp: 4e3,
34
+ // Frame budgets, per connection, per 10s window. cursor and paint have their
35
+ // own budgets because they are continuous by nature (the client sends cursor
36
+ // at 25/s, and one paint per cell while dragging): over budget they are
37
+ // DROPPED, never a disconnect. So do the executor frames an owner's terminal
38
+ // streams back (agentAccept/Delta/Done/Error: each reply sends up to 10
39
+ // deltas a second, and one terminal may run several replies at once): over
40
+ // budget a delta is dropped, and the frames that start or end a reply always
41
+ // pass. Everything else shares framesPerWindow, and a connection over
42
+ // hardFramesPerWindow in total is closed as a flood.
43
+ framesPerWindow: 200,
44
+ // other frames per 10s -> disconnect
45
+ cursorPerWindow: 400,
46
+ // cursor frames per 10s (client sends <= 250) -> drop extras
47
+ paintPerWindow: 1500,
48
+ // paint frames per 10s (fast drags burst) -> drop extras
49
+ execPerWindow: 1e3,
50
+ // executor frames per 10s (10 replies at once) -> drop extra deltas
51
+ hardFramesPerWindow: 4e3,
52
+ // all non-voice frames per 10s -> disconnect
53
+ invokesPerProjectMin: 20,
54
+ triggersPerUserMin: 6,
55
+ // messages that @mention an agent, per user per minute
56
+ // Wrong join codes. A code is the only way into a project, so guesses are
57
+ // limited three ways: per account, per network (an IPv4 address or an IPv6
58
+ // /64; a network that keeps hitting it is locked out for 10, 20, 40...
59
+ // minutes, up to 6 hours), and server-wide. Right codes are never counted.
60
+ joinGuessesPerMin: 5,
61
+ // per signed-in user (was per connection)
62
+ joinGuessesPerNet: 10,
63
+ // per network per 10 minutes
64
+ joinGuessesServerPerMin: 60,
65
+ // all wrong codes on the server, per minute
66
+ joinCodeLength: 8,
67
+ // 32 symbols, uniform: 40 bits
68
+ renamesPerHour: 10,
69
+ revealsPerMin: 10,
70
+ secretsPerProject: 50,
71
+ secretValueMax: 8192,
72
+ agentQueueCap: 5,
73
+ agentMaxDepth: 3,
74
+ agentBudgetPerTrigger: 5,
75
+ agentRepeatPerChain: 2,
76
+ voiceFramesPerSec: 50,
77
+ // ~25/s real, 2x headroom; exempt from framesPerWindow
78
+ voiceFrameMaxB64: 4096
79
+ // a 40ms s16le mono chunk is ~1.7KB base64; cap slack
80
+ };
81
+ var DEFAULT_ACCESS = { mode: "owner", users: [] };
82
+ function encode(msg) {
83
+ return JSON.stringify(msg);
84
+ }
85
+ function decode(data) {
86
+ return JSON.parse(data);
87
+ }
88
+
89
+ export {
90
+ VERSION,
91
+ PROTO,
92
+ PORT,
93
+ colorForId,
94
+ DEFAULT_SERVER,
95
+ CANVAS_W,
96
+ CANVAS_H,
97
+ LIMITS,
98
+ DEFAULT_ACCESS,
99
+ encode,
100
+ decode
101
+ };
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/home.ts
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ function accelr8Dir() {
7
+ const override = process.env.ACCELR8_HOME;
8
+ return override && override.trim() ? path.resolve(override) : path.join(os.homedir(), ".accelr8");
9
+ }
10
+
11
+ export {
12
+ accelr8Dir
13
+ };
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/livekit-token.ts
4
+ function isVoiceUrl(url) {
5
+ try {
6
+ const u = new URL(url);
7
+ return ["ws:", "wss:", "http:", "https:"].includes(u.protocol) && !!u.hostname && !u.username && !u.password;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+ function parseVoiceIdentity(identity) {
13
+ const m = /^sess:([A-Za-z0-9_.@-]{1,128})$/.exec(identity);
14
+ return m ? { sid: m[1] } : null;
15
+ }
16
+ function sanitizeVoiceName(v, maxCp = 32) {
17
+ let out = "";
18
+ let n = 0;
19
+ for (const ch of String(v ?? "")) {
20
+ if (n >= maxCp) break;
21
+ const cp = ch.codePointAt(0);
22
+ if (cp < 32 || cp >= 127 && cp <= 159) continue;
23
+ if (cp >= 8203 && cp <= 8207 || cp >= 8234 && cp <= 8238 || cp >= 8294 && cp <= 8297 || cp === 65279) continue;
24
+ out += ch;
25
+ n++;
26
+ }
27
+ return out.trim() || "guest";
28
+ }
29
+ function decodeVoiceMetadata(raw) {
30
+ if (typeof raw !== "string" || !raw || raw.length > 2048) return null;
31
+ try {
32
+ const o = JSON.parse(raw);
33
+ if (!o || typeof o !== "object" || o.v !== 1) return null;
34
+ return {
35
+ v: 1,
36
+ sid: typeof o.sid === "string" ? o.sid.slice(0, 128) : "",
37
+ userId: typeof o.userId === "string" ? o.userId.slice(0, 64) : "",
38
+ name: sanitizeVoiceName(o.name),
39
+ color: Number.isInteger(o.color) ? o.color : 7,
40
+ client: o.client === "web" ? "web" : "tui"
41
+ };
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ var enc = new TextEncoder();
47
+
48
+ // src/livekit-core.ts
49
+ function initialVoiceState(room = "", identity = "") {
50
+ return {
51
+ connection: "connecting",
52
+ room,
53
+ identity,
54
+ muted: true,
55
+ participants: [],
56
+ inputDeviceId: null,
57
+ playbackBlocked: false,
58
+ needsClick: false,
59
+ error: null,
60
+ endReason: null
61
+ };
62
+ }
63
+ var CONNECTIONS = ["connecting", "connected", "reconnecting", "disconnected"];
64
+ var REASONS = ["left", "replaced", "removed", "room_closed", "network", "error", "window_closed"];
65
+ function normalizeVoiceState(raw) {
66
+ if (!raw || typeof raw !== "object") return {};
67
+ const r = raw;
68
+ const out = {};
69
+ if (typeof r.connection === "string" && CONNECTIONS.includes(r.connection)) out.connection = r.connection;
70
+ if (typeof r.room === "string") out.room = r.room.slice(0, 256);
71
+ if (typeof r.identity === "string") out.identity = r.identity.slice(0, 256);
72
+ if (typeof r.muted === "boolean") out.muted = r.muted;
73
+ if (typeof r.playbackBlocked === "boolean") out.playbackBlocked = r.playbackBlocked;
74
+ if (typeof r.needsClick === "boolean") out.needsClick = r.needsClick;
75
+ if (r.inputDeviceId === null || typeof r.inputDeviceId === "string") out.inputDeviceId = r.inputDeviceId === null ? null : r.inputDeviceId.slice(0, 256);
76
+ if (r.error === null || typeof r.error === "string") out.error = r.error === null ? null : sanitizeText(r.error, 200);
77
+ if (r.endReason === null || typeof r.endReason === "string" && REASONS.includes(r.endReason)) out.endReason = r.endReason ?? null;
78
+ if (Array.isArray(r.participants)) {
79
+ out.participants = r.participants.slice(0, 100).flatMap((p) => {
80
+ if (!p || typeof p !== "object") return [];
81
+ const q = p;
82
+ if (typeof q.identity !== "string" || !q.identity) return [];
83
+ return [{
84
+ identity: q.identity.slice(0, 256),
85
+ sid: typeof q.sid === "string" ? q.sid.slice(0, 128) : null,
86
+ userId: typeof q.userId === "string" ? q.userId.slice(0, 64) : null,
87
+ name: sanitizeVoiceName(q.name),
88
+ color: Number.isInteger(q.color) ? q.color : null,
89
+ client: q.client === "web" ? "web" : q.client === "tui" ? "tui" : null,
90
+ isLocal: q.isLocal === true,
91
+ muted: q.muted !== false,
92
+ speaking: q.speaking === true
93
+ }];
94
+ });
95
+ }
96
+ return out;
97
+ }
98
+ function sanitizeText(s, max) {
99
+ let out = "";
100
+ for (const ch of s) {
101
+ const cp = ch.codePointAt(0);
102
+ if (cp < 32 || cp >= 127 && cp <= 159 || cp >= 8234 && cp <= 8238 || cp >= 8294 && cp <= 8297) continue;
103
+ out += ch;
104
+ if (out.length >= max) break;
105
+ }
106
+ return out;
107
+ }
108
+ function sortParticipants(list) {
109
+ return [...list].sort((a, b) => a.isLocal !== b.isLocal ? a.isLocal ? -1 : 1 : a.name.localeCompare(b.name) || a.identity.localeCompare(b.identity));
110
+ }
111
+ var VoiceStore = class {
112
+ state;
113
+ listeners = /* @__PURE__ */ new Set();
114
+ constructor(initial) {
115
+ this.state = initial;
116
+ }
117
+ snapshot() {
118
+ return this.state;
119
+ }
120
+ subscribe(fn) {
121
+ this.listeners.add(fn);
122
+ return () => {
123
+ this.listeners.delete(fn);
124
+ };
125
+ }
126
+ update(patch) {
127
+ const prev = this.state;
128
+ if (prev.connection === "disconnected" && prev.endReason) return;
129
+ const next = { ...prev, ...patch };
130
+ if (patch.participants) next.participants = sortParticipants(patch.participants);
131
+ if (prev.endReason && !next.endReason) next.endReason = prev.endReason;
132
+ if (next.connection === "disconnected") {
133
+ next.participants = [];
134
+ next.muted = true;
135
+ next.needsClick = false;
136
+ }
137
+ if (JSON.stringify(prev) === JSON.stringify(next)) return;
138
+ this.state = next;
139
+ for (const fn of [...this.listeners]) {
140
+ try {
141
+ fn(next);
142
+ } catch {
143
+ }
144
+ }
145
+ }
146
+ };
147
+ function mapLiveKitDisconnect(code, wasConnected = false) {
148
+ if (code === void 0 && wasConnected) return "network";
149
+ switch (code) {
150
+ case 1:
151
+ return "left";
152
+ // CLIENT_INITIATED
153
+ case 2:
154
+ return "replaced";
155
+ // DUPLICATE_IDENTITY
156
+ case 4:
157
+ return "removed";
158
+ // PARTICIPANT_REMOVED
159
+ case 5:
160
+ case 10:
161
+ return "room_closed";
162
+ case 3:
163
+ case 7:
164
+ case 9:
165
+ case 14:
166
+ case 15:
167
+ return "network";
168
+ default:
169
+ return "error";
170
+ }
171
+ }
172
+ var RTC_NODE = "@livekit/rtc-node";
173
+ var RTC_NODE_VERSION = "1.2.1";
174
+ function rtcNodeMissingHint() {
175
+ return `in-terminal voice needs ${RTC_NODE}@${RTC_NODE_VERSION} \xB7 npm i -g ${RTC_NODE}@${RTC_NODE_VERSION} \xB7 or /voice for the window`;
176
+ }
177
+ function describeEnd(reason) {
178
+ switch (reason) {
179
+ case "left":
180
+ return "voice off";
181
+ case "replaced":
182
+ return "voice moved to another window";
183
+ case "removed":
184
+ return "you were removed from voice";
185
+ case "room_closed":
186
+ return "the voice room closed";
187
+ case "network":
188
+ return "voice lost its connection";
189
+ case "window_closed":
190
+ return "voice off \xB7 the voice window closed";
191
+ case "error":
192
+ return "voice stopped because of an error";
193
+ default:
194
+ return "voice off";
195
+ }
196
+ }
197
+
198
+ export {
199
+ isVoiceUrl,
200
+ parseVoiceIdentity,
201
+ sanitizeVoiceName,
202
+ decodeVoiceMetadata,
203
+ initialVoiceState,
204
+ normalizeVoiceState,
205
+ VoiceStore,
206
+ mapLiveKitDisconnect,
207
+ RTC_NODE,
208
+ RTC_NODE_VERSION,
209
+ rtcNodeMissingHint,
210
+ describeEnd
211
+ };