accelr8 3.1.1 → 3.3.0
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/chunks/app-VQ33JVDI.js +793 -0
- package/dist/chunks/chunk-HY6ULYGH.js +559 -0
- package/dist/chunks/{chunk-AY3UXM6A.js → chunk-TW4QNNNT.js} +1 -1
- package/dist/chunks/client-6SYGGJ7L.js +4035 -0
- package/dist/chunks/{server-EFKIKFUG.js → server-VWQ2NYRN.js} +62 -14
- package/dist/cli.js +17 -4
- package/package.json +5 -3
- package/dist/chunks/client-J62VQCBE.js +0 -4331
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
LIMITS,
|
|
7
7
|
PORT,
|
|
8
8
|
PROTO
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-TW4QNNNT.js";
|
|
10
10
|
|
|
11
11
|
// src/server.ts
|
|
12
12
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -109,6 +109,7 @@ function sanitizeText(v, maxCp) {
|
|
|
109
109
|
return out;
|
|
110
110
|
}
|
|
111
111
|
var RoomCore = class {
|
|
112
|
+
// per-sid last voiceStats (2s)
|
|
112
113
|
constructor(host) {
|
|
113
114
|
this.host = host;
|
|
114
115
|
}
|
|
@@ -137,8 +138,13 @@ var RoomCore = class {
|
|
|
137
138
|
// userId -> trigger times
|
|
138
139
|
// voice channel (realtime, loss-tolerant): sids currently in voice, and a
|
|
139
140
|
// per-sid rate window (voiceFrame is exempt from the general frame flood cap)
|
|
140
|
-
|
|
141
|
+
// voice membership is AUTHORITATIVE on the session attachment (SessionState.voice),
|
|
142
|
+
// not here — an in-memory Set was wiped by every DO hibernation/eviction/redeploy.
|
|
141
143
|
voiceWindow = /* @__PURE__ */ new Map();
|
|
144
|
+
// per-sid frame rate window
|
|
145
|
+
voiceNackAt = /* @__PURE__ */ new Map();
|
|
146
|
+
// per-sid last voiceDrop NACK (1/s)
|
|
147
|
+
voiceStatsAt = /* @__PURE__ */ new Map();
|
|
142
148
|
// ---------- lifecycle ----------
|
|
143
149
|
async onMessage(conn, raw) {
|
|
144
150
|
let msg;
|
|
@@ -330,28 +336,52 @@ var RoomCore = class {
|
|
|
330
336
|
break;
|
|
331
337
|
}
|
|
332
338
|
case "voiceJoin": {
|
|
333
|
-
if (!s.projectId
|
|
334
|
-
|
|
339
|
+
if (!s.projectId) break;
|
|
340
|
+
const already = s.voice === true;
|
|
341
|
+
if (!already) {
|
|
342
|
+
s.voice = true;
|
|
343
|
+
this.host.setSession(conn, s);
|
|
344
|
+
await this.systemLine(s.projectId, `${s.name} joined voice`);
|
|
345
|
+
}
|
|
346
|
+
this.host.send(conn, { t: "voiceJoined", peers: this.voicePeersOf(s.projectId) });
|
|
335
347
|
this.broadcastVoice(s.projectId);
|
|
336
|
-
await this.systemLine(s.projectId, `${s.name} joined voice`);
|
|
337
348
|
break;
|
|
338
349
|
}
|
|
339
350
|
case "voiceLeave": {
|
|
340
|
-
if (!s.projectId ||
|
|
341
|
-
|
|
351
|
+
if (!s.projectId || s.voice !== true) break;
|
|
352
|
+
s.voice = false;
|
|
353
|
+
this.host.setSession(conn, s);
|
|
342
354
|
this.voiceWindow.delete(s.sid);
|
|
355
|
+
this.voiceStatsAt.delete(s.sid);
|
|
343
356
|
this.broadcastVoice(s.projectId);
|
|
344
357
|
await this.systemLine(s.projectId, `${s.name} left voice`);
|
|
345
358
|
break;
|
|
346
359
|
}
|
|
347
360
|
case "voiceFrame": {
|
|
348
|
-
if (!s.projectId
|
|
361
|
+
if (!s.projectId) break;
|
|
362
|
+
const vnow = this.host.now();
|
|
363
|
+
if (s.voice !== true) {
|
|
364
|
+
this.voiceNack(conn, s.sid, vnow, "notInVoice");
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
349
367
|
const data = typeof msg.data === "string" ? msg.data : "";
|
|
350
|
-
if (!data
|
|
351
|
-
if (
|
|
368
|
+
if (!data) break;
|
|
369
|
+
if (data.length > LIMITS.voiceFrameMaxB64) {
|
|
370
|
+
this.voiceNack(conn, s.sid, vnow, "tooBig");
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
if (!this.rateOk(this.voiceWindow, s.sid, LIMITS.voiceFramesPerSec, 1e3)) {
|
|
374
|
+
this.voiceNack(conn, s.sid, vnow, "rateCapped");
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
const lastStat = this.voiceStatsAt.get(s.sid) ?? 0;
|
|
378
|
+
if (vnow - lastStat >= 2e3) {
|
|
379
|
+
this.voiceStatsAt.set(s.sid, vnow);
|
|
380
|
+
this.host.send(conn, { t: "voiceStats", heardFromYou: (this.voiceWindow.get(s.sid) ?? []).length });
|
|
381
|
+
}
|
|
352
382
|
for (const c of this.roomConns(s.projectId)) {
|
|
353
383
|
const peer = this.host.getSession(c);
|
|
354
|
-
if (peer && peer.sid !== s.sid &&
|
|
384
|
+
if (peer && peer.sid !== s.sid && peer.voice === true) {
|
|
355
385
|
this.host.send(c, { t: "voiceFrame", sid: s.sid, data });
|
|
356
386
|
}
|
|
357
387
|
}
|
|
@@ -526,8 +556,10 @@ var RoomCore = class {
|
|
|
526
556
|
agents: this.agentPresence(project),
|
|
527
557
|
chatTail: await this.tail(project.id, latest),
|
|
528
558
|
chatLatestSeq: latest,
|
|
529
|
-
secrets: []
|
|
559
|
+
secrets: [],
|
|
530
560
|
// P4
|
|
561
|
+
voicePeers: this.voicePeersOf(project.id)
|
|
562
|
+
// a (re)joiner learns who's in voice
|
|
531
563
|
});
|
|
532
564
|
this.broadcastSessions(project.id);
|
|
533
565
|
for (const c of this.roomPeers(project.id, conn))
|
|
@@ -554,8 +586,11 @@ var RoomCore = class {
|
|
|
554
586
|
if (inv && inv.projectId === pid) void this.finalizeInvocation(inv, "partial");
|
|
555
587
|
this.queues.delete(agentId);
|
|
556
588
|
}
|
|
557
|
-
const wasInVoice =
|
|
589
|
+
const wasInVoice = s.voice === true;
|
|
590
|
+
s.voice = false;
|
|
558
591
|
this.voiceWindow.delete(s.sid);
|
|
592
|
+
this.voiceStatsAt.delete(s.sid);
|
|
593
|
+
this.voiceNackAt.delete(s.sid);
|
|
559
594
|
s.projectId = null;
|
|
560
595
|
this.host.setSession(conn, s);
|
|
561
596
|
for (const c of this.roomPeers(pid, conn)) {
|
|
@@ -798,11 +833,24 @@ var RoomCore = class {
|
|
|
798
833
|
const sessions = this.sessionsFor(projectId);
|
|
799
834
|
for (const c of this.roomConns(projectId)) this.host.send(c, { t: "sessions", sessions });
|
|
800
835
|
}
|
|
836
|
+
/** Current voice roster for a project, read off each session's attachment
|
|
837
|
+
* (authoritative, hibernation-proof — no in-memory Set to go stale). */
|
|
838
|
+
voicePeersOf(projectId) {
|
|
839
|
+
return this.roomConns(projectId).map((c) => this.host.getSession(c)).filter((s) => !!s && s.voice === true).map((s) => ({ sid: s.sid, userId: s.userId, name: s.name, color: s.color }));
|
|
840
|
+
}
|
|
801
841
|
/** who is in voice for this project (join/leave presence, not per-frame). */
|
|
802
842
|
broadcastVoice(projectId) {
|
|
803
|
-
const peers = this.
|
|
843
|
+
const peers = this.voicePeersOf(projectId);
|
|
804
844
|
for (const c of this.roomConns(projectId)) this.host.send(c, { t: "voice", peers });
|
|
805
845
|
}
|
|
846
|
+
/** NACK a dropped voice frame back to its sender, rate-limited to 1/s/sid so a
|
|
847
|
+
* wedged mic can't spam the sender. Turns silent drops into a fixable signal. */
|
|
848
|
+
voiceNack(conn, sid, now, reason) {
|
|
849
|
+
const last = this.voiceNackAt.get(sid) ?? 0;
|
|
850
|
+
if (now - last < 1e3) return;
|
|
851
|
+
this.voiceNackAt.set(sid, now);
|
|
852
|
+
this.host.send(conn, { t: "voiceDrop", reason });
|
|
853
|
+
}
|
|
806
854
|
onlineIds(projectId) {
|
|
807
855
|
return [...new Set(this.sessionsFor(projectId).map((s) => s.userId))];
|
|
808
856
|
}
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DEFAULT_SERVER,
|
|
4
4
|
PORT,
|
|
5
5
|
VERSION
|
|
6
|
-
} from "./chunks/chunk-
|
|
6
|
+
} from "./chunks/chunk-TW4QNNNT.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
var HELP = `
|
|
@@ -13,7 +13,7 @@ USAGE
|
|
|
13
13
|
accelr8 open the app
|
|
14
14
|
accelr8 join <CODE> open straight into a project
|
|
15
15
|
accelr8 serve run your own sync server
|
|
16
|
-
accelr8 --intro replay the
|
|
16
|
+
accelr8 --intro replay the opening cinematic
|
|
17
17
|
|
|
18
18
|
OPTIONS
|
|
19
19
|
--name <name> display name (remembered for next time)
|
|
@@ -52,7 +52,7 @@ async function main() {
|
|
|
52
52
|
console.error(`invalid port: ${argVal(args, "--port")}`);
|
|
53
53
|
process.exit(1);
|
|
54
54
|
}
|
|
55
|
-
const { runServer } = await import("./chunks/server-
|
|
55
|
+
const { runServer } = await import("./chunks/server-VWQ2NYRN.js");
|
|
56
56
|
runServer(port);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
@@ -61,6 +61,19 @@ async function main() {
|
|
|
61
61
|
await runAgentCli(args.slice(1));
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
+
if (args[0] === "radio") {
|
|
65
|
+
if (!process.stdout.isTTY) {
|
|
66
|
+
console.error("accelr8 radio needs an interactive terminal (TTY).");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const { runRadio } = await import("./chunks/app-VQ33JVDI.js");
|
|
70
|
+
runRadio({
|
|
71
|
+
name: argVal(args, "--name"),
|
|
72
|
+
server: argVal(args, "--server"),
|
|
73
|
+
clip: argVal(args, "--clip")
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
64
77
|
let joinCode;
|
|
65
78
|
if (args[0] === "join") {
|
|
66
79
|
joinCode = args[1];
|
|
@@ -76,7 +89,7 @@ async function main() {
|
|
|
76
89
|
const { cachedUpdate, refreshUpdateCache } = await import("./chunks/update-UEPNGB6Y.js");
|
|
77
90
|
refreshUpdateCache();
|
|
78
91
|
const update = cachedUpdate(VERSION);
|
|
79
|
-
const { runClient } = await import("./chunks/client-
|
|
92
|
+
const { runClient } = await import("./chunks/client-6SYGGJ7L.js");
|
|
80
93
|
runClient({
|
|
81
94
|
name: argVal(args, "--name"),
|
|
82
95
|
server: argVal(args, "--server"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accelr8",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"chattest": "tsx src/chattest.ts",
|
|
43
43
|
"inputtest": "tsx src/inputtest.ts",
|
|
44
44
|
"torturetest": "tsx src/torturetest.ts",
|
|
45
|
-
"test": "for t in core chat input ladder boot orb crypto accounts executor agentcontext map voice glyph materialize update install geo map contrast; do tsx src/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
|
|
45
|
+
"test": "for t in core chat input ladder boot orb crypto accounts executor agentcontext map voice voiceroom chain glyph materialize update install geo map contrast; do tsx src/${t}test.ts || exit 1; done && for t in station asciivideo audio funfacts launch overlay; do tsx src/radio/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
|
|
46
|
+
"teste2e": "tsx src/voicetransporttest.ts",
|
|
46
47
|
"laddertest": "tsx src/laddertest.ts",
|
|
47
48
|
"boottest": "tsx src/boottest.ts",
|
|
48
49
|
"reconnecttest": "tsx src/reconnecttest.ts",
|
|
@@ -54,7 +55,8 @@
|
|
|
54
55
|
"executortest": "tsx src/executortest.ts",
|
|
55
56
|
"agentcontexttest": "tsx src/agentcontexttest.ts",
|
|
56
57
|
"chaintest": "tsx src/chaintest.ts",
|
|
57
|
-
"voicetransporttest": "tsx src/voicetransporttest.ts"
|
|
58
|
+
"voicetransporttest": "tsx src/voicetransporttest.ts",
|
|
59
|
+
"voiceroomtest": "tsx src/voiceroomtest.ts"
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
60
62
|
"terminal-kit": "3.1.2",
|