accelr8 3.8.2 → 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.
- package/README.md +298 -64
- package/dist/chunks/accounts-CCKXHSQQ.js +54 -0
- package/dist/chunks/{agentcli-ZI2O7ID7.js → agentcli-HL6V26E7.js} +6 -3
- package/dist/chunks/{app-SYLCS3QX.js → app-JMUXGMWK.js} +158 -21
- package/dist/chunks/authclient-AUBBFWCI.js +34 -0
- package/dist/chunks/authpage-X2AKCMVH.js +21 -0
- package/dist/chunks/chunk-3OTVRBTJ.js +265 -0
- package/dist/chunks/chunk-5QXGOLQ3.js +101 -0
- package/dist/chunks/chunk-62O2F73J.js +13 -0
- package/dist/chunks/chunk-CAQJPW54.js +211 -0
- package/dist/chunks/chunk-CUUNLVID.js +124 -0
- package/dist/chunks/chunk-EQPM4FIJ.js +419 -0
- package/dist/chunks/chunk-FVC2UPMV.js +306 -0
- package/dist/chunks/{chunk-3JNUWI22.js → chunk-NRXIFJGR.js} +3 -155
- package/dist/chunks/chunk-RZ4OBBLX.js +129 -0
- package/dist/chunks/chunk-UMPU6SAD.js +860 -0
- package/dist/chunks/{client-BKQLCKVT.js → client-UCMRJVMA.js} +1621 -437
- package/dist/chunks/livekit-client-bundle-HLYFRJB2.js +15 -0
- package/dist/chunks/livekit-terminal-22Q2NFB5.js +535 -0
- package/dist/chunks/livekit-window-XVOCBQOM.js +626 -0
- package/dist/chunks/server-A4T45QZ4.js +2707 -0
- package/dist/chunks/{update-UEPNGB6Y.js → update-RGDNBI4T.js} +25 -7
- package/dist/cli.js +132 -10
- package/package.json +10 -7
- package/dist/chunks/chunk-7JSMON7M.js +0 -64
- package/dist/chunks/chunk-HY6ULYGH.js +0 -559
- package/dist/chunks/server-IRLRECZ7.js +0 -1024
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
describeEnd,
|
|
4
|
+
rtcNodeMissingHint
|
|
5
|
+
} from "./chunk-CAQJPW54.js";
|
|
6
|
+
import {
|
|
7
7
|
execute,
|
|
8
|
-
loadAccounts,
|
|
9
|
-
loadTokens,
|
|
10
|
-
removeAccount,
|
|
11
|
-
saveAccounts,
|
|
12
|
-
saveToken,
|
|
13
8
|
verifyAccount
|
|
14
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-NRXIFJGR.js";
|
|
15
10
|
import {
|
|
16
|
-
VoiceCapture,
|
|
17
|
-
VoicePlayback,
|
|
18
|
-
detectVoiceTools,
|
|
19
|
-
listMicDevices,
|
|
20
11
|
localTz,
|
|
21
12
|
tzCity,
|
|
22
|
-
tzToCoords
|
|
13
|
+
tzToCoords
|
|
14
|
+
} from "./chunk-CUUNLVID.js";
|
|
15
|
+
import {
|
|
16
|
+
RelayVoice,
|
|
17
|
+
detectVoiceTools,
|
|
18
|
+
killAllHelpers,
|
|
19
|
+
listMicDevices,
|
|
23
20
|
vlog,
|
|
24
|
-
vlogSession,
|
|
25
21
|
voiceLogPath
|
|
26
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-UMPU6SAD.js";
|
|
23
|
+
import {
|
|
24
|
+
ensureSignedIn,
|
|
25
|
+
getAccessToken
|
|
26
|
+
} from "./chunk-3OTVRBTJ.js";
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_MODEL,
|
|
29
|
+
addAccount,
|
|
30
|
+
defaultHandle,
|
|
31
|
+
deleteCredentials,
|
|
32
|
+
detectClaudeCode,
|
|
33
|
+
loadAccounts,
|
|
34
|
+
loadCredentials,
|
|
35
|
+
loadTokens,
|
|
36
|
+
removeAccount,
|
|
37
|
+
saveToken
|
|
38
|
+
} from "./chunk-FVC2UPMV.js";
|
|
27
39
|
import {
|
|
28
40
|
CANVAS_H,
|
|
29
41
|
CANVAS_W,
|
|
@@ -32,14 +44,16 @@ import {
|
|
|
32
44
|
VERSION,
|
|
33
45
|
decode,
|
|
34
46
|
encode
|
|
35
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-5QXGOLQ3.js";
|
|
48
|
+
import {
|
|
49
|
+
accelr8Dir
|
|
50
|
+
} from "./chunk-62O2F73J.js";
|
|
36
51
|
|
|
37
52
|
// src/client.ts
|
|
38
53
|
import tk from "terminal-kit";
|
|
39
54
|
import { WebSocket } from "ws";
|
|
40
55
|
import { spawn as spawn2 } from "node:child_process";
|
|
41
|
-
import { readFileSync, writeFileSync, mkdirSync
|
|
42
|
-
import { homedir } from "node:os";
|
|
56
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
43
57
|
import { join } from "node:path";
|
|
44
58
|
|
|
45
59
|
// src/logo.ts
|
|
@@ -56,10 +70,346 @@ var LOGO = [
|
|
|
56
70
|
];
|
|
57
71
|
var LOGO_W = Math.max(...LOGO.map((l) => l.length));
|
|
58
72
|
|
|
73
|
+
// src/glyphs.ts
|
|
74
|
+
var ALPHABET = [
|
|
75
|
+
// presence & species
|
|
76
|
+
{ ch: "\u25CF", meaning: "alive / on / selected (color disambiguates: good=presence, accent=selected)", surfaces: "everywhere presence or selection is shown" },
|
|
77
|
+
{ ch: "\u25CB", meaning: "offline / unselected", surfaces: "roster, radios, join slots" },
|
|
78
|
+
{ ch: "\u2726", meaning: "agent \u2014 bright phase / the heart", surfaces: "agent name sigil, orb die window" },
|
|
79
|
+
{ ch: "\u2727", meaning: "agent \u2014 soft phase", surfaces: "agent surfaces only (the \u2726 gait)" },
|
|
80
|
+
// voice & input
|
|
81
|
+
{ ch: "\u25B8", meaning: "a voice speaks", surfaces: "chat author arrow, HOME row cursor" },
|
|
82
|
+
{ ch: "\u25B9", meaning: "sent \u2014 awaiting ack", surfaces: "optimistic chat send" },
|
|
83
|
+
{ ch: "\u270E", meaning: "draft in progress", surfaces: "presence lane (humans AND agents)" },
|
|
84
|
+
{ ch: "\u276F", meaning: "the machine awaits (input only)", surfaces: "input prompt, typed-name ritual" },
|
|
85
|
+
// structure
|
|
86
|
+
{ ch: "\xB7", meaning: "the one connective + system whispers", surfaces: "everywhere" },
|
|
87
|
+
{ ch: "\u2500", meaning: "light rule", surfaces: "rules, dividers" },
|
|
88
|
+
{ ch: "\u2501", meaning: "heavy rule / active nav segment", surfaces: "underline nav, active tab" },
|
|
89
|
+
{ ch: "\u2502", meaning: "light vertical", surfaces: "panels, ledger axis" },
|
|
90
|
+
{ ch: "\u2503", meaning: "heavy vertical", surfaces: "canvas edge weight" },
|
|
91
|
+
{ ch: "\u256D", meaning: "voice ring corner", surfaces: "trust screen (voice ring)" },
|
|
92
|
+
{ ch: "\u256E", meaning: "voice ring corner", surfaces: "trust screen (voice ring)" },
|
|
93
|
+
{ ch: "\u2570", meaning: "voice ring corner", surfaces: "trust screen (voice ring)" },
|
|
94
|
+
{ ch: "\u256F", meaning: "voice ring corner", surfaces: "trust screen (voice ring)" },
|
|
95
|
+
{ ch: "\u2554", meaning: "hands ring corner", surfaces: "trust screen (hands ring)" },
|
|
96
|
+
{ ch: "\u2557", meaning: "hands ring corner", surfaces: "trust screen (hands ring)" },
|
|
97
|
+
{ ch: "\u255A", meaning: "hands ring corner", surfaces: "trust screen (hands ring)" },
|
|
98
|
+
{ ch: "\u255D", meaning: "hands ring corner", surfaces: "trust screen (hands ring)" },
|
|
99
|
+
{ ch: "\u2550", meaning: "hands ring / double rule", surfaces: "trust screen, bar sweep" },
|
|
100
|
+
{ ch: "\u2551", meaning: "hands ring vertical", surfaces: "trust screen" },
|
|
101
|
+
{ ch: "\u250C", meaning: "panel corner", surfaces: "panels, popups" },
|
|
102
|
+
{ ch: "\u2510", meaning: "panel corner", surfaces: "panels, popups" },
|
|
103
|
+
{ ch: "\u2514", meaning: "panel corner", surfaces: "panels, popups" },
|
|
104
|
+
{ ch: "\u2518", meaning: "panel corner", surfaces: "panels, popups" },
|
|
105
|
+
{ ch: "\u258E", meaning: "code-fence border", surfaces: "fenced code in chat" },
|
|
106
|
+
{ ch: "\u2039", meaning: "off-view / strain (left)", surfaces: "canvas edge chips" },
|
|
107
|
+
{ ch: "\u203A", meaning: "off-view / strain (right)", surfaces: "canvas edge chips" },
|
|
108
|
+
// matter — the noise alphabet (condensation) + density ramp
|
|
109
|
+
{ ch: " ", meaning: "void / noise floor", surfaces: "everywhere" },
|
|
110
|
+
{ ch: ".", meaning: "noise", surfaces: "condensation, boot" },
|
|
111
|
+
{ ch: ":", meaning: "noise", surfaces: "condensation, boot" },
|
|
112
|
+
{ ch: "-", meaning: "noise", surfaces: "condensation, boot" },
|
|
113
|
+
{ ch: "=", meaning: "noise", surfaces: "condensation, boot" },
|
|
114
|
+
{ ch: "+", meaning: "noise (also: added, ascii)", surfaces: "condensation, boot, receipts" },
|
|
115
|
+
{ ch: "\u2591", meaning: "density 1 / shadow", surfaces: "ramps, overlay shadow" },
|
|
116
|
+
{ ch: "\u2592", meaning: "density 2", surfaces: "ramps, collapse" },
|
|
117
|
+
{ ch: "\u2593", meaning: "density 3", surfaces: "ramps, collapse" },
|
|
118
|
+
{ ch: "\u2588", meaning: "density 4 / block mass", surfaces: "ramps, wordmark, flash" },
|
|
119
|
+
{ ch: "\u2580", meaning: "upper half block", surfaces: "wordmark" },
|
|
120
|
+
{ ch: "\u2584", meaning: "lower half block", surfaces: "wordmark" },
|
|
121
|
+
{ ch: "\u258A", meaning: "stream caret \u2014 live", surfaces: "agent streaming line" },
|
|
122
|
+
{ ch: "\u2581", meaning: "stream caret \u2014 dying", surfaces: "agent streaming line" },
|
|
123
|
+
{ ch: "\u25B0", meaning: "fuse \u2014 lit", surfaces: "reveal/undo/clear countdowns" },
|
|
124
|
+
{ ch: "\u25B1", meaning: "fuse \u2014 spent", surfaces: "reveal/undo/clear countdowns" },
|
|
125
|
+
{ ch: "\u258D", meaning: "block cursor", surfaces: "boot cursor, editor" },
|
|
126
|
+
{ ch: "\u258F", meaning: "thin caret", surfaces: "modal field" },
|
|
127
|
+
// receipts
|
|
128
|
+
{ ch: "\u2713", meaning: "done", surfaces: "copy receipts, allow" },
|
|
129
|
+
{ ch: "\u2715", meaning: "refused / failed", surfaces: "verify fail, deny" },
|
|
130
|
+
{ ch: "\u21BB", meaning: "retry / reroll", surfaces: "unacked send, name reroll" },
|
|
131
|
+
{ ch: "@", meaning: "mention", surfaces: "chat, summon" },
|
|
132
|
+
// reserved
|
|
133
|
+
{ ch: "\u25C9", meaning: "the app as one cell", surfaces: "micro face (w<3), the heart landing cameo" }
|
|
134
|
+
];
|
|
135
|
+
var WHITELIST = new Set(ALPHABET.map((g) => g.ch));
|
|
136
|
+
function inRanges(cp, ranges) {
|
|
137
|
+
let lo = 0, hi = ranges.length - 1;
|
|
138
|
+
while (lo <= hi) {
|
|
139
|
+
const mid = lo + hi >> 1;
|
|
140
|
+
const r = ranges[mid];
|
|
141
|
+
if (cp < r[0]) hi = mid - 1;
|
|
142
|
+
else if (cp > r[1]) lo = mid + 1;
|
|
143
|
+
else return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
var MARK = /[\p{Mn}\p{Me}]/u;
|
|
148
|
+
var ZERO_WIDTH = [[4448, 4607], [55216, 55295]];
|
|
149
|
+
var FORMAT = new RegExp("\\p{Cf}", "u");
|
|
150
|
+
var WIDE = [
|
|
151
|
+
[4352, 4447],
|
|
152
|
+
// Hangul Jamo
|
|
153
|
+
[8986, 8987],
|
|
154
|
+
// watch, hourglass (emoji presentation, East Asian Wide)
|
|
155
|
+
[9001, 9002],
|
|
156
|
+
[9193, 9196],
|
|
157
|
+
// fast-forward … double-down arrows
|
|
158
|
+
[9200, 9200],
|
|
159
|
+
// alarm clock
|
|
160
|
+
[9203, 9203],
|
|
161
|
+
// hourglass flowing
|
|
162
|
+
[9725, 9726],
|
|
163
|
+
// medium-small squares
|
|
164
|
+
[9748, 9749],
|
|
165
|
+
// umbrella with rain, hot beverage
|
|
166
|
+
[9800, 9811],
|
|
167
|
+
// zodiac
|
|
168
|
+
[9855, 9855],
|
|
169
|
+
// wheelchair
|
|
170
|
+
[9875, 9875],
|
|
171
|
+
// anchor
|
|
172
|
+
[9889, 9889],
|
|
173
|
+
// high voltage
|
|
174
|
+
[9898, 9899],
|
|
175
|
+
// medium circles
|
|
176
|
+
[9917, 9918],
|
|
177
|
+
// soccer ball, baseball
|
|
178
|
+
[9924, 9925],
|
|
179
|
+
// snowman, sun behind cloud
|
|
180
|
+
[9934, 9934],
|
|
181
|
+
// ophiuchus
|
|
182
|
+
[9940, 9940],
|
|
183
|
+
// no entry
|
|
184
|
+
[9962, 9962],
|
|
185
|
+
// church
|
|
186
|
+
[9970, 9971],
|
|
187
|
+
// fountain, flag in hole
|
|
188
|
+
[9973, 9973],
|
|
189
|
+
// sailboat
|
|
190
|
+
[9978, 9978],
|
|
191
|
+
// tent
|
|
192
|
+
[9981, 9981],
|
|
193
|
+
// fuel pump
|
|
194
|
+
[9989, 9989],
|
|
195
|
+
// check mark button
|
|
196
|
+
[9994, 9995],
|
|
197
|
+
// raised fist, raised hand
|
|
198
|
+
[10024, 10024],
|
|
199
|
+
// sparkles
|
|
200
|
+
[10060, 10060],
|
|
201
|
+
// cross mark
|
|
202
|
+
[10062, 10062],
|
|
203
|
+
// cross mark button
|
|
204
|
+
[10067, 10069],
|
|
205
|
+
// question / exclamation ornaments
|
|
206
|
+
[10071, 10071],
|
|
207
|
+
// exclamation mark
|
|
208
|
+
[10133, 10135],
|
|
209
|
+
// heavy plus / minus / divide
|
|
210
|
+
[10160, 10160],
|
|
211
|
+
// curly loop
|
|
212
|
+
[10175, 10175],
|
|
213
|
+
// double curly loop
|
|
214
|
+
[11035, 11036],
|
|
215
|
+
// large squares
|
|
216
|
+
[11088, 11088],
|
|
217
|
+
// star
|
|
218
|
+
[11093, 11093],
|
|
219
|
+
// heavy large circle
|
|
220
|
+
[11904, 12350],
|
|
221
|
+
// CJK radicals … Kangxi
|
|
222
|
+
[12352, 12871],
|
|
223
|
+
// Hiragana, Katakana, CJK symbols …
|
|
224
|
+
[12880, 13311],
|
|
225
|
+
// … (3248-324F, numbers on black squares, are Ambiguous: 1 cell)
|
|
226
|
+
[13312, 19903],
|
|
227
|
+
// CJK ext A
|
|
228
|
+
[19968, 40959],
|
|
229
|
+
// CJK unified
|
|
230
|
+
[40960, 42191],
|
|
231
|
+
// Yi
|
|
232
|
+
[43360, 43388],
|
|
233
|
+
// Hangul Jamo extended-A
|
|
234
|
+
[44032, 55203],
|
|
235
|
+
// Hangul syllables
|
|
236
|
+
[63744, 64255],
|
|
237
|
+
// CJK compatibility ideographs
|
|
238
|
+
[65040, 65049],
|
|
239
|
+
// vertical forms
|
|
240
|
+
[65072, 65135],
|
|
241
|
+
// CJK compatibility forms
|
|
242
|
+
[65280, 65376],
|
|
243
|
+
// FULLWIDTH forms (banned in chrome)
|
|
244
|
+
[65504, 65510],
|
|
245
|
+
// fullwidth signs
|
|
246
|
+
[94176, 94180],
|
|
247
|
+
[94192, 94193],
|
|
248
|
+
// ideographic symbols
|
|
249
|
+
[94208, 100343],
|
|
250
|
+
[100352, 101589],
|
|
251
|
+
[101632, 101640],
|
|
252
|
+
// Tangut, Khitan
|
|
253
|
+
[110576, 110579],
|
|
254
|
+
[110581, 110587],
|
|
255
|
+
[110589, 110590],
|
|
256
|
+
// kana extended-B
|
|
257
|
+
[110592, 111359],
|
|
258
|
+
// kana supplement / extended, small kana, Nushu
|
|
259
|
+
// U+1F000-1FAFF: only what East Asian Width calls W (every Emoji_Presentation
|
|
260
|
+
// emoji, plus the enclosed ideographs). The text-style pictographs between
|
|
261
|
+
// them (🛠 🗓 🖥 🗑 🏷 👁 🌡 🏳 🕊 🎙, most mahjong tiles and playing cards, a
|
|
262
|
+
// lone regional indicator, 🅰) are N: one cell. Unassigned code points in the
|
|
263
|
+
// emoji blocks count as W, because that is where new emoji are added.
|
|
264
|
+
// Generated from Unicode 15.0 EastAsianWidth plus the Unicode 16 emoji.
|
|
265
|
+
[126980, 126980],
|
|
266
|
+
[127183, 127183],
|
|
267
|
+
[127374, 127374],
|
|
268
|
+
[127377, 127386],
|
|
269
|
+
[127488, 127490],
|
|
270
|
+
[127504, 127547],
|
|
271
|
+
[127552, 127560],
|
|
272
|
+
[127568, 127569],
|
|
273
|
+
[127584, 127589],
|
|
274
|
+
[127744, 127776],
|
|
275
|
+
[127789, 127797],
|
|
276
|
+
[127799, 127868],
|
|
277
|
+
[127870, 127891],
|
|
278
|
+
[127904, 127946],
|
|
279
|
+
[127951, 127955],
|
|
280
|
+
[127968, 127984],
|
|
281
|
+
[127988, 127988],
|
|
282
|
+
[127992, 128062],
|
|
283
|
+
[128064, 128064],
|
|
284
|
+
[128066, 128252],
|
|
285
|
+
[128255, 128317],
|
|
286
|
+
[128331, 128334],
|
|
287
|
+
[128336, 128359],
|
|
288
|
+
[128378, 128378],
|
|
289
|
+
[128405, 128406],
|
|
290
|
+
[128420, 128420],
|
|
291
|
+
[128507, 128591],
|
|
292
|
+
[128640, 128709],
|
|
293
|
+
[128716, 128716],
|
|
294
|
+
[128720, 128722],
|
|
295
|
+
[128725, 128735],
|
|
296
|
+
[128747, 128751],
|
|
297
|
+
[128756, 128767],
|
|
298
|
+
[128992, 129003],
|
|
299
|
+
[129008, 129008],
|
|
300
|
+
[129292, 129338],
|
|
301
|
+
[129340, 129349],
|
|
302
|
+
[129351, 129535],
|
|
303
|
+
[129648, 129791],
|
|
304
|
+
[131072, 262141]
|
|
305
|
+
// CJK ext B+ (astral)
|
|
306
|
+
];
|
|
307
|
+
var isRegionalIndicator = (cp) => cp >= 127462 && cp <= 127487;
|
|
308
|
+
var bmpWidth = new Uint8Array(65536);
|
|
309
|
+
function measure(cp) {
|
|
310
|
+
const ch = String.fromCodePoint(cp);
|
|
311
|
+
if (inRanges(cp, ZERO_WIDTH) || MARK.test(ch) || FORMAT.test(ch)) return 0;
|
|
312
|
+
return inRanges(cp, WIDE) ? 2 : 1;
|
|
313
|
+
}
|
|
314
|
+
function charWidth(cp) {
|
|
315
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
316
|
+
if (cp < 768) return 1;
|
|
317
|
+
if (cp < 65536) {
|
|
318
|
+
const v = bmpWidth[cp];
|
|
319
|
+
if (v) return v - 1;
|
|
320
|
+
const w = measure(cp);
|
|
321
|
+
bmpWidth[cp] = w + 1;
|
|
322
|
+
return w;
|
|
323
|
+
}
|
|
324
|
+
return measure(cp);
|
|
325
|
+
}
|
|
326
|
+
function stringWidth(s) {
|
|
327
|
+
let w = 0, i = 0;
|
|
328
|
+
for (; i < s.length; i++) {
|
|
329
|
+
const c = s.charCodeAt(i);
|
|
330
|
+
if (c >= 768) break;
|
|
331
|
+
if (c >= 32 && (c < 127 || c >= 160)) w++;
|
|
332
|
+
}
|
|
333
|
+
if (i === s.length) return w;
|
|
334
|
+
w = 0;
|
|
335
|
+
for (const g of clusters(s)) w += clusterWidth(g);
|
|
336
|
+
return w;
|
|
337
|
+
}
|
|
338
|
+
var PICTO = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}]/u;
|
|
339
|
+
function clusterWidth(g) {
|
|
340
|
+
let sum = 0, max = 0, ri = 0, emoji = false;
|
|
341
|
+
for (const ch of g) {
|
|
342
|
+
const cp = ch.codePointAt(0);
|
|
343
|
+
const w = charWidth(cp);
|
|
344
|
+
sum += w;
|
|
345
|
+
if (w > max) max = w;
|
|
346
|
+
if (isRegionalIndicator(cp)) {
|
|
347
|
+
ri++;
|
|
348
|
+
emoji = true;
|
|
349
|
+
} else if (!emoji && cp >= 169 && PICTO.test(ch)) emoji = true;
|
|
350
|
+
}
|
|
351
|
+
if (ri >= 2) return 2;
|
|
352
|
+
return emoji ? max : sum;
|
|
353
|
+
}
|
|
354
|
+
function cellPieces(g) {
|
|
355
|
+
const w = clusterWidth(g);
|
|
356
|
+
if (w === 0) return [];
|
|
357
|
+
for (const ch of g) {
|
|
358
|
+
const cp = ch.codePointAt(0);
|
|
359
|
+
if (isRegionalIndicator(cp) || cp >= 169 && PICTO.test(ch)) return [{ text: g, w, emoji: true }];
|
|
360
|
+
}
|
|
361
|
+
const out = [];
|
|
362
|
+
for (const ch of g.normalize("NFC")) {
|
|
363
|
+
const cp = ch.codePointAt(0);
|
|
364
|
+
const cw = charWidth(cp);
|
|
365
|
+
if (cw > 0) out.push({ text: ch, w: cw });
|
|
366
|
+
else if (out.length && !FORMAT.test(ch) && !(cp >= 65024 && cp <= 65039) && !(cp >= 917760 && cp <= 917999))
|
|
367
|
+
out[out.length - 1].text += ch;
|
|
368
|
+
}
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
function clusters(s) {
|
|
372
|
+
const seg = segmenter();
|
|
373
|
+
if (seg) {
|
|
374
|
+
const out = [];
|
|
375
|
+
for (const p of seg.segment(s)) out.push(p.segment);
|
|
376
|
+
return out;
|
|
377
|
+
}
|
|
378
|
+
return [...s];
|
|
379
|
+
}
|
|
380
|
+
var seg0;
|
|
381
|
+
function segmenter() {
|
|
382
|
+
if (seg0 === void 0) {
|
|
383
|
+
const Seg = Intl.Segmenter;
|
|
384
|
+
seg0 = Seg ? new Seg(void 0, { granularity: "grapheme" }) : null;
|
|
385
|
+
}
|
|
386
|
+
return seg0;
|
|
387
|
+
}
|
|
388
|
+
function truncateWidth(s, cells, tail = "") {
|
|
389
|
+
if (cells <= 0) return "";
|
|
390
|
+
if (stringWidth(s) <= cells) return s;
|
|
391
|
+
const budget = cells - stringWidth(tail);
|
|
392
|
+
if (budget < 0) return "";
|
|
393
|
+
let out = "";
|
|
394
|
+
let w = 0;
|
|
395
|
+
for (const g of clusters(s)) {
|
|
396
|
+
const gw = stringWidth(g);
|
|
397
|
+
if (w + gw > budget) break;
|
|
398
|
+
out += g;
|
|
399
|
+
w += gw;
|
|
400
|
+
}
|
|
401
|
+
return out + tail;
|
|
402
|
+
}
|
|
403
|
+
function padWidth(s, cells) {
|
|
404
|
+
const t = truncateWidth(s, cells);
|
|
405
|
+
return t + " ".repeat(Math.max(0, cells - stringWidth(t)));
|
|
406
|
+
}
|
|
407
|
+
|
|
59
408
|
// src/chat.ts
|
|
60
409
|
var CHAT_MAX = 1e3;
|
|
61
410
|
var SPINNER_COLOR = -1;
|
|
62
411
|
var SYSTEM_COLOR = -2;
|
|
412
|
+
var NOTICE_COLOR = -4;
|
|
63
413
|
var DIM = 245;
|
|
64
414
|
var FAINT = 245;
|
|
65
415
|
var SELF_MENTION_BG = -3;
|
|
@@ -89,35 +439,57 @@ function gutterFor(prev, e) {
|
|
|
89
439
|
const d = new Date(e.ts);
|
|
90
440
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
91
441
|
}
|
|
92
|
-
|
|
442
|
+
var CONTROL = /[\x00-\x09\x0b-\x1f\x7f-\x9f]/;
|
|
443
|
+
var CONTROLS = /[\x00-\x09\x0b-\x1f\x7f-\x9f]/g;
|
|
444
|
+
function displayText(s) {
|
|
445
|
+
return CONTROL.test(s) ? s.replace(/\r\n/g, "\n").replace(CONTROLS, " ") : s;
|
|
446
|
+
}
|
|
447
|
+
function unitsOf(text) {
|
|
93
448
|
const out = [];
|
|
449
|
+
let cp = 0;
|
|
450
|
+
for (const g of clusters(text)) {
|
|
451
|
+
const n = [...g].length;
|
|
452
|
+
out.push({ s: cp, e: cp + n, w: stringWidth(g), space: g === " ", nl: g.includes("\n") });
|
|
453
|
+
cp += n;
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
function wrapRanges(u, firstW, contW) {
|
|
458
|
+
const out = [];
|
|
459
|
+
const cpAt = (i) => i < u.length ? u[i].s : u.length ? u[u.length - 1].e : 0;
|
|
460
|
+
const push = (i, j) => out.push({ s: cpAt(i), e: j > i ? u[j - 1].e : cpAt(i) });
|
|
94
461
|
let first = true;
|
|
95
462
|
let segStart = 0;
|
|
96
|
-
for (let j = 0; j <=
|
|
97
|
-
if (j <
|
|
463
|
+
for (let j = 0; j <= u.length; j++) {
|
|
464
|
+
if (j < u.length && !u[j].nl) continue;
|
|
98
465
|
let i = segStart;
|
|
99
466
|
if (i === j) {
|
|
100
|
-
|
|
467
|
+
push(i, i);
|
|
101
468
|
first = false;
|
|
102
469
|
}
|
|
103
470
|
while (i < j) {
|
|
104
471
|
const avail = Math.max(1, first ? firstW : contW);
|
|
105
472
|
first = false;
|
|
106
|
-
|
|
107
|
-
|
|
473
|
+
let m = i, used = 0;
|
|
474
|
+
while (m < j && (m === i || used + u[m].w <= avail)) {
|
|
475
|
+
used += u[m].w;
|
|
476
|
+
m++;
|
|
477
|
+
}
|
|
478
|
+
if (m === j) {
|
|
479
|
+
push(i, j);
|
|
108
480
|
break;
|
|
109
481
|
}
|
|
110
482
|
let brk = -1;
|
|
111
|
-
for (let k =
|
|
483
|
+
for (let k = m; k > i; k--) if (u[k].space) {
|
|
112
484
|
brk = k;
|
|
113
485
|
break;
|
|
114
486
|
}
|
|
115
487
|
if (brk >= 0) {
|
|
116
|
-
|
|
488
|
+
push(i, brk);
|
|
117
489
|
i = brk + 1;
|
|
118
490
|
} else {
|
|
119
|
-
|
|
120
|
-
i
|
|
491
|
+
push(i, m);
|
|
492
|
+
i = m;
|
|
121
493
|
}
|
|
122
494
|
}
|
|
123
495
|
segStart = j + 1;
|
|
@@ -125,40 +497,53 @@ function wrapRanges(cps, firstW, contW) {
|
|
|
125
497
|
return out;
|
|
126
498
|
}
|
|
127
499
|
function clampLine(line, width) {
|
|
128
|
-
|
|
129
|
-
|
|
500
|
+
if (stringWidth(line.text) <= width) return line;
|
|
501
|
+
let kept = "", used = 0, cps = 0;
|
|
502
|
+
for (const g of clusters(line.text)) {
|
|
503
|
+
const w = stringWidth(g);
|
|
504
|
+
if (used + w > width) break;
|
|
505
|
+
kept += g;
|
|
506
|
+
used += w;
|
|
507
|
+
cps += [...g].length;
|
|
508
|
+
}
|
|
130
509
|
return {
|
|
131
510
|
...line,
|
|
132
|
-
text:
|
|
133
|
-
spans: line.spans.filter((s) => s.start <
|
|
511
|
+
text: kept,
|
|
512
|
+
spans: line.spans.filter((s) => s.start < cps).map((s) => ({ ...s, end: Math.min(s.end, cps) }))
|
|
134
513
|
};
|
|
135
514
|
}
|
|
136
|
-
function systemLines(text, width) {
|
|
137
|
-
const
|
|
138
|
-
|
|
515
|
+
function systemLines(text, width, color = SYSTEM_COLOR) {
|
|
516
|
+
const body = `\xB7 ${displayText(text)} \xB7`;
|
|
517
|
+
const cps = [...body];
|
|
518
|
+
return wrapRanges(unitsOf(body), width, width).map((r) => clampLine({
|
|
139
519
|
text: cps.slice(r.s, r.e).join(""),
|
|
140
|
-
spans: r.e > r.s ? [{ start: 0, end: r.e - r.s, color
|
|
520
|
+
spans: r.e > r.s ? [{ start: 0, end: r.e - r.s, color }] : [],
|
|
141
521
|
centered: true
|
|
142
522
|
}, width));
|
|
143
523
|
}
|
|
144
|
-
function entryLines(e, mentions, width) {
|
|
524
|
+
function entryLines(e, mentions, width, noticer) {
|
|
525
|
+
const notice = noticer ? noticer(e) : null;
|
|
526
|
+
if (notice) return systemLines(notice, width, NOTICE_COLOR);
|
|
145
527
|
if (e.kind === "system") return systemLines(e.text, width);
|
|
146
528
|
const name = (e.kind === "agent" ? "\u2726" : "") + e.authorName;
|
|
147
529
|
const nameLen = [...name].length;
|
|
148
530
|
const pre = name + (e.streaming ? " \u283F" : "") + " \u25B8 ";
|
|
149
531
|
const preLen = [...pre].length;
|
|
532
|
+
const preW = stringWidth(pre);
|
|
150
533
|
const nameSpan = { start: 0, end: nameLen, color: e.color };
|
|
151
534
|
if (!e.pending) nameSpan.bold = true;
|
|
152
535
|
if (e.kind === "agent") nameSpan.italic = true;
|
|
153
536
|
const preSpans = [nameSpan];
|
|
154
537
|
if (e.streaming) preSpans.push({ start: nameLen + 1, end: nameLen + 2, color: SPINNER_COLOR });
|
|
155
538
|
preSpans.push({ start: preLen - 2, end: preLen - 1, color: DIM });
|
|
156
|
-
const
|
|
157
|
-
const
|
|
539
|
+
const text = displayText(e.text);
|
|
540
|
+
const body = e.partial ? text + CUTOFF : text;
|
|
541
|
+
const cps = [...body];
|
|
542
|
+
const textLen = [...text].length;
|
|
158
543
|
const bodySpans = e.pending ? [{ start: 0, end: cps.length, color: DIM }] : [...mentions];
|
|
159
544
|
if (e.partial) bodySpans.push({ start: textLen, end: cps.length, color: FAINT });
|
|
160
|
-
const indent = Math.min(
|
|
161
|
-
const ranges = wrapRanges(
|
|
545
|
+
const indent = Math.min(preW, INDENT_MAX);
|
|
546
|
+
const ranges = wrapRanges(unitsOf(body), Math.max(1, width - preW), Math.max(1, width - indent));
|
|
162
547
|
return ranges.map((r, k) => {
|
|
163
548
|
const col = k === 0 ? preLen : indent;
|
|
164
549
|
const spans = k === 0 ? [...preSpans] : [];
|
|
@@ -169,10 +554,147 @@ function entryLines(e, mentions, width) {
|
|
|
169
554
|
return clampLine({ text: (k === 0 ? pre : " ".repeat(indent)) + cps.slice(r.s, r.e).join(""), spans }, width);
|
|
170
555
|
});
|
|
171
556
|
}
|
|
557
|
+
var F_LOGIN = {
|
|
558
|
+
what: (where) => `claude code is logged out on ${where}`,
|
|
559
|
+
fixYou: () => "fix: run claude in a terminal, then type /login",
|
|
560
|
+
fixOther: (o) => `ask ${o} to run claude in a terminal and type /login`
|
|
561
|
+
};
|
|
562
|
+
var F_LIMIT = {
|
|
563
|
+
what: (_w, whose) => `${whose} claude plan hit its usage limit`,
|
|
564
|
+
fixYou: () => "fix: wait for the limit to reset, or press F2 and connect an api key",
|
|
565
|
+
fixOther: () => "try again after the limit resets"
|
|
566
|
+
};
|
|
567
|
+
var F_SPEND = {
|
|
568
|
+
what: (_w, whose) => `${whose} anthropic account hit its spend limit`,
|
|
569
|
+
fixYou: () => "fix: raise the spend limit in the Anthropic console",
|
|
570
|
+
fixOther: (o) => `ask ${o} to raise the spend limit`
|
|
571
|
+
};
|
|
572
|
+
var F_CREDITS = {
|
|
573
|
+
what: (_w, whose) => `${whose} provider account is out of credits`,
|
|
574
|
+
fixYou: () => "fix: add credits in your provider console",
|
|
575
|
+
fixOther: (o) => `ask ${o} to add credits`
|
|
576
|
+
};
|
|
577
|
+
var F_NO_CLI = {
|
|
578
|
+
what: (where) => `claude code is not installed on ${where}`,
|
|
579
|
+
fixYou: () => "fix: install claude code, or press F2 and connect an api key",
|
|
580
|
+
fixOther: (o) => `ask ${o} to install claude code`
|
|
581
|
+
};
|
|
582
|
+
var F_AUTH = {
|
|
583
|
+
what: (_w, whose) => `${whose} api key was rejected`,
|
|
584
|
+
fixYou: (h) => `fix: press F2, remove \u2726${h}, then connect a working key`,
|
|
585
|
+
fixOther: (o) => `ask ${o} to reconnect the key`
|
|
586
|
+
};
|
|
587
|
+
var F_MODEL = {
|
|
588
|
+
what: () => "the model is not available on that key",
|
|
589
|
+
fixYou: (h) => `fix: press F2, remove \u2726${h}, then connect it with another model`,
|
|
590
|
+
fixOther: (o) => `ask ${o} to pick another model`
|
|
591
|
+
};
|
|
592
|
+
var F_RATE = {
|
|
593
|
+
what: () => "the provider is rate limiting it",
|
|
594
|
+
fixYou: () => "try again in a minute",
|
|
595
|
+
fixOther: () => "try again in a minute"
|
|
596
|
+
};
|
|
597
|
+
var F_BUSY = {
|
|
598
|
+
what: () => "the provider is overloaded",
|
|
599
|
+
fixYou: () => "try again in a minute",
|
|
600
|
+
fixOther: () => "try again in a minute"
|
|
601
|
+
};
|
|
602
|
+
var F_NET = {
|
|
603
|
+
what: (where) => `${where} can't reach the provider`,
|
|
604
|
+
fixYou: () => "fix: check your internet connection",
|
|
605
|
+
fixOther: () => "try again later"
|
|
606
|
+
};
|
|
607
|
+
var SEP = "\\s*[\xB7\u2022\u2219]\\s*";
|
|
608
|
+
var CLI_LOGIN = new RegExp(`^(not logged in|invalid api key|oauth token (has )?(expired|been revoked|revoked))${SEP}please run /login\\.?$`);
|
|
609
|
+
var CLI_LIMIT = [
|
|
610
|
+
/^claude ai usage limit reached\|\d+$/,
|
|
611
|
+
// "Claude AI usage limit reached|1760000000"
|
|
612
|
+
/^claude (ai )?usage limit reached\.( your limit will reset at [^\n]{1,60})?$/,
|
|
613
|
+
new RegExp(`^((5-hour|weekly|session|daily|opus|sonnet)( weekly)?|usage) limit reached${SEP}resets? [^\\n]{1,60}$`),
|
|
614
|
+
new RegExp(`^you['\u2019]ve (hit|reached) your (usage )?limit${SEP}resets? [^\\n]{1,60}$`)
|
|
615
|
+
];
|
|
616
|
+
var CLI_CREDITS = /^credit balance is too low\.?$/;
|
|
617
|
+
var CLI_NET = /^api error:?\s*\(?(request timed out|connection error|fetch failed)[.)]*$/;
|
|
618
|
+
var CLI_LIMIT_ANY = /claude (ai )?usage limit reached|\b(5-hour|weekly|session|daily|opus|sonnet)( weekly)? limit reached|you['’]ve (hit|reached) your (usage )?limit/;
|
|
619
|
+
function cliApiError(raw) {
|
|
620
|
+
const m = /^api error:?\s*(\d{3})\b\s*([\s\S]*)$/i.exec(raw.trim());
|
|
621
|
+
if (!m) return null;
|
|
622
|
+
const login = new RegExp(`${SEP}please run /login\\.?$`, "i");
|
|
623
|
+
const json = m[2].replace(login, "").trim();
|
|
624
|
+
let message = "";
|
|
625
|
+
if (json) {
|
|
626
|
+
if (!json.startsWith("{")) return null;
|
|
627
|
+
try {
|
|
628
|
+
const o = JSON.parse(json);
|
|
629
|
+
message = String(o?.error?.message ?? o?.message ?? "");
|
|
630
|
+
} catch {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const body = (json + (login.test(m[2]) ? " please run /login" : "")).toLowerCase();
|
|
635
|
+
return { status: Number(m[1]), body, message };
|
|
636
|
+
}
|
|
637
|
+
function classifyFailure(raw, reply) {
|
|
638
|
+
const s = raw.trim().toLowerCase();
|
|
639
|
+
if (reply) {
|
|
640
|
+
if (!s || s.includes("\n") || s.length > 600) return null;
|
|
641
|
+
if (CLI_LOGIN.test(s)) return F_LOGIN;
|
|
642
|
+
if (CLI_LIMIT.some((re) => re.test(s))) return F_LIMIT;
|
|
643
|
+
if (CLI_CREDITS.test(s)) return F_CREDITS;
|
|
644
|
+
if (CLI_NET.test(s)) return F_NET;
|
|
645
|
+
const api = cliApiError(raw);
|
|
646
|
+
if (!api) return null;
|
|
647
|
+
if (api.status === 401 || api.status === 403 || /please run \/login|oauth token/.test(api.body)) return F_LOGIN;
|
|
648
|
+
return providerFailure(api.body, api.status) ?? { unknown: `api error ${api.status}${api.message ? " \xB7 " + api.message : ""}` };
|
|
649
|
+
}
|
|
650
|
+
if (/claude cli not found|claude: command not found/.test(s)) return F_NO_CLI;
|
|
651
|
+
if (/please run \/login|^not logged in\b|oauth token (has )?(expired|been revoked|revoked)/.test(s)) return F_LOGIN;
|
|
652
|
+
const status = Number(/^api error:?\s*(\d{3})\b/.exec(s)?.[1] ?? 0);
|
|
653
|
+
return providerFailure(s, status) ?? { unknown: raw };
|
|
654
|
+
}
|
|
655
|
+
function providerFailure(s, status) {
|
|
656
|
+
if (/credit balance is too low|insufficient[ _](credit|balance|quota)|exceeded your current quota/.test(s)) return F_CREDITS;
|
|
657
|
+
if (/specified api usage limits/.test(s)) return F_SPEND;
|
|
658
|
+
if (status === 429 || /rate.?limit|too many requests/.test(s)) return F_RATE;
|
|
659
|
+
if (CLI_LIMIT_ANY.test(s)) return F_LIMIT;
|
|
660
|
+
if (status === 401 || status === 403 || /invalid x-api-key|invalid api key|incorrect api key|authentication|unauthorized|permission/.test(s)) return F_AUTH;
|
|
661
|
+
if (status === 404 || /not_found_error|model.{0,40}(not found|does not exist|not available)|invalid model|^model: \S+$/.test(s)) return F_MODEL;
|
|
662
|
+
if (status >= 500 || /overloaded|internal server error|service unavailable|bad gateway/.test(s)) return F_BUSY;
|
|
663
|
+
if (/network|fetch failed|econnrefused|enotfound|etimedout|econnreset|timed out|socket hang up|getaddrinfo/.test(s)) return F_NET;
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
var COULDNT_REPLY = /^@([a-z0-9][a-z0-9-]{2,19}) couldn't reply: ([\s\S]*)$/;
|
|
667
|
+
function agentNotice(e, ownerOf) {
|
|
668
|
+
let handle, raw, reply;
|
|
669
|
+
if (e.kind === "agent") {
|
|
670
|
+
if (e.streaming && !e.text) return null;
|
|
671
|
+
handle = e.authorName;
|
|
672
|
+
raw = e.text;
|
|
673
|
+
reply = true;
|
|
674
|
+
} else if (e.kind === "system") {
|
|
675
|
+
const m = COULDNT_REPLY.exec(e.text);
|
|
676
|
+
if (!m) return null;
|
|
677
|
+
handle = m[1];
|
|
678
|
+
raw = m[2];
|
|
679
|
+
reply = false;
|
|
680
|
+
} else return null;
|
|
681
|
+
const owner = ownerOf(handle);
|
|
682
|
+
if (reply && owner?.cli === false) return null;
|
|
683
|
+
const f = classifyFailure(raw, reply);
|
|
684
|
+
if (!f) return null;
|
|
685
|
+
if ("unknown" in f) {
|
|
686
|
+
const why = f.unknown.trim().replace(/\s+/g, " ").slice(0, 160) || "no reason given";
|
|
687
|
+
return `\u2726${handle} can't reply \xB7 ${why}`;
|
|
688
|
+
}
|
|
689
|
+
const where = owner?.you ? "this machine" : owner ? `${owner.name}'s machine` : "its owner's machine";
|
|
690
|
+
const whose = owner?.you ? "your" : owner ? `${owner.name}'s` : "its owner's";
|
|
691
|
+
const fix = owner?.you ? f.fixYou(handle) : f.fixOther(owner?.name ?? "its owner", handle);
|
|
692
|
+
return `\u2726${handle} can't reply \xB7 ${f.what(where, whose)} \xB7 ${fix}`;
|
|
693
|
+
}
|
|
172
694
|
var makeCell = (e, ents) => ({
|
|
173
695
|
e,
|
|
174
696
|
ents,
|
|
175
|
-
mentions: ents ? scanMentions(e.text, ents) : [],
|
|
697
|
+
mentions: ents ? scanMentions(displayText(e.text), ents) : [],
|
|
176
698
|
cache: null
|
|
177
699
|
});
|
|
178
700
|
var emptyLine = () => ({ text: "", spans: [] });
|
|
@@ -185,13 +707,18 @@ var ChatLog = class {
|
|
|
185
707
|
lastGutters = [];
|
|
186
708
|
pinned = true;
|
|
187
709
|
unseen = 0;
|
|
710
|
+
unseenRun = 0;
|
|
188
711
|
/** Older pages may exist above; assume yes until a history page says no. */
|
|
189
712
|
hasMoreHistory = true;
|
|
713
|
+
/** Optional: swap agent-failure entries for a plain notice (agentNotice).
|
|
714
|
+
* Consulted at wrap time; call dropCaches() when its answers change. */
|
|
715
|
+
noticer = null;
|
|
190
716
|
get size() {
|
|
191
717
|
return this.cells.length;
|
|
192
718
|
}
|
|
193
719
|
push(e, mentionEntities2) {
|
|
194
|
-
|
|
720
|
+
const cell = makeCell(e, mentionEntities2);
|
|
721
|
+
this.cells.push(cell);
|
|
195
722
|
if (this.cells.length > CHAT_MAX) {
|
|
196
723
|
this.cells.shift();
|
|
197
724
|
if (this.anchor) {
|
|
@@ -199,7 +726,10 @@ var ChatLog = class {
|
|
|
199
726
|
else this.anchor.offset = 0;
|
|
200
727
|
}
|
|
201
728
|
}
|
|
202
|
-
if (!this.pinned)
|
|
729
|
+
if (!this.pinned) {
|
|
730
|
+
this.unseen++;
|
|
731
|
+
cell.unseenRun = this.unseenRun;
|
|
732
|
+
}
|
|
203
733
|
}
|
|
204
734
|
/** Replace the whole log (fresh join / resync); re-pins to the tail. */
|
|
205
735
|
reset(entries, mentionEntities2) {
|
|
@@ -229,13 +759,7 @@ var ChatLog = class {
|
|
|
229
759
|
const i = this.cells.findIndex((c) => c.e.pending && c.e.cmid === cmid);
|
|
230
760
|
if (i < 0) return null;
|
|
231
761
|
const [cell] = this.cells.splice(i, 1);
|
|
232
|
-
|
|
233
|
-
if (this.anchor.index > i) this.anchor.index--;
|
|
234
|
-
else if (this.anchor.index === i) {
|
|
235
|
-
if (!this.cells.length) this.pinToTail();
|
|
236
|
-
else this.anchor = { index: Math.min(i, this.cells.length - 1), offset: 0 };
|
|
237
|
-
}
|
|
238
|
-
}
|
|
762
|
+
this.forget(cell, i);
|
|
239
763
|
return cell.e;
|
|
240
764
|
}
|
|
241
765
|
/** Append stream text to the matching streaming entry; only that
|
|
@@ -246,7 +770,7 @@ var ChatLog = class {
|
|
|
246
770
|
const c = this.cells[i];
|
|
247
771
|
if (!c.e.streaming || !match(c.e)) continue;
|
|
248
772
|
c.e.text += delta;
|
|
249
|
-
if (c.ents) c.mentions = scanMentions(c.e.text, c.ents);
|
|
773
|
+
if (c.ents) c.mentions = scanMentions(displayText(c.e.text), c.ents);
|
|
250
774
|
c.cache = null;
|
|
251
775
|
return true;
|
|
252
776
|
}
|
|
@@ -262,6 +786,34 @@ var ChatLog = class {
|
|
|
262
786
|
}
|
|
263
787
|
return false;
|
|
264
788
|
}
|
|
789
|
+
/** Remove a streaming entry that never received any text (the agent
|
|
790
|
+
* failed or ended silently), so no empty `✦name ▸` row is left behind.
|
|
791
|
+
* Returns the removed entry (so the caller can say what happened), or
|
|
792
|
+
* null when no empty stream matched. */
|
|
793
|
+
dropEmptyStream(match) {
|
|
794
|
+
for (let i = this.cells.length - 1; i >= 0; i--) {
|
|
795
|
+
const c = this.cells[i];
|
|
796
|
+
if (!c.e.streaming || !match(c.e)) continue;
|
|
797
|
+
if (c.e.text.trim()) return null;
|
|
798
|
+
this.cells.splice(i, 1);
|
|
799
|
+
this.forget(c, i);
|
|
800
|
+
return c.e;
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
/** Bookkeeping after removing `cell` from index i: the read position keeps
|
|
805
|
+
* its place, and a row that counted toward the "↓ N new" pill no longer
|
|
806
|
+
* does (it would count a message that is not there). */
|
|
807
|
+
forget(cell, i) {
|
|
808
|
+
if (cell.unseenRun === this.unseenRun && this.unseen > 0) this.unseen--;
|
|
809
|
+
if (this.anchor) {
|
|
810
|
+
if (this.anchor.index > i) this.anchor.index--;
|
|
811
|
+
else if (this.anchor.index === i) {
|
|
812
|
+
if (!this.cells.length) this.pinToTail();
|
|
813
|
+
else this.anchor = { index: Math.min(i, this.cells.length - 1), offset: 0 };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
265
817
|
/** End a stream; opts.partial marks it as cut off mid-generation. */
|
|
266
818
|
finalize(match, opts) {
|
|
267
819
|
for (let i = this.cells.length - 1; i >= 0; i--) {
|
|
@@ -312,6 +864,7 @@ var ChatLog = class {
|
|
|
312
864
|
this.pinned = true;
|
|
313
865
|
this.anchor = null;
|
|
314
866
|
this.unseen = 0;
|
|
867
|
+
this.unseenRun++;
|
|
315
868
|
}
|
|
316
869
|
scrollToTop(width, viewH) {
|
|
317
870
|
if (!this.topAnchor(width, viewH)) {
|
|
@@ -374,7 +927,7 @@ var ChatLog = class {
|
|
|
374
927
|
// ---------- internals ----------
|
|
375
928
|
wrapOf(i, width) {
|
|
376
929
|
const c = this.cells[i];
|
|
377
|
-
if (!c.cache || c.cache.width !== width) c.cache = { width, lines: entryLines(c.e, c.mentions, width) };
|
|
930
|
+
if (!c.cache || c.cache.width !== width) c.cache = { width, lines: entryLines(c.e, c.mentions, width, this.noticer) };
|
|
378
931
|
return c.cache.lines;
|
|
379
932
|
}
|
|
380
933
|
gutterAt(i) {
|
|
@@ -838,7 +1391,8 @@ var CinePlayer = class {
|
|
|
838
1391
|
const sx = Math.min(CINE_W - 1, Math.floor((x - offX) / scale));
|
|
839
1392
|
const p = sy * CINE_W + sx;
|
|
840
1393
|
const color = buf[COLOR_BASE + p * N + idx];
|
|
841
|
-
|
|
1394
|
+
const ch = color === BAR ? " " : CINE_RAMP[buf[p * N + idx]] ?? " ";
|
|
1395
|
+
cells.push({ x, y, cell: { ch, color, bgColor: BAR } });
|
|
842
1396
|
}
|
|
843
1397
|
}
|
|
844
1398
|
return { cells };
|
|
@@ -1303,6 +1857,408 @@ function microLine(w, engine, frame2, info) {
|
|
|
1303
1857
|
return clampTo(tiny, Math.max(0, w));
|
|
1304
1858
|
}
|
|
1305
1859
|
|
|
1860
|
+
// src/livekit-session.ts
|
|
1861
|
+
function voiceModeFrom(v) {
|
|
1862
|
+
const s = (v ?? "").trim().toLowerCase();
|
|
1863
|
+
return s === "livekit" ? "livekit" : s === "relay" ? "relay" : "auto";
|
|
1864
|
+
}
|
|
1865
|
+
var LIVEKIT_JOIN = { t: "voiceJoin", via: "livekit" };
|
|
1866
|
+
var VoiceSession = class {
|
|
1867
|
+
constructor(host, parts = {}) {
|
|
1868
|
+
this.host = host;
|
|
1869
|
+
this.parts = parts;
|
|
1870
|
+
this.mode = parts.mode ?? voiceModeFrom(process.env.ACCELR8_VOICE);
|
|
1871
|
+
this.device = parts.device;
|
|
1872
|
+
this.relay = new RelayVoice({
|
|
1873
|
+
send: (m) => host.send(m),
|
|
1874
|
+
status: (s) => host.status(s),
|
|
1875
|
+
currentProject: () => host.currentProject()?.id ?? null,
|
|
1876
|
+
setPeers: (p) => host.setPeers(p),
|
|
1877
|
+
now: host.now ? () => host.now() : void 0
|
|
1878
|
+
}, parts.relayParts ?? {}, parts.device);
|
|
1879
|
+
}
|
|
1880
|
+
host;
|
|
1881
|
+
parts;
|
|
1882
|
+
mode;
|
|
1883
|
+
/** the server's voiceCaps answer; null until one arrives (old servers never answer) */
|
|
1884
|
+
caps = null;
|
|
1885
|
+
relay;
|
|
1886
|
+
call = null;
|
|
1887
|
+
phase = "off";
|
|
1888
|
+
projectId = null;
|
|
1889
|
+
presence = false;
|
|
1890
|
+
// we told the room we are in voice
|
|
1891
|
+
unsub = null;
|
|
1892
|
+
gen = 0;
|
|
1893
|
+
// bumped by leave(): cancels a start in flight
|
|
1894
|
+
waiters = [];
|
|
1895
|
+
last = null;
|
|
1896
|
+
endStatus = null;
|
|
1897
|
+
device;
|
|
1898
|
+
wantTalk = false;
|
|
1899
|
+
// what ctrl-g last asked for (snapshots lag behind)
|
|
1900
|
+
capsAskedAt = null;
|
|
1901
|
+
// when we last sent voiceCaps
|
|
1902
|
+
capsWaiters = [];
|
|
1903
|
+
/** in voice, or on the way in */
|
|
1904
|
+
get active() {
|
|
1905
|
+
return this.relay.on || this.phase !== "off";
|
|
1906
|
+
}
|
|
1907
|
+
get transport() {
|
|
1908
|
+
return this.relay.on ? "relay" : this.phase !== "off" ? "livekit" : null;
|
|
1909
|
+
}
|
|
1910
|
+
/** 'window' | 'terminal' while a LiveKit call exists */
|
|
1911
|
+
get liveKitKind() {
|
|
1912
|
+
return this.call?.kind ?? null;
|
|
1913
|
+
}
|
|
1914
|
+
/** /voice and /voice headphones */
|
|
1915
|
+
toggle(opts = {}) {
|
|
1916
|
+
if (this.active) this.leave();
|
|
1917
|
+
else this.start(opts);
|
|
1918
|
+
}
|
|
1919
|
+
start(opts = {}) {
|
|
1920
|
+
if (this.active) return;
|
|
1921
|
+
const p = this.host.currentProject();
|
|
1922
|
+
if (!p) {
|
|
1923
|
+
this.host.status("join a project first");
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
if (this.mode === "relay") {
|
|
1927
|
+
if (opts.here) this.host.status("ACCELR8_VOICE=relay \xB7 using the relay");
|
|
1928
|
+
this.startRelay(p.id);
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
if (this.mode === "auto" && !opts.here && this.caps === null && this.capsPending()) {
|
|
1932
|
+
void this.startAfterCaps(p);
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
this.choose(p, opts.here === true);
|
|
1936
|
+
}
|
|
1937
|
+
choose(p, here) {
|
|
1938
|
+
const tryLiveKit = this.mode === "livekit" || here || this.caps === true;
|
|
1939
|
+
if (!tryLiveKit) {
|
|
1940
|
+
this.startRelay(p.id);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
void this.startLiveKit(p, here);
|
|
1944
|
+
}
|
|
1945
|
+
capsWaitMs() {
|
|
1946
|
+
return this.parts.capsWaitMs ?? 3e3;
|
|
1947
|
+
}
|
|
1948
|
+
capsPending() {
|
|
1949
|
+
return this.capsAskedAt !== null && this.now() - this.capsAskedAt < this.capsWaitMs();
|
|
1950
|
+
}
|
|
1951
|
+
async startAfterCaps(p) {
|
|
1952
|
+
const gen = ++this.gen;
|
|
1953
|
+
this.phase = "asking";
|
|
1954
|
+
this.projectId = p.id;
|
|
1955
|
+
this.host.status("voice \xB7 connecting\u2026");
|
|
1956
|
+
await new Promise((resolve) => {
|
|
1957
|
+
const done = () => {
|
|
1958
|
+
clearTimeout(timer);
|
|
1959
|
+
const i = this.capsWaiters.indexOf(done);
|
|
1960
|
+
if (i >= 0) this.capsWaiters.splice(i, 1);
|
|
1961
|
+
resolve();
|
|
1962
|
+
};
|
|
1963
|
+
const timer = setTimeout(done, Math.max(0, (this.capsAskedAt ?? 0) + this.capsWaitMs() - this.now()));
|
|
1964
|
+
timer.unref?.();
|
|
1965
|
+
this.capsWaiters.push(done);
|
|
1966
|
+
});
|
|
1967
|
+
if (gen !== this.gen) return;
|
|
1968
|
+
this.phase = "off";
|
|
1969
|
+
this.projectId = null;
|
|
1970
|
+
if (this.host.currentProject()?.id !== p.id) return;
|
|
1971
|
+
this.choose(p, false);
|
|
1972
|
+
}
|
|
1973
|
+
startRelay(projectId) {
|
|
1974
|
+
const r = this.relay.start(projectId, { name: this.host.meName(), version: Number(this.host.version) || 0 });
|
|
1975
|
+
if (r === "install") this.host.offerInstall();
|
|
1976
|
+
}
|
|
1977
|
+
/** Tell the room whether this session is in voice (only on a change). */
|
|
1978
|
+
setPresence(on) {
|
|
1979
|
+
if (on === this.presence) return;
|
|
1980
|
+
this.presence = on;
|
|
1981
|
+
this.host.send(on ? { ...LIVEKIT_JOIN } : { t: "voiceLeave" });
|
|
1982
|
+
}
|
|
1983
|
+
async startLiveKit(p, here) {
|
|
1984
|
+
const gen = ++this.gen;
|
|
1985
|
+
this.phase = "asking";
|
|
1986
|
+
this.projectId = p.id;
|
|
1987
|
+
this.host.status("voice \xB7 connecting\u2026");
|
|
1988
|
+
this.setPresence(true);
|
|
1989
|
+
const r = await this.requestToken();
|
|
1990
|
+
if (gen !== this.gen) return;
|
|
1991
|
+
if (!r || !r.ok) {
|
|
1992
|
+
this.phase = "off";
|
|
1993
|
+
this.projectId = null;
|
|
1994
|
+
const why = !r ? "the server did not answer" : r.message;
|
|
1995
|
+
vlog(this.now(), "session", "no livekit token", { why, error: r && !r.ok ? r.error : "timeout" });
|
|
1996
|
+
const noLiveKit = r ? r.error === "voice_unconfigured" : this.caps !== true;
|
|
1997
|
+
if (this.mode === "auto" && noLiveKit) {
|
|
1998
|
+
this.presence = false;
|
|
1999
|
+
if (this.relay.start(p.id, { name: this.host.meName(), version: Number(this.host.version) || 0 }) === "install") {
|
|
2000
|
+
this.host.send({ t: "voiceLeave" });
|
|
2001
|
+
this.host.offerInstall();
|
|
2002
|
+
}
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
this.setPresence(false);
|
|
2006
|
+
if (r && !r.ok && (r.error === "rate" || r.error === "not_in_project" || r.error === "not_in_voice")) {
|
|
2007
|
+
this.host.status("voice \xB7 " + why);
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
if (this.mode === "livekit") {
|
|
2011
|
+
this.host.status(`voice \xB7 no LiveKit here: ${why} \xB7 ACCELR8_VOICE=relay to use the relay`);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
this.host.status(r ? `voice \xB7 ${why} \xB7 try /voice again` : "voice \xB7 the server did not answer, try /voice again");
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (this.host.currentProject()?.id !== p.id) {
|
|
2018
|
+
this.phase = "off";
|
|
2019
|
+
this.projectId = null;
|
|
2020
|
+
this.setPresence(false);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
this.phase = "opening";
|
|
2024
|
+
let call;
|
|
2025
|
+
try {
|
|
2026
|
+
if (here) {
|
|
2027
|
+
const tools = (this.parts.tools ?? detectVoiceTools)();
|
|
2028
|
+
if (!tools.ffmpegPath) {
|
|
2029
|
+
this.phase = "off";
|
|
2030
|
+
this.projectId = null;
|
|
2031
|
+
this.setPresence(false);
|
|
2032
|
+
this.host.offerInstall();
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const join2 = this.parts.joinTerminal ?? (await import("./livekit-terminal-22Q2NFB5.js")).joinTerminalVoice;
|
|
2036
|
+
call = await join2({ url: r.url, token: r.token, room: r.room, identity: r.identity, tools, device: this.device });
|
|
2037
|
+
} else {
|
|
2038
|
+
const open = this.parts.openWindow ?? (await import("./livekit-window-XVOCBQOM.js")).openVoiceWindow;
|
|
2039
|
+
let first = { token: r.token, expiresAt: r.expiresAt };
|
|
2040
|
+
call = await open({
|
|
2041
|
+
liveKitUrl: r.url,
|
|
2042
|
+
room: r.room,
|
|
2043
|
+
identity: r.identity,
|
|
2044
|
+
title: p.name,
|
|
2045
|
+
getToken: async () => {
|
|
2046
|
+
if (first && first.expiresAt - this.now() > 6e4) return first.token;
|
|
2047
|
+
first = null;
|
|
2048
|
+
const again = await this.requestToken();
|
|
2049
|
+
if (!again || !again.ok) throw new Error(again && !again.ok ? again.message : "the server did not answer");
|
|
2050
|
+
return again.token;
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
} catch (e) {
|
|
2055
|
+
if (gen === this.gen) {
|
|
2056
|
+
this.phase = "off";
|
|
2057
|
+
this.projectId = null;
|
|
2058
|
+
this.setPresence(false);
|
|
2059
|
+
}
|
|
2060
|
+
const m = e instanceof Error ? e.message : String(e);
|
|
2061
|
+
vlog(this.now(), "session", "livekit client failed to start", { here, err: m });
|
|
2062
|
+
if (gen !== this.gen) return;
|
|
2063
|
+
if (here && /Cannot find (package|module)|ERR_MODULE_NOT_FOUND/i.test(m)) {
|
|
2064
|
+
this.host.status(rtcNodeMissingHint());
|
|
2065
|
+
} else {
|
|
2066
|
+
this.host.status("voice \xB7 " + m.slice(0, 90));
|
|
2067
|
+
}
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
if (gen !== this.gen) {
|
|
2071
|
+
void call.leave();
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
this.call = call;
|
|
2075
|
+
this.phase = "on";
|
|
2076
|
+
this.last = null;
|
|
2077
|
+
this.endStatus = null;
|
|
2078
|
+
this.wantTalk = !call.snapshot().muted;
|
|
2079
|
+
this.host.status(here ? "voice in this terminal \xB7 headphones only \xB7 ctrl-g to talk" : "voice window open \xB7 click Join there");
|
|
2080
|
+
this.unsub = call.subscribe((s) => this.onCallState(s));
|
|
2081
|
+
this.onCallState(call.snapshot());
|
|
2082
|
+
}
|
|
2083
|
+
onCallState(s) {
|
|
2084
|
+
const prev = this.last;
|
|
2085
|
+
this.last = s;
|
|
2086
|
+
const inCall = s.connection === "connected" || s.connection === "reconnecting";
|
|
2087
|
+
if (inCall) this.setPresence(true);
|
|
2088
|
+
if (s.connection === "disconnected") {
|
|
2089
|
+
this.setPresence(false);
|
|
2090
|
+
const why = this.endStatus ?? describeEnd(s.endReason) + (s.error && s.endReason !== "left" ? " \xB7 " + s.error.slice(0, 60) : "");
|
|
2091
|
+
vlog(this.now(), "session", "livekit call ended", { reason: s.endReason, error: s.error });
|
|
2092
|
+
this.unsub?.();
|
|
2093
|
+
this.unsub = null;
|
|
2094
|
+
this.call = null;
|
|
2095
|
+
this.phase = "off";
|
|
2096
|
+
this.projectId = null;
|
|
2097
|
+
this.endStatus = null;
|
|
2098
|
+
this.host.status(why);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (prev && prev.muted !== s.muted) this.wantTalk = !s.muted;
|
|
2102
|
+
if (!prev || prev.needsClick !== s.needsClick) {
|
|
2103
|
+
if (s.needsClick) this.host.status("voice window open \xB7 click Join there");
|
|
2104
|
+
}
|
|
2105
|
+
if (s.connection === "connected" && prev?.connection !== "connected") {
|
|
2106
|
+
this.host.status(s.muted ? "voice on \xB7 muted \xB7 ctrl-g to talk" : "\u25CF live \xB7 ctrl-g or /mute to stop");
|
|
2107
|
+
} else if (s.connection === "reconnecting" && prev?.connection === "connected") {
|
|
2108
|
+
this.host.status("voice \xB7 reconnecting\u2026");
|
|
2109
|
+
} else if (prev && s.connection === "connected" && prev.muted !== s.muted) {
|
|
2110
|
+
this.host.status(s.muted ? "muted \xB7 ctrl-g or /talk to speak" : "\u25CF live \xB7 ctrl-g or /mute to stop");
|
|
2111
|
+
}
|
|
2112
|
+
if (s.error && s.error !== prev?.error) this.host.status("voice \xB7 " + s.error.slice(0, 90));
|
|
2113
|
+
}
|
|
2114
|
+
/** /voice while in voice, goHome, a project switch. */
|
|
2115
|
+
leave(quiet = false, why) {
|
|
2116
|
+
this.gen++;
|
|
2117
|
+
if (this.relay.on) {
|
|
2118
|
+
this.relay.leave(quiet || !!why);
|
|
2119
|
+
if (why) this.host.status(why);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
if (this.phase === "asking" || this.phase === "opening") {
|
|
2123
|
+
this.phase = "off";
|
|
2124
|
+
this.projectId = null;
|
|
2125
|
+
this.setPresence(false);
|
|
2126
|
+
if (!quiet || why) this.host.status(why ?? "voice off");
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (this.call) {
|
|
2130
|
+
this.endStatus = why ?? "voice off";
|
|
2131
|
+
void this.call.leave().catch(() => void 0);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
/** ctrl-g toggles; /talk and /mute set. A typed /mute never opens the mic. */
|
|
2135
|
+
talk(on) {
|
|
2136
|
+
if (this.relay.on) {
|
|
2137
|
+
if (on === void 0) this.relay.toggleTalk();
|
|
2138
|
+
else this.relay.setTalk(on);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const c = this.call;
|
|
2142
|
+
if (!c) return;
|
|
2143
|
+
const s = c.snapshot();
|
|
2144
|
+
const next = on ?? !this.wantTalk;
|
|
2145
|
+
this.wantTalk = next;
|
|
2146
|
+
void c.setMuted(!next);
|
|
2147
|
+
if (s.connection !== "connected") {
|
|
2148
|
+
this.host.status(next ? "your mic opens once you are in the call" : "you will join muted");
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
/** The room socket (re)joined a project. */
|
|
2152
|
+
onJoined(projectId) {
|
|
2153
|
+
if (this.mode !== "relay") {
|
|
2154
|
+
this.host.send({ t: "voiceCaps" });
|
|
2155
|
+
this.capsAskedAt = this.now();
|
|
2156
|
+
}
|
|
2157
|
+
if (this.relay.on) {
|
|
2158
|
+
this.relay.onJoined(projectId);
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
if (this.phase === "off") return;
|
|
2162
|
+
if (projectId !== this.projectId) {
|
|
2163
|
+
this.leave(true, "voice off \xB7 you switched projects");
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
if (this.presence) this.host.send({ ...LIVEKIT_JOIN });
|
|
2167
|
+
}
|
|
2168
|
+
/** Voice messages from the server. true = handled. */
|
|
2169
|
+
handle(msg) {
|
|
2170
|
+
switch (msg.t) {
|
|
2171
|
+
case "voiceCaps":
|
|
2172
|
+
this.caps = msg.livekit === true;
|
|
2173
|
+
for (const w of [...this.capsWaiters]) w();
|
|
2174
|
+
return true;
|
|
2175
|
+
case "voiceToken": {
|
|
2176
|
+
const w = this.waiters.shift();
|
|
2177
|
+
if (w) {
|
|
2178
|
+
clearTimeout(w.timer);
|
|
2179
|
+
w.resolve(msg);
|
|
2180
|
+
}
|
|
2181
|
+
return true;
|
|
2182
|
+
}
|
|
2183
|
+
case "voiceJoined":
|
|
2184
|
+
if (this.relay.on) return this.relay.handle(msg);
|
|
2185
|
+
if (Array.isArray(msg.peers)) this.host.setPeers(msg.peers);
|
|
2186
|
+
return true;
|
|
2187
|
+
case "voiceDrop":
|
|
2188
|
+
case "voiceStats":
|
|
2189
|
+
case "voiceFrame":
|
|
2190
|
+
return this.relay.on ? this.relay.handle(msg) : true;
|
|
2191
|
+
}
|
|
2192
|
+
return false;
|
|
2193
|
+
}
|
|
2194
|
+
requestToken() {
|
|
2195
|
+
return new Promise((resolve) => {
|
|
2196
|
+
const timer = setTimeout(() => {
|
|
2197
|
+
const i = this.waiters.findIndex((w) => w.timer === timer);
|
|
2198
|
+
if (i >= 0) this.waiters.splice(i, 1);
|
|
2199
|
+
resolve(null);
|
|
2200
|
+
}, this.parts.tokenTimeoutMs ?? 4e3);
|
|
2201
|
+
timer.unref?.();
|
|
2202
|
+
this.waiters.push({ resolve, timer });
|
|
2203
|
+
this.host.send({ t: "voiceToken", client: "tui" });
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
indicator(peerCount) {
|
|
2207
|
+
if (this.relay.on) return this.relay.indicator(peerCount);
|
|
2208
|
+
if (this.phase === "off") return null;
|
|
2209
|
+
const s = this.call?.snapshot();
|
|
2210
|
+
if (!s) return { label: " \u25CC connecting\u2026", live: false };
|
|
2211
|
+
if (s.needsClick) return { label: " \u25CC click Join in the voice window", live: false };
|
|
2212
|
+
if (s.connection === "connecting") return { label: " \u25CC connecting\u2026", live: false };
|
|
2213
|
+
if (s.connection === "reconnecting") return { label: " \u25CC reconnecting\u2026", live: false };
|
|
2214
|
+
const n = Math.max(s.participants.length, peerCount);
|
|
2215
|
+
return s.muted ? { label: ` \u25CB voice ${n} \xB7 ctrl-g to talk`, live: false } : { label: " \u25CF live \xB7 ctrl-g to mute", live: true };
|
|
2216
|
+
}
|
|
2217
|
+
debugLines(peerCount) {
|
|
2218
|
+
const head = `mode: ${this.mode} \xB7 server LiveKit: ${this.caps === null ? "no answer" : this.caps ? "yes" : "no"}`;
|
|
2219
|
+
if (this.relay.on) return [head, ...this.relay.debugLines(peerCount)];
|
|
2220
|
+
if (this.call) return [head, ...this.call.debug()];
|
|
2221
|
+
return [head, `voice: ${this.phase === "off" ? "off" : this.phase}`];
|
|
2222
|
+
}
|
|
2223
|
+
async listDevices() {
|
|
2224
|
+
if (this.call) {
|
|
2225
|
+
const list = await this.call.listDevices("audioinput");
|
|
2226
|
+
return list.map((d) => ({ index: d.id, name: d.label }));
|
|
2227
|
+
}
|
|
2228
|
+
return listMicDevices();
|
|
2229
|
+
}
|
|
2230
|
+
setDevice(id) {
|
|
2231
|
+
if (this.call?.kind === "window") {
|
|
2232
|
+
if (id) void this.call.setInputDevice(id);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
this.device = id;
|
|
2236
|
+
this.relay.setDevice(id);
|
|
2237
|
+
if (this.call && id) void this.call.setInputDevice(id);
|
|
2238
|
+
}
|
|
2239
|
+
resetTools() {
|
|
2240
|
+
this.relay.resetTools();
|
|
2241
|
+
}
|
|
2242
|
+
/** Quit path: synchronous. Every helper dies now; the voice window gets
|
|
2243
|
+
* 'bye' (and hangs up by itself if that never arrives). */
|
|
2244
|
+
shutdown() {
|
|
2245
|
+
try {
|
|
2246
|
+
this.relay.shutdown();
|
|
2247
|
+
} catch {
|
|
2248
|
+
}
|
|
2249
|
+
try {
|
|
2250
|
+
this.call?.quit();
|
|
2251
|
+
} catch {
|
|
2252
|
+
}
|
|
2253
|
+
this.call = null;
|
|
2254
|
+
this.phase = "off";
|
|
2255
|
+
killAllHelpers();
|
|
2256
|
+
}
|
|
2257
|
+
now() {
|
|
2258
|
+
return this.host.now ? this.host.now() : Date.now();
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
|
|
1306
2262
|
// src/world-data.ts
|
|
1307
2263
|
var PACKED = `-92.32,48.24 -88.13,48.92 -83.11,46.27 -81.66,44.76 -82.09,42.29 -77.1,44.0 -69.95,46.92 -65.92,45.32 -66.37,44.25 -61.22,45.43 -64.94,47.34 -64.12,48.52 -70.68,47.02 -67.24,49.33 -59.82,50.48 -56.14,52.46 -59.07,53.58 -58.26,54.21 -60.69,55.33 -61.97,57.41 -64.35,59.49 -67.29,58.15 -69.89,59.91 -71.31,61.45 -78.22,61.97 -77.28,59.53 -77.09,55.88 -79.06,51.68 -82.23,52.7 -86.75,55.72 -92.17,56.86 -95.61,58.82 -92.66,62.02 -90.65,63.24 -95.96,64.12 -89.88,63.98 -89.3,65.22 -86.86,66.12 -84.54,66.88 -82.3,67.76 -83.1,69.68 -86.05,67.98 -88.18,68.2 -91.0,68.82 -91.72,69.69 -93.15,71.09 -96.58,71.05 -93.35,69.52 -94.23,68.25 -95.96,66.73 -98.83,68.27 -102.45,67.69 -108.34,68.43 -105.83,68.05 -108.15,66.6 -111.15,67.63 -114.1,68.23 -120.92,69.44 -124.32,69.26 -128.76,70.5 -131.86,69.19 -131.15,69.79 -135.81,69.13 -140.19,69.37 -141.2,69.58 -141.21,69.56 -142.49,69.83 -148.09,70.26 -154.37,70.96 -159.53,70.38 -166.64,68.25 -161.56,66.55 -162.99,65.97 -168.23,65.49 -161.12,64.49 -165.29,62.57 -164.58,60.06 -162.06,58.36 -157.85,58.12 -162.34,55.06 -156.52,57.11 -153.53,59.32 -149.18,60.81 -149.9,59.5 -146.54,60.36 -139.98,59.73 -137.12,58.28 -136.01,59.12 -133.84,57.12 -131.46,55.98 -132.08,57.2 -140.37,60.25 -141.21,60.16 -133.38,58.93 -130.88,54.83 -128.86,53.9 -126.58,52.12 -127.08,50.8 -124.42,49.66 -122.56,48.91 -122.44,48.92 -124.42,47.18 -124.52,42.48 -123.09,38.45 -121.73,36.62 -117.6,33.34 -117.28,32.64 -117.29,32.48 -114.75,27.8 -112.53,24.8 -110.55,24.07 -114.23,29.59 -112.58,29.99 -109.57,25.94 -105.61,21.94 -102.09,17.87 -95.75,15.94 -92.21,14.97 -92.22,14.71 -86.74,12.06 -83.03,8.65 -79.93,8.74 -77.0,7.82 -81.99,8.97 -83.92,12.7 -86.33,15.8 -88.4,15.92 -88.45,17.42 -87.01,21.33 -91.65,18.72 -96.96,20.37 -97.65,25.67 -97.62,25.82 -95.62,28.84 -90.77,29.03 -87.33,30.22 -82.69,28.15 -80.16,26.66 -80.74,32.31 -76.89,35.43 -76.47,38.21 -75.66,37.67 -71.31,41.76 -69.44,44.17 -67.69,47.03 -73.18,45.14 -79.26,43.28 -82.84,42.59 -83.49,45.32 -86.36,43.65 -87.75,43.42 -86.01,45.96 -87.0,46.59 -91.39,46.79 -90.05,47.96;-152.62,58.41 -152.6,58.4;-153.3,57.8 -152.4,57.48 -153.32,57.79;-166.96,53.96 -167.01,53.95;-168.36,53.5 -168.19,53.36;-170.73,52.68 -170.6,52.55;-174.47,51.94 -174.47,51.92;-176.58,51.71 -176.64,51.73;-177.55,51.76 -177.41,51.63;-178.27,51.75;177.35,51.8 177.33,51.76;172.44,53.0 172.55,53.03;-123.4,48.33 -128.0,50.84 -123.5,48.34;-132.49,52.88 -132.44,52.91;-132.64,53.02 -131.97,53.71 -132.63,53.02;-55.36,51.56 -54.66,49.52 -53.65,47.48 -52.98,46.31 -56.12,46.84 -58.47,47.57 -57.61,50.38 -55.39,51.53;-61.37,49.01 -61.8,49.29 -61.38,49.03;-63.01,46.71 -64.42,46.61 -63.04,46.68;-60.14,46.48 -60.14,46.5;-71.97,41.11 -71.97,41.15;-80.79,27.03 -81.01,26.99;-113.01,42.09 -113.1,42.01;-155.74,20.02 -155.73,19.98;-156.51,20.78 -156.51,20.78;-157.12,21.21 -157.08,20.95;-157.87,21.42;-159.53,22.07;-117.44,66.46 -119.59,65.24 -123.95,65.03 -123.69,66.44 -119.21,66.22 -117.44,66.44;-120.71,64.03 -114.91,62.3 -109.07,62.72 -112.62,61.19 -118.68,61.19 -117.01,61.17 -115.97,62.56 -119.46,64.0 -120.59,63.94;-112.31,58.46 -108.9,59.44 -104.14,58.9 -102.56,56.72 -101.82,58.73 -104.65,58.91 -111.0,58.51 -112.35,58.62;-98.74,50.09 -99.75,52.24 -99.62,51.47 -98.82,50.39;-97.02,50.21 -97.5,54.02 -98.69,52.93 -97.19,51.09 -96.98,50.2;-95.34,49.04 -92.32,50.34 -94.14,49.47 -95.36,48.82;-80.39,56.16 -79.22,55.94 -80.34,56.08;-103.56,58.6 -103.6,58.58;-101.82,58.03 -102.33,58.1 -101.77,58.06;-101.88,55.79 -97.92,57.15 -101.22,55.85 -101.88,55.74;-77.61,6.8 -78.7,0.97 -80.75,-4.47 -76.19,-14.57 -70.44,-18.75 -70.68,-26.15 -71.44,-32.03 -73.38,-37.27 -73.06,-42.11 -73.17,-46.09 -73.52,-48.05 -73.67,-51.56 -71.06,-53.88 -69.14,-50.77 -67.51,-46.59 -63.49,-42.8 -62.14,-40.16 -57.12,-36.71 -53.17,-34.15 -51.26,-32.02 -48.16,-25.48 -40.73,-22.32 -38.88,-15.24 -34.6,-7.81 -41.95,-3.42 -48.02,-1.84 -48.44,-1.57 -50.81,0.0 -54.47,5.39 -60.59,8.32 -64.19,9.88 -70.78,10.64 -70.97,11.89 -76.26,8.76 -77.61,6.8;-69.14,-52.79 -66.16,-55.08 -70.01,-54.88 -70.55,-53.85 -69.31,-52.81;-59.29,-51.58 -59.35,-51.54;-58.65,-51.55 -58.55,-51.56;-84.39,21.44 -73.9,19.73 -79.27,21.18 -83.74,21.8 -84.32,21.42;-66.96,17.95 -67.05,17.89;-77.88,17.22 -78.06,16.98;-74.47,18.08 -69.88,18.99 -71.1,17.76 -74.45,17.86;-85.28,73.74 -85.79,70.96 -85.13,71.94 -84.74,72.96 -80.61,73.1 -78.45,72.2 -75.44,72.55 -73.89,71.98 -72.56,71.04 -71.49,70.57 -69.78,70.29 -68.12,69.71 -65.91,69.19 -66.92,68.39 -64.08,67.68 -62.5,66.68 -63.07,65.33 -66.11,66.08 -67.48,65.41 -64.05,63.15 -66.58,63.26 -69.04,62.33 -72.22,63.77 -76.88,64.17 -73.25,65.54 -70.09,66.64 -72.05,67.44 -76.32,68.36 -78.34,70.17 -82.12,69.71 -87.64,70.12 -89.68,71.43 -85.28,73.74;-80.9,76.1 -84.21,76.28 -88.94,76.38 -85.47,77.4 -85.43,77.93 -87.01,78.54 -83.17,78.94 -84.87,79.93 -81.33,79.82 -76.27,80.92 -82.88,80.62 -82.58,81.16 -86.51,81.05 -89.36,81.21 -90.45,81.38 -89.28,81.86 -87.21,82.3 -80.51,82.05 -80.16,82.55 -77.83,82.86 -75.51,83.05 -71.18,82.9 -65.1,82.78 -63.34,81.8 -68.26,81.26 -69.46,80.34 -71.05,79.82 -74.4,79.46 -75.42,79.03 -75.48,78.92 -76.01,78.2 -80.66,77.28 -78.07,76.98 -80.9,76.13;-92.86,74.13 -92.5,72.7 -94.89,73.16 -92.96,74.14;-94.8,76.95 -89.68,76.04 -88.52,75.4 -82.36,75.67 -79.39,74.65 -86.15,74.22 -91.7,74.94 -95.6,76.91 -94.87,76.96;-99.96,73.74 -97.89,72.9 -98.28,71.13 -102.04,72.92 -101.34,73.14 -99.69,73.59;-107.58,73.25 -104.59,71.02 -101.71,69.56 -104.07,68.62 -106.61,69.12 -114.09,69.05 -113.89,70.12 -115.88,70.32 -116.1,71.32 -117.45,72.48 -113.53,72.44 -109.84,72.24 -106.62,71.71 -107.43,73.04;-120.96,74.29 -118.37,72.53 -123.06,71.18 -123.4,73.77 -120.93,74.27;-108.83,76.74 -106.25,75.54 -107.08,74.78 -112.99,74.16 -112.28,74.99 -116.04,75.33 -115.27,76.2 -110.95,75.56 -109.77,76.31 -108.82,76.7;-115.7,77.46 -118.1,76.3 -121.13,76.37 -116.04,77.28;-110.01,77.86 -112.36,77.68 -109.96,77.86;-109.6,78.48 -112.2,78.01 -109.6,78.48;-97.87,76.61 -99.21,75.31 -100.86,75.6 -99.4,76.26 -97.79,76.6;-94.72,75.53 -94.66,75.52;-104.1,79.01 -99.19,77.54 -103.22,78.08 -104.3,78.95;-93.74,77.52 -93.74,77.52;-96.88,78.5 -96.91,77.77 -96.94,78.48;-84.69,65.84 -81.58,63.87 -85.0,62.96 -84.63,65.71;-81.84,62.75 -82.01,62.63;-79.88,62.12 -79.88,62.12;-43.53,59.89 -45.29,60.67 -47.91,60.83 -49.9,62.41 -50.71,64.42 -51.39,64.94 -52.96,66.09 -53.62,67.19 -53.51,67.51 -51.84,68.65 -52.19,70.0 -51.85,71.03 -55.41,71.41 -54.63,72.97 -56.98,74.7 -61.95,76.09 -66.38,75.83 -71.13,77.0 -66.81,77.6 -70.78,77.78 -64.96,79.7 -63.38,81.16 -56.89,82.17 -48.18,82.15 -42.08,82.74 -38.02,83.54 -23.96,82.94 -25.97,81.97 -25.99,80.64 -13.57,80.97 -16.6,80.16 -19.82,78.82 -18.8,77.54 -21.98,76.46 -20.69,75.12 -21.78,74.4 -24.1,73.69 -26.54,73.08 -24.63,72.69 -21.84,71.69 -24.62,71.24 -27.16,70.89 -27.21,70.0 -24.1,69.35 -28.35,68.43 -32.48,68.56 -35.26,66.26 -37.9,65.9 -40.04,65.0 -40.49,64.04 -42.01,63.14 -42.88,61.15 -43.09,60.07 -43.56,59.9;-16.26,66.41 -15.32,64.29 -20.14,63.47 -21.76,64.21 -21.33,64.97 -23.04,65.62 -21.76,66.26 -18.77,66.12 -16.23,66.35;0.56,51.47 -1.71,54.94 -3.41,57.52 -5.42,58.14 -5.77,55.59 -3.48,54.82 -4.68,52.88 -2.68,51.58 -3.8,50.08 1.26,51.14 0.65,51.41;-7.17,54.91 -9.97,53.47 -8.52,51.76 -5.69,54.79 -7.34,55.25;-1.33,60.66 -1.17,60.38;-6.18,58.44 -6.09,58.36;-6.47,57.58 -6.33,57.54;-7.3,57.54;-7.46,57.05;-6.54,56.94;-6.0,55.94;-5.09,55.55;-4.44,54.38 -4.3,54.19;-8.08,71.02 -8.21,70.86;16.92,79.52 22.26,78.46 16.86,76.41 16.0,77.39 16.03,77.92 16.81,79.5;14.71,79.4 16.05,79.12 14.02,77.8 13.56,78.46 12.63,79.26 14.68,79.4;22.01,78.24 21.86,78.23;21.54,77.75 23.88,77.26 21.53,77.67 22.79,77.79;23.5,79.97 28.24,79.54 20.85,78.94 19.0,79.34 21.05,79.88 23.41,79.96;46.98,80.23 43.13,79.97 47.18,80.22;50.43,80.19 50.55,79.88 47.77,79.86 50.45,80.14;61.79,80.18 61.79,80.18;65.08,80.69 64.27,80.59 65.13,80.68;-5.13,35.66 4.06,36.63 10.4,37.12 11.36,33.61 20.1,30.1 23.49,32.17 31.65,30.8 35.76,23.74 39.75,14.82 42.93,11.34 51.52,11.45 49.82,6.99 43.13,-0.62 39.15,-7.58 40.37,-13.2 37.74,-18.17 35.33,-22.71 32.84,-28.15 26.5,-34.39 19.55,-35.51 17.5,-30.88 12.24,-18.75 13.89,-12.81 12.05,-5.55 9.67,0.14 7.19,3.79 1.74,5.39 -4.77,4.59 -12.0,6.75 -15.54,10.98 -16.33,15.5 -16.1,22.29 -12.9,27.12 -9.52,31.09 -5.41,35.58;33.71,0.0 33.48,-3.42 33.34,-0.2 33.71,0.0;49.3,-12.5 49.28,-18.79 43.95,-25.5 44.37,-20.08 46.34,-16.31 47.91,-14.08 49.3,-12.5;178.88,69.1 181.2,68.42 183.52,67.78 188.87,66.38 186.54,64.74 182.87,65.63 180.13,65.14 179.48,64.88 178.2,64.29 177.46,62.62 170.42,60.17 164.48,59.89 162.92,57.34 161.82,54.88 156.42,51.09 156.4,57.76 163.79,61.73 159.9,60.73 156.81,61.68 153.83,59.1 148.57,59.46 140.77,58.39 137.1,54.07 140.72,52.43 138.77,47.3 129.92,42.04 128.33,38.46 126.15,35.18 125.12,39.08 121.62,40.15 117.58,38.21 121.77,36.9 120.73,32.65 121.28,30.25 118.83,24.93 112.69,21.81 108.53,21.73 107.55,16.34 107.32,10.45 104.39,10.37 100.01,13.52 100.26,8.3 103.22,1.56 98.21,9.17 97.66,15.36 94.21,17.79 90.05,21.74 90.06,21.03 82.06,15.95 80.05,11.72 76.41,8.6 72.79,17.43 72.02,20.0 68.98,21.99 64.62,24.41 57.83,24.77 53.11,26.2 49.67,29.41 50.96,25.15 54.33,23.44 59.03,22.57 57.87,18.86 52.95,15.74 47.26,12.96 42.75,14.68 39.93,19.61 36.92,25.78 33.3,28.46 32.6,30.63 32.18,30.58 36.08,35.03 32.53,36.17 27.77,36.94 26.51,39.18 31.54,40.82 38.53,40.48 40.35,43.17 39.88,46.45 35.18,44.99 33.5,44.96 30.24,45.14 28.7,41.48 26.55,39.84 23.62,39.67 23.8,37.34 21.9,36.92 18.79,42.02 14.52,44.31 14.58,42.25 18.32,39.57 16.05,39.35 11.52,42.36 6.87,43.08 2.8,41.09 -1.11,37.14 -6.24,36.7 -8.67,39.57 -6.51,43.13 -0.84,45.55 -3.93,48.4 0.48,49.09 4.2,51.29 6.44,52.92 8.42,55.94 11.72,55.49 11.73,53.66 16.78,54.14 21.4,56.32 24.67,57.2 28.94,59.18 24.16,59.52 22.07,62.66 23.76,65.35 18.7,62.54 19.11,59.67 18.4,58.54 15.34,55.73 11.74,58.08 8.37,57.68 5.8,59.2 7.38,60.86 7.51,61.86 9.62,62.99 13.37,65.46 15.46,67.12 18.54,68.62 22.32,69.64 24.77,70.17 25.93,69.79 28.56,70.46 29.75,69.76 33.83,69.11 41.9,66.85 35.14,66.25 33.3,66.07 35.46,64.15 37.68,64.03 41.71,64.09 44.8,65.58 44.87,68.16 45.92,66.83 51.79,67.85 53.7,67.89 59.68,68.09 65.07,69.08 68.56,69.19 68.38,70.97 73.03,71.62 73.8,68.29 69.42,66.45 73.43,66.36 77.51,68.36 80.74,66.74 75.27,68.67 75.11,71.8 78.62,70.56 78.43,71.9 82.72,71.23 84.25,70.03 81.4,72.76 86.5,74.01 87.68,74.78 90.25,75.23 89.68,75.57 95.12,75.95 99.69,76.09 104.1,77.52 106.34,76.4 112.99,75.6 107.88,73.72 110.43,73.71 113.34,73.37 123.1,73.28 128.94,73.02 126.1,72.24 130.53,70.86 135.49,71.51 139.6,72.23 146.04,72.39 146.92,72.21 150.77,71.28 159.92,70.14 167.68,69.63 170.2,69.99 178.88,69.1;68.33,76.71 66.03,75.62 59.1,74.11 54.92,73.03 56.67,74.1 58.56,75.09 63.86,75.87 68.19,76.7;53.04,72.57 58.29,70.39 55.03,70.78 53.44,72.26 53.63,72.61;52.22,46.5 51.73,44.73 52.56,41.8 53.43,40.4 54.22,37.86 49.04,38.45 48.17,42.76 49.33,45.64 52.22,46.5;62.32,46.32 60.32,43.06 59.57,45.58 61.94,46.33;79.55,46.12 74.3,44.44 78.62,45.79 79.66,46.07;76.81,41.96 76.73,41.86;35.15,35.15 34.61,34.84 35.18,35.17;23.84,35.33 24.3,34.91 24.09,35.39;15.54,37.89 13.47,37.89 15.54,37.89;9.56,40.95 8.46,39.99 9.12,40.69;9.72,42.6 9.54,42.35;80.6,8.95 79.73,5.96 80.1,8.3;11.04,57.44 10.67,57.25;-77.92,24.67 -77.98,24.22;-77.61,23.62 -77.18,23.64;-75.55,24.13 -75.41,24.31;-91.4,-0.17 -91.52,-0.26;-60.25,46.68 -60.71,46.33;-63.89,49.47 -63.45,49.43;142.53,-10.6 145.62,-16.34 149.79,-22.09 153.21,-26.82 150.52,-35.19 145.6,-38.53 140.13,-37.69 137.34,-34.77 135.76,-34.56 131.5,-31.34 121.72,-33.65 115.62,-33.25 114.09,-26.01 114.88,-21.27 122.34,-18.13 125.32,-14.53 128.39,-14.9 132.35,-11.42 136.16,-12.43 138.07,-16.45 142.25,-10.78;144.72,-40.68 148.32,-42.14 145.57,-42.77 146.47,-41.19;172.86,-34.23 176.1,-37.52 177.06,-39.49 174.77,-38.03 172.83,-34.27;172.36,-40.53 172.92,-43.81 168.41,-46.13 170.26,-43.21 173.69,-40.94;150.74,-10.18 143.04,-8.26 138.48,-6.97 131.95,-2.94 130.91,-1.35 134.38,-2.64 141.24,-2.62 148.19,-8.15 150.75,-10.27;117.24,7.01 117.9,0.76 113.89,-3.5 109.44,-0.82 113.13,3.38 117.24,7.01;95.31,5.75 102.32,1.4 106.03,-2.98 101.46,-2.81 95.2,5.73;140.91,41.53 140.79,35.75 136.82,34.56 133.56,34.72 132.49,35.41 136.73,37.2 139.82,40.0 140.68,41.43;133.71,34.3 131.41,31.58 129.38,33.1 133.9,34.37;141.89,45.5 144.12,42.92 140.3,41.64 141.53,45.3 141.89,45.53;142.57,54.36 143.64,49.19 141.99,45.88 141.92,50.85 142.6,54.34;121.92,25.48 120.53,24.7 121.7,25.51;110.81,20.07 109.2,19.66 110.81,20.07;106.51,-6.16 114.15,-7.72 108.71,-7.89 106.51,-6.16;164.27,-20.01 164.16,-20.27;178.61,-17.04 178.61,-17.04;179.45,-16.43 179.35,-16.43;-172.55,-13.39 -172.61,-13.78;122.26,18.67 123.05,13.86 120.73,13.8 120.43,16.43 121.72,18.4;125.34,9.79 125.56,6.28 122.38,7.0 125.1,9.38;119.64,11.35 118.81,10.16 119.59,10.86 119.64,11.35;-179.87,65.14 -177.13,65.63 -173.46,64.74 -171.13,66.38 -176.48,67.78 -178.8,68.42;101.96,79.08 101.31,77.86 101.22,79.04;94.29,79.29 95.31,78.68 100.02,79.43 97.26,79.62 95.44,79.65;95.46,80.62 92.39,79.66 95.07,80.54;138.54,76.05 144.93,75.45 140.3,74.99 137.27,75.44 138.29,75.98;146.08,75.29 147.75,74.73 145.85,75.06;141.44,73.88 141.48,73.84;0.01,-71.68 6.57,-70.57 15.04,-70.44 25.1,-70.75 33.37,-69.1 38.46,-69.77 42.85,-68.16 46.59,-67.23 49.35,-66.96 52.9,-65.97 58.46,-67.2 63.6,-67.58 70.63,-68.41 69.24,-70.36 76.2,-69.44 88.08,-66.64 94.98,-66.52 101.53,-66.09 111.31,-65.91 118.64,-66.87 126.24,-66.24 133.09,-66.18 139.85,-66.72 146.86,-67.96 153.65,-68.82 159.94,-69.57 164.1,-70.67 170.19,-71.94 165.68,-74.64 163.82,-77.6 162.1,-78.95 166.72,-82.84 175.58,-83.86;-178.56,-84.37 -147.96,-85.4 -152.96,-81.12 -153.95,-79.5 -151.24,-77.48 -146.74,-76.44 -137.68,-75.16 -131.63,-74.63 -123.05,-74.41 -114.76,-73.97 -111.91,-75.41 -105.05,-74.77 -100.9,-74.21 -101.04,-73.18 -100.28,-73.06 -93.06,-73.33 -85.4,-73.18 -79.82,-73.04 -78.21,-72.52 -71.9,-73.41 -67.51,-71.1 -67.57,-68.92 -66.65,-66.83 -64.3,-65.28 -59.14,-63.74 -59.58,-64.37 -62.5,-65.94 -62.48,-66.66 -65.64,-68.02 -63.85,-69.07 -61.69,-70.87 -60.89,-72.71 -61.07,-74.3 -63.33,-75.88 -76.05,-77.06 -83.04,-77.12 -74.3,-80.83 -56.4,-82.14 -42.46,-81.65 -31.6,-80.17 -34.01,-79.2 -32.48,-77.28 -26.28,-76.18 -17.18,-73.45 -11.2,-72.01 -8.67,-71.98 -5.45,-71.45 -0.82,-71.74 0.07,-71.68;164.65,-77.89 170.95,-77.37 179.67,-78.25;-178.74,-78.24 -165.76,-78.47 -158.42,-77.73;-58.98,-64.63 -60.99,-68.62 -61.02,-71.7;-62.01,-74.94 -52.0,-77.07 -42.23,-77.8 -36.22,-78.03;-35.03,-77.81 -26.13,-75.54 -19.35,-73.04 -12.16,-71.86 -6.15,-70.65 -0.57,-69.14 4.93,-70.25 10.91,-69.99 16.52,-69.87 25.41,-70.22 32.13,-69.29 33.62,-69.58;70.56,-68.53 73.91,-69.51;81.42,-67.87 84.67,-66.41 89.07,-66.73;-135.79,-74.67 -124.34,-73.22 -116.65,-74.08 -109.93,-74.64 -105.36,-74.56 -105.83,-74.77;-69.3,-70.06 -71.33,-72.68 -71.42,-71.85 -75.1,-71.46 -71.79,-70.55 -70.34,-69.26 -69.34,-70.13;-49.2,-77.83 -44.59,-78.79 -44.14,-80.13 -59.04,-79.95 -49.28,-77.84 -48.24,-77.81;-58.13,-80.12 -63.25,-80.2 -58.32,-80.12;-163.64,-78.74 -161.2,-79.93 -163.62,-78.74;66.82,66.82 66.82,66.82`;
|
|
1308
2264
|
var POLYLINES = PACKED.split(";").map(
|
|
@@ -1452,9 +2408,45 @@ function hover(rendered, x, y) {
|
|
|
1452
2408
|
// src/client.ts
|
|
1453
2409
|
var term = tk.terminal;
|
|
1454
2410
|
var ScreenBuffer = tk.ScreenBuffer;
|
|
1455
|
-
var
|
|
1456
|
-
var
|
|
1457
|
-
var
|
|
2411
|
+
var CELL_TEXT_BYTES = 20;
|
|
2412
|
+
var ASCII_CHARS = Array.from({ length: 128 }, (_, i) => String.fromCharCode(i));
|
|
2413
|
+
var CellScreenBuffer = class extends ScreenBuffer {
|
|
2414
|
+
constructor(options) {
|
|
2415
|
+
super(options);
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
{
|
|
2419
|
+
const P = CellScreenBuffer.prototype, base = ScreenBuffer.prototype;
|
|
2420
|
+
P.CHAR_SIZE = CELL_TEXT_BYTES;
|
|
2421
|
+
P.ITEM_SIZE = base.ATTR_SIZE + CELL_TEXT_BYTES;
|
|
2422
|
+
const clear = Buffer.alloc(P.ITEM_SIZE);
|
|
2423
|
+
clear.writeInt32BE(base.CLEAR_ATTR, 0);
|
|
2424
|
+
clear.write(" ", base.ATTR_SIZE);
|
|
2425
|
+
P.CLEAR_BUFFER = clear;
|
|
2426
|
+
P.writeChar = function(buffer, char, at) {
|
|
2427
|
+
const start = at + this.ATTR_SIZE;
|
|
2428
|
+
buffer.fill(0, start, start + CELL_TEXT_BYTES);
|
|
2429
|
+
return buffer.write(char, start, CELL_TEXT_BYTES);
|
|
2430
|
+
};
|
|
2431
|
+
P.readChar = function(buffer, at) {
|
|
2432
|
+
const start = at + this.ATTR_SIZE;
|
|
2433
|
+
const b0 = buffer[start];
|
|
2434
|
+
if (b0 < 128 && buffer[start + 1] === 0) return ASCII_CHARS[b0];
|
|
2435
|
+
let end = start + 1;
|
|
2436
|
+
while (end < start + CELL_TEXT_BYTES && buffer[end] !== 0) end++;
|
|
2437
|
+
return buffer.toString("utf8", start, end);
|
|
2438
|
+
};
|
|
2439
|
+
const unflag = (mask) => function(buffer, at) {
|
|
2440
|
+
const attr = buffer.readInt32BE(at);
|
|
2441
|
+
if (!(attr & mask)) return;
|
|
2442
|
+
buffer.writeInt32BE(attr & ~mask, at);
|
|
2443
|
+
this.writeChar(buffer, " ", at);
|
|
2444
|
+
};
|
|
2445
|
+
P.removeLeadingFullWidth = unflag(base.LEADING_FULLWIDTH);
|
|
2446
|
+
P.removeTrailingFullWidth = unflag(base.TRAILING_FULLWIDTH);
|
|
2447
|
+
P.removeFullWidth = unflag(base.LEADING_FULLWIDTH | base.TRAILING_FULLWIDTH);
|
|
2448
|
+
}
|
|
2449
|
+
var CONF_DIR = accelr8Dir();
|
|
1458
2450
|
var DIM_PARTNER = {
|
|
1459
2451
|
39: 25,
|
|
1460
2452
|
213: 132,
|
|
@@ -1468,30 +2460,13 @@ var DIM_PARTNER = {
|
|
|
1468
2460
|
165: 90
|
|
1469
2461
|
};
|
|
1470
2462
|
var dimOf = (color) => DIM_PARTNER[color] ?? 243;
|
|
1471
|
-
function loadIdentity(nameOverride) {
|
|
1472
|
-
let saved = {};
|
|
1473
|
-
if (existsSync(ID_FILE)) {
|
|
1474
|
-
try {
|
|
1475
|
-
saved = JSON.parse(readFileSync(ID_FILE, "utf8"));
|
|
1476
|
-
} catch {
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
const id = saved.id || "u_" + Math.random().toString(36).slice(2, 10);
|
|
1480
|
-
const name = (nameOverride || saved.name || process.env.USER || "guest").slice(0, 16);
|
|
1481
|
-
let h = 0;
|
|
1482
|
-
for (const ch of id) h = h * 31 + ch.charCodeAt(0) >>> 0;
|
|
1483
|
-
const color = USER_COLORS[h % USER_COLORS.length];
|
|
1484
|
-
try {
|
|
1485
|
-
mkdirSync(CONF_DIR, { recursive: true });
|
|
1486
|
-
writeFileSync(ID_FILE, JSON.stringify({ id, name }));
|
|
1487
|
-
} catch {
|
|
1488
|
-
}
|
|
1489
|
-
return { id, name, color };
|
|
1490
|
-
}
|
|
1491
2463
|
var me = { id: "u_local", name: process.env.USER || "guest", color: 39 };
|
|
1492
2464
|
var SERVER = DEFAULT_SERVER;
|
|
1493
2465
|
var autoJoinCode;
|
|
1494
2466
|
var mySid = "";
|
|
2467
|
+
var pendingRename;
|
|
2468
|
+
var forceRefresh = false;
|
|
2469
|
+
var socketToken = "";
|
|
1495
2470
|
var myAccounts = [];
|
|
1496
2471
|
var runningExec = /* @__PURE__ */ new Map();
|
|
1497
2472
|
var acctStatus = /* @__PURE__ */ new Map();
|
|
@@ -1501,29 +2476,43 @@ var overlayFocus = 0;
|
|
|
1501
2476
|
var updateInfo = null;
|
|
1502
2477
|
var task = null;
|
|
1503
2478
|
var cancelTask = null;
|
|
2479
|
+
var updateRunning = false;
|
|
1504
2480
|
function startUpdate() {
|
|
1505
2481
|
if (task && task.phase === "running") return;
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
2482
|
+
if (updateRunning) {
|
|
2483
|
+
state.status = "the last update is still stopping \xB7 try again in a moment";
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const t = { title: "updating accelr8", phase: "running", line: "starting npm\u2026", okLabel: "quit now \xB7 enter", onOk: () => shutdown() };
|
|
2487
|
+
task = t;
|
|
2488
|
+
updateRunning = true;
|
|
2489
|
+
import("./update-RGDNBI4T.js").then(({ runUpdate }) => {
|
|
2490
|
+
if (task !== t) {
|
|
2491
|
+
updateRunning = false;
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const cancel = runUpdate({
|
|
1509
2495
|
onLine: (l) => {
|
|
1510
|
-
if (task)
|
|
2496
|
+
if (task === t) t.line = l.slice(0, 120);
|
|
1511
2497
|
},
|
|
1512
2498
|
onDone: (ok, message) => {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
2499
|
+
updateRunning = false;
|
|
2500
|
+
if (task === t) {
|
|
2501
|
+
t.phase = "done";
|
|
2502
|
+
t.ok = ok;
|
|
2503
|
+
t.line = message;
|
|
1517
2504
|
}
|
|
1518
|
-
cancelTask = null;
|
|
2505
|
+
if (cancelTask === cancel) cancelTask = null;
|
|
1519
2506
|
if (ok) updateInfo = null;
|
|
1520
2507
|
}
|
|
1521
2508
|
});
|
|
2509
|
+
if (updateRunning) cancelTask = cancel;
|
|
1522
2510
|
}).catch(() => {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
2511
|
+
updateRunning = false;
|
|
2512
|
+
if (task === t) {
|
|
2513
|
+
t.phase = "done";
|
|
2514
|
+
t.ok = false;
|
|
2515
|
+
t.line = "could not load the updater \xB7 npm i -g accelr8@latest";
|
|
1527
2516
|
}
|
|
1528
2517
|
});
|
|
1529
2518
|
}
|
|
@@ -1559,7 +2548,7 @@ function startVoiceInstall() {
|
|
|
1559
2548
|
});
|
|
1560
2549
|
},
|
|
1561
2550
|
onOk: () => {
|
|
1562
|
-
|
|
2551
|
+
voice.resetTools();
|
|
1563
2552
|
task = null;
|
|
1564
2553
|
toggleVoice();
|
|
1565
2554
|
}
|
|
@@ -1569,134 +2558,29 @@ function startVoiceInstall() {
|
|
|
1569
2558
|
state.status = "could not load the installer";
|
|
1570
2559
|
});
|
|
1571
2560
|
}
|
|
1572
|
-
var
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
if (!mics.length) return void 0;
|
|
1588
|
-
const builtin = mics.find((m) => /macbook|built.?in|internal/i.test(m.name));
|
|
1589
|
-
if (builtin) return builtin.index;
|
|
1590
|
-
const notPhone = mics.find((m) => !/iphone|ipad|continuity|desk view/i.test(m.name));
|
|
1591
|
-
return notPhone?.index;
|
|
1592
|
-
}
|
|
1593
|
-
if (process.platform === "win32") {
|
|
1594
|
-
const mics = listMicDevices();
|
|
1595
|
-
return mics[0]?.index;
|
|
1596
|
-
}
|
|
1597
|
-
return void 0;
|
|
1598
|
-
}
|
|
1599
|
-
function startCapture() {
|
|
1600
|
-
if (!voiceCapture) return;
|
|
1601
|
-
voiceCapture.start({
|
|
1602
|
-
device: voiceDevice,
|
|
1603
|
-
onChunk: (b64) => {
|
|
1604
|
-
voiceRetries = 0;
|
|
1605
|
-
if (!voiceTransmit) return;
|
|
1606
|
-
voiceSent++;
|
|
1607
|
-
if (voiceSent === 1) vlog(Date.now(), "transmit", "first frame sent to server");
|
|
1608
|
-
else if (voiceSent % 125 === 0) vlog(Date.now(), "transmit", "still sending", { frames: voiceSent });
|
|
1609
|
-
sendMsg({ t: "voiceFrame", data: b64 });
|
|
1610
|
-
},
|
|
1611
|
-
onError: (m) => {
|
|
1612
|
-
if (!voiceTransmit) return;
|
|
1613
|
-
voiceRetries++;
|
|
1614
|
-
if (voiceRetries <= 6) {
|
|
1615
|
-
state.status = `mic dropped \xB7 reconnecting ${voiceRetries}\u2026`;
|
|
1616
|
-
setTimeout(() => {
|
|
1617
|
-
if (voiceTransmit && voiceCapture && !voiceCapture.active) startCapture();
|
|
1618
|
-
}, 200);
|
|
1619
|
-
} else {
|
|
1620
|
-
voiceTransmit = false;
|
|
1621
|
-
voiceRetries = 0;
|
|
1622
|
-
state.status = "mic keeps dropping \xB7 /voice mics to pick another \xB7 " + m.slice(0, 30);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
});
|
|
1626
|
-
}
|
|
1627
|
-
function toggleVoice() {
|
|
1628
|
-
if (voiceOn) return leaveVoice();
|
|
1629
|
-
if (!state.project) {
|
|
1630
|
-
state.status = "join a project first";
|
|
1631
|
-
return;
|
|
1632
|
-
}
|
|
1633
|
-
voiceTools = voiceTools ?? detectVoiceTools();
|
|
1634
|
-
if (!voiceTools.capture && !voiceTools.playback) {
|
|
1635
|
-
startVoiceInstall();
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
voicePlayback = new VoicePlayback(voiceTools);
|
|
1639
|
-
voiceCapture = new VoiceCapture(voiceTools);
|
|
1640
|
-
voiceOn = true;
|
|
1641
|
-
voiceReady = false;
|
|
1642
|
-
voiceTransmit = false;
|
|
1643
|
-
voiceSent = 0;
|
|
1644
|
-
voiceHeardWarned = false;
|
|
1645
|
-
voiceRecv.clear();
|
|
1646
|
-
if (voiceDevice === void 0 && voiceTools.capture) voiceDevice = autoPickMic();
|
|
1647
|
-
vlogSession(Date.now(), {
|
|
1648
|
-
platform: process.platform,
|
|
1649
|
-
version: PROTO,
|
|
1650
|
-
name: me.name,
|
|
1651
|
-
ffmpeg: voiceTools.ffmpegPath ?? "MISSING",
|
|
1652
|
-
ffplay: voiceTools.ffplayPath ?? "MISSING",
|
|
1653
|
-
capture: voiceTools.capture,
|
|
1654
|
-
playback: voiceTools.playback,
|
|
1655
|
-
mic: voiceDevice ?? "default"
|
|
1656
|
-
});
|
|
1657
|
-
sendMsg({ t: "voiceJoin" });
|
|
1658
|
-
state.status = "voice \xB7 connecting\u2026";
|
|
2561
|
+
var voice = new VoiceSession({
|
|
2562
|
+
send: (m) => sendMsg(m),
|
|
2563
|
+
status: (t) => {
|
|
2564
|
+
state.status = t;
|
|
2565
|
+
},
|
|
2566
|
+
currentProject: () => state.project ? { id: state.project.id, name: state.project.name } : null,
|
|
2567
|
+
setPeers: (p) => {
|
|
2568
|
+
state.voicePeers = p;
|
|
2569
|
+
},
|
|
2570
|
+
offerInstall: () => startVoiceInstall(),
|
|
2571
|
+
meName: () => me.name,
|
|
2572
|
+
version: PROTO
|
|
2573
|
+
}, { device: process.env.ACCELR8_MIC || void 0 });
|
|
2574
|
+
function toggleVoice(here = false) {
|
|
2575
|
+
voice.toggle({ here });
|
|
1659
2576
|
}
|
|
1660
2577
|
function leaveVoice() {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
voiceCapture = null;
|
|
1666
|
-
voicePlayback = null;
|
|
1667
|
-
voiceOn = false;
|
|
1668
|
-
voiceReady = false;
|
|
1669
|
-
voiceTransmit = false;
|
|
1670
|
-
state.voicePeers = [];
|
|
1671
|
-
sendMsg({ t: "voiceLeave" });
|
|
1672
|
-
state.status = "voice off";
|
|
1673
|
-
}
|
|
1674
|
-
function toggleTalk() {
|
|
1675
|
-
if (!voiceOn || !voiceCapture) return;
|
|
1676
|
-
if (!voiceTools?.capture) {
|
|
1677
|
-
state.status = "no mic \xB7 install ffmpeg to talk";
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
if (voiceTransmit) {
|
|
1681
|
-
voiceTransmit = false;
|
|
1682
|
-
voiceRetries = 0;
|
|
1683
|
-
voiceCapture.stop();
|
|
1684
|
-
state.status = "muted \xB7 /talk (or ctrl-g) to speak";
|
|
1685
|
-
} else {
|
|
1686
|
-
if (!voiceReady) {
|
|
1687
|
-
const now = Date.now();
|
|
1688
|
-
if (now - voiceRejoinAt > 1e3) {
|
|
1689
|
-
voiceRejoinAt = now;
|
|
1690
|
-
sendMsg({ t: "voiceJoin" });
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
voiceTransmit = true;
|
|
1694
|
-
voiceRetries = 0;
|
|
1695
|
-
voiceHeardWarned = false;
|
|
1696
|
-
state.status = "\u25CF live \xB7 /mute (or ctrl-g) to stop";
|
|
1697
|
-
startCapture();
|
|
1698
|
-
}
|
|
2578
|
+
voice.leave();
|
|
2579
|
+
}
|
|
2580
|
+
function toggleTalk(on) {
|
|
2581
|
+
voice.talk(on);
|
|
1699
2582
|
}
|
|
2583
|
+
var micChoices = [];
|
|
1700
2584
|
function openHelp() {
|
|
1701
2585
|
overlay = "help";
|
|
1702
2586
|
overlayFocus = 0;
|
|
@@ -1711,7 +2595,7 @@ function openRadio() {
|
|
|
1711
2595
|
}
|
|
1712
2596
|
function goHome() {
|
|
1713
2597
|
if (state.project) {
|
|
1714
|
-
if (
|
|
2598
|
+
if (voice.active) leaveVoice();
|
|
1715
2599
|
overlay = null;
|
|
1716
2600
|
state.screen = "home";
|
|
1717
2601
|
sendMsg({ t: "tab", tab: "home" });
|
|
@@ -1737,6 +2621,13 @@ var chatLog = new ChatLog();
|
|
|
1737
2621
|
var editor = new LineEditor();
|
|
1738
2622
|
var popup = new MentionPopup();
|
|
1739
2623
|
var orbEngine = new OrbEngine();
|
|
2624
|
+
chatLog.noticer = (e) => agentNotice(e, (handle) => {
|
|
2625
|
+
const ag = state.agents.find((a) => a.handle === handle);
|
|
2626
|
+
if (!ag) return null;
|
|
2627
|
+
const you = ag.ownerId === me.id;
|
|
2628
|
+
const cli = ag.provider === "anthropic" && !(you && myAccounts.some((a) => a.handle === handle && a.auth.kind === "api_key"));
|
|
2629
|
+
return { name: memberOf(ag.ownerId)?.name ?? ag.ownerName, you, cli };
|
|
2630
|
+
});
|
|
1740
2631
|
var unread = { t0: 0, t2: 0 };
|
|
1741
2632
|
var ghosts = /* @__PURE__ */ new Map();
|
|
1742
2633
|
var cursors = /* @__PURE__ */ new Map();
|
|
@@ -1832,6 +2723,7 @@ function chatVisible() {
|
|
|
1832
2723
|
}
|
|
1833
2724
|
var BOOT_MIN_W = 60;
|
|
1834
2725
|
var BOOT_MIN_H = 16;
|
|
2726
|
+
var BOOT_ACCENT = 39;
|
|
1835
2727
|
var boot = null;
|
|
1836
2728
|
var bootStart = 0;
|
|
1837
2729
|
var bootWasFull = false;
|
|
@@ -1930,6 +2822,32 @@ var syncFrom = 0;
|
|
|
1930
2822
|
var syncBuffer = [];
|
|
1931
2823
|
function connect() {
|
|
1932
2824
|
state.status = "connecting to " + SERVER + " ...";
|
|
2825
|
+
const force = forceRefresh;
|
|
2826
|
+
forceRefresh = false;
|
|
2827
|
+
getAccessToken(SERVER, { force }).then((tok) => {
|
|
2828
|
+
if (!tok) {
|
|
2829
|
+
signedOut();
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
openSocket(tok.token);
|
|
2833
|
+
}, () => {
|
|
2834
|
+
if (!reconnectTimer) reconnectTimer = setTimeout(() => {
|
|
2835
|
+
reconnectTimer = null;
|
|
2836
|
+
connect();
|
|
2837
|
+
}, 2e3);
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
function authLost() {
|
|
2841
|
+
const saved = loadCredentials(SERVER);
|
|
2842
|
+
if (saved && socketToken && saved.accessToken !== socketToken) return;
|
|
2843
|
+
signedOut();
|
|
2844
|
+
}
|
|
2845
|
+
function signedOut() {
|
|
2846
|
+
deleteCredentials(SERVER, void 0, (saved) => !socketToken || saved.accessToken === socketToken);
|
|
2847
|
+
shutdownWithMessage(`You are signed out of ${SERVER}. Sign in again with: accelr8 login`);
|
|
2848
|
+
}
|
|
2849
|
+
function openSocket(token) {
|
|
2850
|
+
socketToken = token;
|
|
1933
2851
|
try {
|
|
1934
2852
|
ws = new WebSocket(SERVER);
|
|
1935
2853
|
} catch {
|
|
@@ -1940,7 +2858,14 @@ function connect() {
|
|
|
1940
2858
|
state.connected = true;
|
|
1941
2859
|
state.status = "";
|
|
1942
2860
|
lastPong = Date.now();
|
|
1943
|
-
sendMsg({
|
|
2861
|
+
sendMsg({
|
|
2862
|
+
t: "hello",
|
|
2863
|
+
v: PROTO,
|
|
2864
|
+
token,
|
|
2865
|
+
...pendingRename ? { name: pendingRename } : {},
|
|
2866
|
+
tz: localTz(),
|
|
2867
|
+
executes: myAccounts.map((a) => ({ handle: a.handle, provider: a.provider, model: a.model }))
|
|
2868
|
+
});
|
|
1944
2869
|
if (autoJoinCode && !state.project) sendMsg({ t: "joinProject", code: autoJoinCode });
|
|
1945
2870
|
else if (state.project) {
|
|
1946
2871
|
rejoining = true;
|
|
@@ -1976,9 +2901,19 @@ function sendMsg(msg) {
|
|
|
1976
2901
|
}
|
|
1977
2902
|
function handleServer(msg) {
|
|
1978
2903
|
if (!msg || typeof msg !== "object" || typeof msg.t !== "string") return;
|
|
2904
|
+
if (msg.t !== "voice" && voice.handle(msg)) return;
|
|
1979
2905
|
switch (msg.t) {
|
|
1980
2906
|
case "welcome":
|
|
1981
2907
|
mySid = msg.sid;
|
|
2908
|
+
if (msg.you && typeof msg.you.id === "string" && typeof msg.you.name === "string") {
|
|
2909
|
+
me = { id: msg.you.id, name: msg.you.name, color: Number(msg.you.color) || me.color };
|
|
2910
|
+
pendingRename = void 0;
|
|
2911
|
+
}
|
|
2912
|
+
break;
|
|
2913
|
+
case "notice":
|
|
2914
|
+
if (typeof msg.text !== "string") break;
|
|
2915
|
+
state.status = msg.text;
|
|
2916
|
+
if (state.project) chatLog.push({ seq: null, kind: "system", authorId: "system", authorName: "system", color: 240, text: msg.text, ts: Date.now() }, mentionEntities());
|
|
1982
2917
|
break;
|
|
1983
2918
|
case "projects":
|
|
1984
2919
|
state.projects = Array.isArray(msg.list) ? msg.list : [];
|
|
@@ -1992,6 +2927,7 @@ function handleServer(msg) {
|
|
|
1992
2927
|
state.sessions = Array.isArray(msg.sessions) ? msg.sessions : [];
|
|
1993
2928
|
state.agents = Array.isArray(msg.agents) ? msg.agents : [];
|
|
1994
2929
|
state.secrets = Array.isArray(msg.secrets) ? msg.secrets : [];
|
|
2930
|
+
chatLog.dropCaches();
|
|
1995
2931
|
cursors.clear();
|
|
1996
2932
|
ghosts.clear();
|
|
1997
2933
|
if (rejoining && sameProject) {
|
|
@@ -2011,13 +2947,8 @@ function handleServer(msg) {
|
|
|
2011
2947
|
}
|
|
2012
2948
|
rejoining = false;
|
|
2013
2949
|
attachMyAgents();
|
|
2014
|
-
if (
|
|
2015
|
-
|
|
2016
|
-
vlog(Date.now(), "transport", "re-sending voiceJoin after (re)connect");
|
|
2017
|
-
sendMsg({ t: "voiceJoin" });
|
|
2018
|
-
} else if (Array.isArray(msg.voicePeers)) {
|
|
2019
|
-
state.voicePeers = msg.voicePeers;
|
|
2020
|
-
}
|
|
2950
|
+
if (Array.isArray(msg.voicePeers)) state.voicePeers = msg.voicePeers;
|
|
2951
|
+
voice.onJoined(msg.project.id);
|
|
2021
2952
|
break;
|
|
2022
2953
|
}
|
|
2023
2954
|
case "agentToken":
|
|
@@ -2055,10 +2986,22 @@ function handleServer(msg) {
|
|
|
2055
2986
|
}
|
|
2056
2987
|
break;
|
|
2057
2988
|
}
|
|
2058
|
-
case "agentEnd":
|
|
2059
|
-
chatLog.
|
|
2060
|
-
if (
|
|
2989
|
+
case "agentEnd": {
|
|
2990
|
+
const dropped = chatLog.dropEmptyStream((e) => e.invId === msg.invId);
|
|
2991
|
+
if (!dropped) chatLog.finalize((e) => e.invId === msg.invId && !!e.streaming, { partial: msg.status !== "done" });
|
|
2992
|
+
else if (msg.status === "done") {
|
|
2993
|
+
chatLog.push({
|
|
2994
|
+
seq: null,
|
|
2995
|
+
kind: "system",
|
|
2996
|
+
authorId: "system",
|
|
2997
|
+
authorName: "system",
|
|
2998
|
+
color: 245,
|
|
2999
|
+
text: `\u2726${dropped.authorName} ended without a reply`,
|
|
3000
|
+
ts: Date.now()
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
2061
3003
|
break;
|
|
3004
|
+
}
|
|
2062
3005
|
case "joinError":
|
|
2063
3006
|
rejoining = false;
|
|
2064
3007
|
syncing = false;
|
|
@@ -2074,6 +3017,7 @@ function handleServer(msg) {
|
|
|
2074
3017
|
break;
|
|
2075
3018
|
case "members":
|
|
2076
3019
|
state.members = Array.isArray(msg.members) ? msg.members : [];
|
|
3020
|
+
chatLog.dropCaches();
|
|
2077
3021
|
break;
|
|
2078
3022
|
case "cursor": {
|
|
2079
3023
|
if (msg.sid === mySid) break;
|
|
@@ -2144,6 +3088,7 @@ function handleServer(msg) {
|
|
|
2144
3088
|
}
|
|
2145
3089
|
case "agents":
|
|
2146
3090
|
state.agents = Array.isArray(msg.agents) ? msg.agents : [];
|
|
3091
|
+
chatLog.dropCaches();
|
|
2147
3092
|
break;
|
|
2148
3093
|
case "secrets":
|
|
2149
3094
|
state.secrets = Array.isArray(msg.list) ? msg.list : [];
|
|
@@ -2151,49 +3096,6 @@ function handleServer(msg) {
|
|
|
2151
3096
|
case "voice":
|
|
2152
3097
|
state.voicePeers = Array.isArray(msg.peers) ? msg.peers : [];
|
|
2153
3098
|
break;
|
|
2154
|
-
case "voiceJoined": {
|
|
2155
|
-
const first = !voiceReady;
|
|
2156
|
-
voiceReady = true;
|
|
2157
|
-
state.voicePeers = Array.isArray(msg.peers) ? msg.peers : [];
|
|
2158
|
-
vlog(Date.now(), "transport", "voiceJoined ACK", { peers: state.voicePeers.length });
|
|
2159
|
-
if (first) {
|
|
2160
|
-
state.status = voiceTools?.capture ? "voice on \xB7 /talk (or ctrl-g) to speak" : "voice on \xB7 listen only (no mic found)";
|
|
2161
|
-
}
|
|
2162
|
-
break;
|
|
2163
|
-
}
|
|
2164
|
-
case "voiceDrop": {
|
|
2165
|
-
vlog(Date.now(), "transport", "voiceDrop", { reason: msg.reason });
|
|
2166
|
-
if (msg.reason === "notInVoice") {
|
|
2167
|
-
voiceReady = false;
|
|
2168
|
-
const now = Date.now();
|
|
2169
|
-
if (now - voiceRejoinAt > 1e3) {
|
|
2170
|
-
voiceRejoinAt = now;
|
|
2171
|
-
vlog(now, "transport", "auto re-join after notInVoice drop");
|
|
2172
|
-
sendMsg({ t: "voiceJoin" });
|
|
2173
|
-
}
|
|
2174
|
-
} else if (msg.reason === "rateCapped") {
|
|
2175
|
-
state.status = "voice \xB7 sending too fast (throttled)";
|
|
2176
|
-
}
|
|
2177
|
-
break;
|
|
2178
|
-
}
|
|
2179
|
-
case "voiceStats": {
|
|
2180
|
-
vlog(Date.now(), "transport", "voiceStats", { heardFromYou: msg.heardFromYou });
|
|
2181
|
-
if (voiceTransmit && msg.heardFromYou === 0 && !voiceHeardWarned) {
|
|
2182
|
-
voiceHeardWarned = true;
|
|
2183
|
-
state.status = "mic hot but server hears nothing \xB7 /voice mics to switch";
|
|
2184
|
-
} else if (msg.heardFromYou > 0) {
|
|
2185
|
-
voiceHeardWarned = false;
|
|
2186
|
-
}
|
|
2187
|
-
break;
|
|
2188
|
-
}
|
|
2189
|
-
case "voiceFrame":
|
|
2190
|
-
if (voiceOn) {
|
|
2191
|
-
const n = (voiceRecv.get(msg.sid) ?? 0) + 1;
|
|
2192
|
-
voiceRecv.set(msg.sid, n);
|
|
2193
|
-
if (n === 1) vlog(Date.now(), "receive", "first frame received from a peer", { sid: msg.sid.slice(0, 8) });
|
|
2194
|
-
voicePlayback?.feed(msg.sid, msg.data);
|
|
2195
|
-
}
|
|
2196
|
-
break;
|
|
2197
3099
|
case "error": {
|
|
2198
3100
|
if (msg.code === "chatRate" || msg.code === "msgTooLarge") {
|
|
2199
3101
|
const failed = msg.ref ? chatLog.failOptimistic(msg.ref) : null;
|
|
@@ -2201,9 +3103,24 @@ function handleServer(msg) {
|
|
|
2201
3103
|
state.status = msg.code === "chatRate" ? "slow down" : "message too large";
|
|
2202
3104
|
} else if (msg.code === "protoVersion") {
|
|
2203
3105
|
shutdownWithMessage("this client is outdated. update with: npm i -g accelr8");
|
|
3106
|
+
} else if (msg.code === "authExpired") {
|
|
3107
|
+
forceRefresh = true;
|
|
3108
|
+
} else if (msg.code === "authInvalid" || msg.code === "authRequired" || msg.code === "authRevoked") {
|
|
3109
|
+
authLost();
|
|
3110
|
+
} else if (msg.code === "notOwner") {
|
|
3111
|
+
state.status = msg.message;
|
|
2204
3112
|
}
|
|
2205
3113
|
break;
|
|
2206
3114
|
}
|
|
3115
|
+
case "removed":
|
|
3116
|
+
if (state.project?.id === msg.projectId) {
|
|
3117
|
+
if (voice.active) leaveVoice();
|
|
3118
|
+
overlay = null;
|
|
3119
|
+
state.project = null;
|
|
3120
|
+
state.screen = "home";
|
|
3121
|
+
}
|
|
3122
|
+
state.status = `${msg.by} removed you from ${msg.name}.`;
|
|
3123
|
+
break;
|
|
2207
3124
|
}
|
|
2208
3125
|
}
|
|
2209
3126
|
var grid = {};
|
|
@@ -2211,7 +3128,7 @@ var screen;
|
|
|
2211
3128
|
function makeScreen() {
|
|
2212
3129
|
const width = Math.max(20, term.width || process.stdout.columns || 120);
|
|
2213
3130
|
const height = Math.max(6, term.height || process.stdout.rows || 40);
|
|
2214
|
-
screen = new
|
|
3131
|
+
screen = new CellScreenBuffer({ dst: term, width, height });
|
|
2215
3132
|
chatLog.dropCaches();
|
|
2216
3133
|
if (cine) {
|
|
2217
3134
|
} else if (boot && (width < BOOT_MIN_W || height < BOOT_MIN_H)) {
|
|
@@ -2225,7 +3142,7 @@ function makeScreen() {
|
|
|
2225
3142
|
}
|
|
2226
3143
|
}
|
|
2227
3144
|
function __setHeadlessScreen(width, height) {
|
|
2228
|
-
screen = new
|
|
3145
|
+
screen = new CellScreenBuffer({ width, height });
|
|
2229
3146
|
chatLog.dropCaches();
|
|
2230
3147
|
}
|
|
2231
3148
|
function __dump() {
|
|
@@ -2237,12 +3154,26 @@ function __dumpCells() {
|
|
|
2237
3154
|
const row = [];
|
|
2238
3155
|
for (let x = 0; x < screen.width; x++) {
|
|
2239
3156
|
const c = screen.get({ x, y });
|
|
2240
|
-
|
|
3157
|
+
const at = (y * screen.width + x) * screen.ITEM_SIZE;
|
|
3158
|
+
row.push({
|
|
3159
|
+
ch: c.char,
|
|
3160
|
+
fg: c.attr?.color,
|
|
3161
|
+
bg: c.attr?.bgColor,
|
|
3162
|
+
bold: c.attr?.bold,
|
|
3163
|
+
wide: screen.hasLeadingFullWidth(screen.buffer, at),
|
|
3164
|
+
tail: screen.hasTrailingFullWidth(screen.buffer, at)
|
|
3165
|
+
});
|
|
2241
3166
|
}
|
|
2242
3167
|
rows.push(row);
|
|
2243
3168
|
}
|
|
2244
3169
|
return rows;
|
|
2245
3170
|
}
|
|
3171
|
+
function __server(msg) {
|
|
3172
|
+
handleServer(msg);
|
|
3173
|
+
}
|
|
3174
|
+
function __puts(x, y, text) {
|
|
3175
|
+
puts(x, y, text, { color: C.text, bgColor: C.bg });
|
|
3176
|
+
}
|
|
2246
3177
|
function __setMySid(sid) {
|
|
2247
3178
|
mySid = sid;
|
|
2248
3179
|
}
|
|
@@ -2267,20 +3198,82 @@ var W = () => screen.width;
|
|
|
2267
3198
|
function puts(x, y, str, attr) {
|
|
2268
3199
|
if (y < 0 || y >= H()) return;
|
|
2269
3200
|
const a = attr && attr.bgColor !== void 0 ? attr : { ...attr, bgColor: C.bg };
|
|
2270
|
-
const clean = String(str).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
2271
|
-
screen.put({ x, y, attr: a, dx: 1, dy: 0, wrap: false }, clean);
|
|
3201
|
+
const clean = String(str).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
3202
|
+
if (isPlainText(clean)) screen.put({ x, y, attr: a, dx: 1, dy: 0, wrap: false }, clean);
|
|
3203
|
+
else putClusters(x, y, clean, a);
|
|
2272
3204
|
}
|
|
2273
3205
|
function puts2(x, y, str, attr) {
|
|
2274
3206
|
puts(x, y, str, attr);
|
|
2275
|
-
return x + String(str)
|
|
3207
|
+
return x + stringWidth(String(str));
|
|
3208
|
+
}
|
|
3209
|
+
var tkWidth = tk.stringWidth;
|
|
3210
|
+
var plainBmp = new Uint8Array(65536);
|
|
3211
|
+
function isPlainCp(cp) {
|
|
3212
|
+
let v = plainBmp[cp];
|
|
3213
|
+
if (!v) {
|
|
3214
|
+
v = charWidth(cp) === 1 && tkWidth(String.fromCharCode(cp)) === 1 ? 1 : 2;
|
|
3215
|
+
plainBmp[cp] = v;
|
|
3216
|
+
}
|
|
3217
|
+
return v === 1;
|
|
3218
|
+
}
|
|
3219
|
+
function isPlainText(s) {
|
|
3220
|
+
for (let i = 0; i < s.length; i++) {
|
|
3221
|
+
const c = s.charCodeAt(i);
|
|
3222
|
+
if (c < 768) continue;
|
|
3223
|
+
if (c >= 55296 && c <= 57343) return false;
|
|
3224
|
+
if (!isPlainCp(c)) return false;
|
|
3225
|
+
}
|
|
3226
|
+
return true;
|
|
3227
|
+
}
|
|
3228
|
+
function putClusters(x, y, text, attr, maxCells = Infinity) {
|
|
3229
|
+
let col = 0;
|
|
3230
|
+
for (const g of clusters(text)) {
|
|
3231
|
+
const w = stringWidth(g);
|
|
3232
|
+
if (w === 0) continue;
|
|
3233
|
+
if (col + w > maxCells) break;
|
|
3234
|
+
putCluster(x + col, y, g, w, attr);
|
|
3235
|
+
col += w;
|
|
3236
|
+
}
|
|
3237
|
+
return col;
|
|
3238
|
+
}
|
|
3239
|
+
function putCluster(x, y, g, w, attr) {
|
|
3240
|
+
const cps = [...g];
|
|
3241
|
+
if (w === 2 && cps.length === 2 && cps.every((c) => {
|
|
3242
|
+
const p = c.codePointAt(0);
|
|
3243
|
+
return p >= 127462 && p <= 127487;
|
|
3244
|
+
})) {
|
|
3245
|
+
putPiece(x, y, cps[0], 1, attr);
|
|
3246
|
+
putPiece(x + 1, y, cps[1], 1, attr);
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
let col = x;
|
|
3250
|
+
for (const p of cellPieces(g)) {
|
|
3251
|
+
putPiece(col, y, p.emoji ? representative(p.text, p.w) : p.text, p.w, attr);
|
|
3252
|
+
col += p.w;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
var WIDE_SLOT = "\u3013";
|
|
3256
|
+
function putPiece(x, y, text, w, attr) {
|
|
3257
|
+
screen.put({ x, y, attr, dx: 1, dy: 0, wrap: false }, w === 2 ? WIDE_SLOT : " ");
|
|
3258
|
+
if (x >= 0 && x + w <= screen.width) screen.writeChar(screen.buffer, text, (y * screen.width + x) * screen.ITEM_SIZE);
|
|
3259
|
+
}
|
|
3260
|
+
function representative(g, w) {
|
|
3261
|
+
const cps = [...g];
|
|
3262
|
+
return cps.find((c) => charWidth(c.codePointAt(0)) === w) ?? cps.find((c) => charWidth(c.codePointAt(0)) > 0) ?? cps[0];
|
|
3263
|
+
}
|
|
3264
|
+
var fit = (s, cells) => truncateWidth(s, cells, "\u2026");
|
|
3265
|
+
function firstFit(variants, cells) {
|
|
3266
|
+
for (const v of variants) if (stringWidth(v) <= cells) return v;
|
|
3267
|
+
return fit(variants[variants.length - 1], cells);
|
|
2276
3268
|
}
|
|
2277
3269
|
var clickables = [];
|
|
2278
3270
|
function button(x, y, label, attr, action) {
|
|
2279
3271
|
const focused = overlay !== null && clickables.length === overlayFocus;
|
|
2280
3272
|
puts(x, y, label, focused ? { ...attr, bgColor: C.popupSel, color: C.selFg, bold: true } : attr);
|
|
2281
3273
|
if (focused) puts(x - 1, y, "\u276F", { color: C.accent, bold: true });
|
|
2282
|
-
|
|
2283
|
-
|
|
3274
|
+
const lw = stringWidth(label);
|
|
3275
|
+
clickables.push({ x0: x, y0: y, x1: x + lw - 1, y1: y, action });
|
|
3276
|
+
return x + lw;
|
|
2284
3277
|
}
|
|
2285
3278
|
var BASE = {
|
|
2286
3279
|
bg: 233,
|
|
@@ -2382,6 +3375,12 @@ function __curTheme() {
|
|
|
2382
3375
|
function __setUpdate(latest, current = "3.0.0") {
|
|
2383
3376
|
updateInfo = latest ? { latest, current } : null;
|
|
2384
3377
|
}
|
|
3378
|
+
function __startUpdate() {
|
|
3379
|
+
startUpdate();
|
|
3380
|
+
}
|
|
3381
|
+
function __updateRunning() {
|
|
3382
|
+
return updateRunning;
|
|
3383
|
+
}
|
|
2385
3384
|
function __taskDemo(phase, ok = true) {
|
|
2386
3385
|
task = { title: "enable voice", phase, ok, line: phase === "confirm" ? "voice needs ffmpeg \xB7 brew install ffmpeg \xB7 press enter" : phase === "running" ? "installing ffmpeg\u2026" : ok ? "ffmpeg ready \xB7 voice is on" : "brew exited 1 \xB7 brew install ffmpeg", okLabel: "start voice \xB7 enter" };
|
|
2387
3386
|
}
|
|
@@ -2396,6 +3395,34 @@ function __setNav(s) {
|
|
|
2396
3395
|
function __accent() {
|
|
2397
3396
|
return C.accent;
|
|
2398
3397
|
}
|
|
3398
|
+
var voiceTest;
|
|
3399
|
+
function __setVoice(on, ready = on, transmit = false) {
|
|
3400
|
+
voiceTest = !on ? null : { label: !ready ? " \u25CC connecting\u2026" : transmit ? " \u25CF live \xB7 /mute" : ` \u25CB voice ${state.voicePeers.length} \xB7 /talk`, live: ready && transmit };
|
|
3401
|
+
}
|
|
3402
|
+
function __bootAt(tMs, short = false, rng) {
|
|
3403
|
+
cine = null;
|
|
3404
|
+
boot = new BootAnim(W(), H(), { short, rng });
|
|
3405
|
+
bootStart = Date.now() - tMs;
|
|
3406
|
+
}
|
|
3407
|
+
function __bootOff() {
|
|
3408
|
+
boot = null;
|
|
3409
|
+
cine = null;
|
|
3410
|
+
}
|
|
3411
|
+
function __key(name) {
|
|
3412
|
+
onKey(name);
|
|
3413
|
+
}
|
|
3414
|
+
function __mouse(name, x, y, extra = {}) {
|
|
3415
|
+
onMouse(name, { x: x + 1, y: y + 1, ...extra });
|
|
3416
|
+
}
|
|
3417
|
+
function __grid() {
|
|
3418
|
+
return grid;
|
|
3419
|
+
}
|
|
3420
|
+
function __canvasPan() {
|
|
3421
|
+
return { ...canvasPan };
|
|
3422
|
+
}
|
|
3423
|
+
function __canvasView() {
|
|
3424
|
+
return { ...canvasView };
|
|
3425
|
+
}
|
|
2399
3426
|
function rungFor(w, h) {
|
|
2400
3427
|
if (h <= 4) return "micro";
|
|
2401
3428
|
if (w < 12) return "micro";
|
|
@@ -2452,9 +3479,14 @@ function render() {
|
|
|
2452
3479
|
const f = boot.frame(Date.now() - bootStart);
|
|
2453
3480
|
if (f === "done") finishBoot();
|
|
2454
3481
|
else {
|
|
3482
|
+
const seat = homeLogoSeat(w, h);
|
|
3483
|
+
const dx = seat ? seat.left - boot.logoLeft : 0;
|
|
3484
|
+
const dy = seat ? seat.top - boot.logoTop : 0;
|
|
2455
3485
|
for (const { x, y, cell } of f.cells) {
|
|
2456
|
-
if (x
|
|
2457
|
-
|
|
3486
|
+
if (x < 0 || x >= w || y < 0 || y >= h) continue;
|
|
3487
|
+
const sx = (x + dx + w) % w, sy = (y + dy + h) % h;
|
|
3488
|
+
const color = cell.color === BOOT_ACCENT ? C.accent : cell.color;
|
|
3489
|
+
screen.put({ x: sx, y: sy, attr: { color, bold: cell.bold, bgColor: cell.bgColor ?? C.bg }, dx: 1, dy: 0, wrap: false }, cell.ch);
|
|
2458
3490
|
}
|
|
2459
3491
|
if (screen.dst) screen.draw({ delta: true });
|
|
2460
3492
|
return;
|
|
@@ -2532,15 +3564,17 @@ function orbInfo() {
|
|
|
2532
3564
|
function blitOrbLine(x0, y, line) {
|
|
2533
3565
|
if (!line || y < 0 || y >= H()) return;
|
|
2534
3566
|
puts(x0, y, line.text, { color: C.dim, bgColor: C.bg });
|
|
3567
|
+
const chars = [...line.text];
|
|
3568
|
+
const col = columnsOf(chars);
|
|
2535
3569
|
for (const sp of line.spans) {
|
|
2536
|
-
const seg =
|
|
2537
|
-
if (seg) puts(x0 + sp.start, y, seg, { color: readableFg(sp.color, C.bg, 3.2), bold: sp.bold, bgColor: C.bg });
|
|
3570
|
+
const seg = chars.slice(sp.start, sp.end).join("");
|
|
3571
|
+
if (seg) puts(x0 + col(sp.start), y, seg, { color: readableFg(sp.color, C.bg, 3.2), bold: sp.bold, bgColor: C.bg });
|
|
2538
3572
|
}
|
|
2539
3573
|
}
|
|
2540
3574
|
function renderMicro(w, h) {
|
|
2541
3575
|
const line = microLine(w, orbEngine, frame, orbInfo());
|
|
2542
3576
|
const y = Math.floor((h - 1) / 2);
|
|
2543
|
-
const x = Math.max(0, Math.floor((w -
|
|
3577
|
+
const x = Math.max(0, Math.floor((w - stringWidth(line.text)) / 2));
|
|
2544
3578
|
blitOrbLine(x, y, line);
|
|
2545
3579
|
}
|
|
2546
3580
|
function renderOrb(w, h) {
|
|
@@ -2554,14 +3588,12 @@ function renderTicker(w, h) {
|
|
|
2554
3588
|
}
|
|
2555
3589
|
puts(0, 0, " ".repeat(w), { bgColor: C.panel });
|
|
2556
3590
|
let x = 1;
|
|
2557
|
-
const projName = state.project.name
|
|
2558
|
-
|
|
2559
|
-
x += projName.length + 3;
|
|
3591
|
+
const projName = fit(state.project.name, 18);
|
|
3592
|
+
x = puts2(x, 0, projName, { bgColor: C.panel, color: C.bright, bold: true }) + 3;
|
|
2560
3593
|
if (w >= 80) {
|
|
2561
3594
|
for (const m of state.members.slice(0, 5)) {
|
|
2562
3595
|
const on = state.sessions.some((s) => s.userId === m.id);
|
|
2563
|
-
|
|
2564
|
-
x += m.name.slice(0, 8).length + 3;
|
|
3596
|
+
x = puts2(x, 0, (on ? "\u25CF " : "\u25CB ") + fit(m.name, 8), { bgColor: C.panel, color: on ? readableFg(m.color, C.panel) : C.dim }) + 1;
|
|
2565
3597
|
}
|
|
2566
3598
|
} else {
|
|
2567
3599
|
puts(x, 0, `\xB7 ${onlineUserIds().size} on`, { bgColor: C.panel, color: C.dim });
|
|
@@ -2587,13 +3619,7 @@ function renderTicker(w, h) {
|
|
|
2587
3619
|
drawSpannedLine(1, threadTop + i, line.text, line.spans, w - 2, line.centered);
|
|
2588
3620
|
void gutters;
|
|
2589
3621
|
}
|
|
2590
|
-
if (hasGhost)
|
|
2591
|
-
const g = activeGhosts()[0];
|
|
2592
|
-
const m = memberOf(g.userId);
|
|
2593
|
-
puts(1, ghostRow, m?.name ?? "?", { color: readableFg(m?.color ?? C.dim), bold: true });
|
|
2594
|
-
puts(1 + (m?.name ?? "?").length, ghostRow, " \u270E ", { color: C.sys });
|
|
2595
|
-
puts(4 + (m?.name ?? "?").length, ghostRow, [...g.text].slice(0, w - (m?.name ?? "?").length - 6).join(""), { color: dimOf(m?.color ?? 245), italic: true });
|
|
2596
|
-
}
|
|
3622
|
+
if (hasGhost) drawGhostRow(activeGhosts()[0], ghostRow, w);
|
|
2597
3623
|
renderInputLine(inputRow, w);
|
|
2598
3624
|
if (hasStatus) {
|
|
2599
3625
|
puts(0, h - 1, " ".repeat(w), { bgColor: C.panel });
|
|
@@ -2611,7 +3637,7 @@ function renderSidecarRung(w, h) {
|
|
|
2611
3637
|
return;
|
|
2612
3638
|
}
|
|
2613
3639
|
puts(0, 0, " ".repeat(w), { bgColor: C.panel });
|
|
2614
|
-
puts(1, 0, state.project.name
|
|
3640
|
+
puts(1, 0, fit(state.project.name, w - 12), { bgColor: C.panel, color: C.bright, bold: true });
|
|
2615
3641
|
const right = `${onlineUserIds().size} on ${state.connected ? "\u25CF" : "\u25CB"}`;
|
|
2616
3642
|
puts(w - right.length - 1, 0, right, { bgColor: C.panel, color: state.connected ? C.good : C.warn });
|
|
2617
3643
|
const inputRow = h - 2;
|
|
@@ -2637,15 +3663,9 @@ function renderSidecarRung(w, h) {
|
|
|
2637
3663
|
}
|
|
2638
3664
|
if (sidecarBlock) {
|
|
2639
3665
|
puts(1, blockTop, `\xB7 ${sidecarBlock.title} \xB7 local view \xB7`.padStart(Math.floor((w + sidecarBlock.title.length + 16) / 2)), { color: C.sys });
|
|
2640
|
-
sidecarBlock.lines.forEach((l, i) => puts(2, blockTop + 1 + i, l
|
|
2641
|
-
}
|
|
2642
|
-
if (ghostRows) {
|
|
2643
|
-
const g = activeGhosts()[0];
|
|
2644
|
-
const m = memberOf(g.userId);
|
|
2645
|
-
puts(1, ghostRow, m?.name ?? "?", { color: readableFg(m?.color ?? C.dim), bold: true });
|
|
2646
|
-
puts(1 + (m?.name ?? "?").length, ghostRow, " \u270E ", { color: C.sys });
|
|
2647
|
-
puts(4 + (m?.name ?? "?").length, ghostRow, [...g.text].slice(0, Math.max(0, w - (m?.name ?? "?").length - 6)).join(""), { color: dimOf(m?.color ?? 245), italic: true });
|
|
3666
|
+
sidecarBlock.lines.forEach((l, i) => puts(2, blockTop + 1 + i, fit(l, w - 4), { color: C.dim }));
|
|
2648
3667
|
}
|
|
3668
|
+
if (ghostRows) drawGhostRow(activeGhosts()[0], ghostRow, w);
|
|
2649
3669
|
renderInputLine(inputRow, w);
|
|
2650
3670
|
puts(0, h - 1, " ".repeat(w), { bgColor: C.panel });
|
|
2651
3671
|
const hintLeft = Date.now() - sidecarHintAt < 3e3 ? "tabs live at 60 cols and up" : "/team /secrets";
|
|
@@ -2654,19 +3674,75 @@ function renderSidecarRung(w, h) {
|
|
|
2654
3674
|
puts(w - hint.length, h - 1, hint, { bgColor: C.panel, color: C.dim });
|
|
2655
3675
|
if (popup.open) renderPopup(inputRow, w);
|
|
2656
3676
|
}
|
|
3677
|
+
var inputScroll = 0;
|
|
2657
3678
|
function renderInputLine(inputRow, width) {
|
|
2658
3679
|
puts(0, inputRow, " ".repeat(width), { bgColor: C.inputBg });
|
|
2659
3680
|
puts(1, inputRow, "\u276F ", { color: C.accent, bgColor: C.inputBg, bold: true });
|
|
2660
3681
|
const fieldW = width - 4;
|
|
2661
|
-
const win = editor.
|
|
2662
|
-
|
|
3682
|
+
const win = inputWindow(editor.text, editor.cursor, inputScroll, fieldW);
|
|
3683
|
+
inputScroll = win.scroll;
|
|
3684
|
+
puts(3, inputRow, padWidth(win.view, fieldW), { color: C.bright, bgColor: C.inputBg });
|
|
2663
3685
|
if (win.clippedLeft) puts(3, inputRow, "\u2026", { color: C.dim, bgColor: C.inputBg });
|
|
2664
3686
|
if (win.clippedRight) puts(3 + fieldW - 1, inputRow, "\u2026", { color: C.dim, bgColor: C.inputBg });
|
|
2665
|
-
const cx = 3 + win.
|
|
2666
|
-
if (cx
|
|
2667
|
-
|
|
2668
|
-
|
|
3687
|
+
const cx = 3 + win.cursorX;
|
|
3688
|
+
if (fieldW > 0 && cx + Math.max(1, stringWidth(win.under)) <= 3 + fieldW)
|
|
3689
|
+
puts(cx, inputRow, win.under, { color: onFill(C.bright), bgColor: C.bright });
|
|
3690
|
+
}
|
|
3691
|
+
function inputWindow(text, cursor, scroll, cells) {
|
|
3692
|
+
const { gs, at, ws: ws2, pw } = draftClusters(text);
|
|
3693
|
+
const n = gs.length;
|
|
3694
|
+
if (cells <= 0) return { view: "", cursorX: 0, under: " ", clippedLeft: false, clippedRight: n > 0, scroll: 0 };
|
|
3695
|
+
const width = (a, b) => pw[b] - pw[a];
|
|
3696
|
+
let cc = n;
|
|
3697
|
+
for (let i = 0; i < n; i++) if (at[i + 1] > cursor) {
|
|
3698
|
+
cc = i;
|
|
3699
|
+
break;
|
|
3700
|
+
}
|
|
3701
|
+
let uc = cc;
|
|
3702
|
+
while (uc < n && ws2[uc] === 0) uc++;
|
|
3703
|
+
const curW = uc < n ? ws2[uc] : 1;
|
|
3704
|
+
const margin = cells > 2 ? 1 : 0;
|
|
3705
|
+
const right = uc < n ? margin : 0;
|
|
3706
|
+
let s = 0;
|
|
3707
|
+
while (s < n && at[s] < scroll) s++;
|
|
3708
|
+
if (cc - s < margin) s = Math.max(0, cc - margin);
|
|
3709
|
+
while (s < cc && width(s, cc) + curW + right > cells) s++;
|
|
3710
|
+
while (s > 0 && width(s - 1, n) + 1 <= cells) s--;
|
|
3711
|
+
let e = s, used = 0;
|
|
3712
|
+
while (e < n && used + ws2[e] <= cells) {
|
|
3713
|
+
used += ws2[e];
|
|
3714
|
+
e++;
|
|
3715
|
+
}
|
|
3716
|
+
return {
|
|
3717
|
+
view: gs.slice(s, e).join(""),
|
|
3718
|
+
cursorX: width(s, cc),
|
|
3719
|
+
under: uc < n ? gs[uc] : " ",
|
|
3720
|
+
clippedLeft: s > 0,
|
|
3721
|
+
clippedRight: e < n,
|
|
3722
|
+
scroll: at[s]
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
var draftMemo = { text: "", gs: [], at: [0], ws: [], pw: [0] };
|
|
3726
|
+
function draftClusters(text) {
|
|
3727
|
+
if (draftMemo.text !== text) {
|
|
3728
|
+
const gs = clusters(text.replace(/\n/g, "\u21B5"));
|
|
3729
|
+
const at = [0], ws2 = [], pw = [0];
|
|
3730
|
+
for (const g of gs) {
|
|
3731
|
+
const w = stringWidth(g);
|
|
3732
|
+
at.push(at[at.length - 1] + [...g].length);
|
|
3733
|
+
ws2.push(w);
|
|
3734
|
+
pw.push(pw[pw.length - 1] + w);
|
|
3735
|
+
}
|
|
3736
|
+
draftMemo = { text, gs, at, ws: ws2, pw };
|
|
2669
3737
|
}
|
|
3738
|
+
return draftMemo;
|
|
3739
|
+
}
|
|
3740
|
+
function drawGhostRow(g, y, width) {
|
|
3741
|
+
const m = memberOf(g.userId);
|
|
3742
|
+
const nx = puts2(1, y, fit(m?.name ?? "?", 16), { color: readableFg(m?.color ?? C.dim), bold: true });
|
|
3743
|
+
puts(nx, y, " \u270E ", { color: C.sys });
|
|
3744
|
+
const draft = [...g.text].slice(0, 120).join("");
|
|
3745
|
+
puts(nx + 3, y, fit(draft, Math.max(0, width - nx - 5)), { color: dimOf(m?.color ?? 245), italic: true });
|
|
2670
3746
|
}
|
|
2671
3747
|
function renderChrome(rung) {
|
|
2672
3748
|
const w = W();
|
|
@@ -2684,8 +3760,8 @@ function renderChrome(rung) {
|
|
|
2684
3760
|
}
|
|
2685
3761
|
const whoMax = Math.max(0, w - 1 - x2);
|
|
2686
3762
|
let who2 = `${me.name} \u25CF`;
|
|
2687
|
-
if (who2
|
|
2688
|
-
const whoX = Math.max(x2, w - who2
|
|
3763
|
+
if (stringWidth(who2) > whoMax) who2 = whoMax >= 4 ? fit(me.name, whoMax - 2) + " \u25CF" : "\u25CF".slice(0, whoMax);
|
|
3764
|
+
const whoX = Math.max(x2, w - stringWidth(who2) - 1);
|
|
2689
3765
|
puts(whoX, 0, who2, { bgColor: C.panel, color: readableFg(me.color, C.panel), bold: true });
|
|
2690
3766
|
const gap = whoX - x2;
|
|
2691
3767
|
const label = gap >= 10 ? " settings " : gap >= 5 ? " set " : "";
|
|
@@ -2694,11 +3770,11 @@ function renderChrome(rung) {
|
|
|
2694
3770
|
}
|
|
2695
3771
|
puts(0, 0, " ".repeat(w), { bgColor: C.panel });
|
|
2696
3772
|
button(1, 0, " accelr8 ", { bgColor: C.accent, color: C.onAccent, bold: true }, goHome);
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
let hx = w - who.length - 1;
|
|
3773
|
+
const who = `${fit(me.name, 16)} \u25CF`;
|
|
3774
|
+
let hx = w - stringWidth(who) - 1;
|
|
2700
3775
|
puts(hx, 0, who, { bgColor: C.panel, color: readableFg(me.color, C.panel), bold: true });
|
|
2701
3776
|
hx -= 11;
|
|
3777
|
+
puts(11, 0, fit(state.project?.name ?? "", Math.max(0, hx - 12)), { bgColor: C.panel, color: C.bright, bold: true });
|
|
2702
3778
|
button(hx, 0, " settings ", { bgColor: C.panel, color: C.dim }, openSettings);
|
|
2703
3779
|
puts(0, 1, " ".repeat(w), { bgColor: C.bg });
|
|
2704
3780
|
const style = settings.tabs;
|
|
@@ -2773,27 +3849,35 @@ function clearUnread() {
|
|
|
2773
3849
|
unread.t0 = 0;
|
|
2774
3850
|
unread.t2 = 0;
|
|
2775
3851
|
}
|
|
3852
|
+
function homeLogoSeat(w, h) {
|
|
3853
|
+
if (w < LOGO_W + 4 || h < LOGO.length + 16) return null;
|
|
3854
|
+
return { top: 3, left: Math.max(2, Math.floor((w - LOGO_W) / 2)) };
|
|
3855
|
+
}
|
|
2776
3856
|
function renderHome(rung) {
|
|
2777
3857
|
const w = W();
|
|
2778
3858
|
puts(0, 0, " ".repeat(w), { bgColor: C.panel });
|
|
2779
3859
|
puts(1, 0, " accelr8 ", { bgColor: C.accent, color: C.onAccent, bold: true });
|
|
2780
3860
|
puts(11, 0, "vibe-code together", { bgColor: C.panel, color: C.dim, italic: true });
|
|
2781
|
-
const who = `${me.name} \u25CF`;
|
|
2782
|
-
|
|
2783
|
-
|
|
3861
|
+
const who = `${fit(me.name, 16)} \u25CF`;
|
|
3862
|
+
const whoW = stringWidth(who);
|
|
3863
|
+
puts(w - whoW - 1, 0, who, { bgColor: C.panel, color: readableFg(me.color, C.panel), bold: true });
|
|
3864
|
+
button(w - whoW - 11, 0, " settings ", { bgColor: C.panel, color: C.dim }, openSettings);
|
|
2784
3865
|
const dot = state.connected ? "\u25CF live" : "\u25CB offline";
|
|
2785
3866
|
puts(w - dot.length - 1, 1, dot, { bgColor: C.bg, color: state.connected ? C.good : C.warn });
|
|
2786
3867
|
let y = 2;
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
3868
|
+
const seat = homeLogoSeat(w, H());
|
|
3869
|
+
if (seat) {
|
|
3870
|
+
y = seat.top;
|
|
2790
3871
|
for (const line of LOGO) {
|
|
2791
|
-
puts(
|
|
3872
|
+
puts(seat.left, y, line, { color: C.accent, bgColor: C.bg });
|
|
2792
3873
|
y++;
|
|
2793
3874
|
}
|
|
2794
3875
|
y += 1;
|
|
2795
3876
|
}
|
|
2796
|
-
if (updateInfo && !task) {
|
|
3877
|
+
if (updateInfo && !task && updateRunning) {
|
|
3878
|
+
puts(2, y, "stopping the update \xB7 one moment", { color: C.dim, italic: true });
|
|
3879
|
+
y += 3;
|
|
3880
|
+
} else if (updateInfo && !task) {
|
|
2797
3881
|
puts(2, y, "\u21BB", { color: C.warn, bold: true });
|
|
2798
3882
|
const w2 = W();
|
|
2799
3883
|
const label = ` update to v${updateInfo.latest} \xB7 press enter `;
|
|
@@ -2819,15 +3903,15 @@ function renderHome(rung) {
|
|
|
2819
3903
|
for (const p of state.projects) {
|
|
2820
3904
|
if (y >= H() - 2) break;
|
|
2821
3905
|
const current = state.project?.id === p.id;
|
|
2822
|
-
const row = `${p.code.padEnd(8)} ${p.name
|
|
3906
|
+
const row = `${p.code.padEnd(8)} ${padWidth(fit(p.name, 30), 30)} ${String(p.members).padStart(5)} ${String(p.online).padStart(5)}`;
|
|
2823
3907
|
puts(2, y, current ? "\u25CF" : "\u25BA", { color: current ? C.good : C.accent });
|
|
2824
|
-
button(4, y, row, { color: C.text, bgColor: C.bg }, () => {
|
|
3908
|
+
const end = button(4, y, row, { color: C.text, bgColor: C.bg }, () => {
|
|
2825
3909
|
if (current) {
|
|
2826
3910
|
state.screen = "room";
|
|
2827
3911
|
sendMsg({ t: "tab", tab: state.tab });
|
|
2828
3912
|
} else sendMsg({ t: "joinProject", code: p.code });
|
|
2829
3913
|
});
|
|
2830
|
-
if (current) puts(
|
|
3914
|
+
if (current) puts(end + 1, y, "current", { color: C.good });
|
|
2831
3915
|
y++;
|
|
2832
3916
|
}
|
|
2833
3917
|
puts(2, H() - 2, "click a row to open its room", { color: C.dim, italic: true });
|
|
@@ -2841,32 +3925,42 @@ function renderRoom(rung) {
|
|
|
2841
3925
|
const sidebar = rung === "comfy";
|
|
2842
3926
|
const mainW = sidebar ? W() - 24 : W();
|
|
2843
3927
|
if (state.tab === "chat") renderChat(top, mainW, rung);
|
|
2844
|
-
else if (state.tab === "canvas") renderCanvas(top);
|
|
3928
|
+
else if (state.tab === "canvas") renderCanvas(top, mainW);
|
|
2845
3929
|
else if (state.tab === "secrets") renderSecrets(top);
|
|
2846
|
-
else renderTeam(top);
|
|
3930
|
+
else renderTeam(top, mainW);
|
|
2847
3931
|
if (sidebar) renderSidebar(mainW, top);
|
|
2848
3932
|
renderStatusBar(rung);
|
|
2849
3933
|
}
|
|
3934
|
+
var VOICE_ECHO = /^(voice · connecting|voice on · \/talk|● live · \/mute|muted · \/talk)/;
|
|
2850
3935
|
function renderStatusBar(rung) {
|
|
2851
3936
|
const w = W(), sy = H() - 1;
|
|
2852
3937
|
puts(0, sy, " ".repeat(w), { bgColor: C.panel });
|
|
2853
3938
|
const agentBit = state.agents.length ? ` \xB7 \u2726${state.agents.length} agents` : "";
|
|
2854
3939
|
const left = state.project ? ` ${state.project.name} \xB7 ${state.project.code} \xB7 ${onlineUserIds().size} online${agentBit} ` : " no project open ";
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
}
|
|
3940
|
+
const vi = voiceTest !== void 0 ? voiceTest : voice.indicator(state.voicePeers.length);
|
|
3941
|
+
const live = !!vi && vi.live;
|
|
3942
|
+
const voiceLabel = vi ? vi.label : "";
|
|
3943
|
+
let msg = state.status;
|
|
3944
|
+
if (vi && msg) msg = VOICE_ECHO.test(msg) ? "" : msg.replace(/^voice( on)? · /, "");
|
|
3945
|
+
const mid = msg ? ` ${msg} ` : bootHint ? ` ${bootHint} ` : "";
|
|
2862
3946
|
const hint = state.modal ? "[enter] ok \xB7 [esc] cancel " : "[tab] tabs \xB7 [^C] quit ";
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3947
|
+
const voiceW = stringWidth(voiceLabel);
|
|
3948
|
+
let midW = stringWidth(mid);
|
|
3949
|
+
const hintW = stringWidth(hint);
|
|
3950
|
+
const showHint = voiceW + midW + 1 + hintW <= w;
|
|
3951
|
+
const right = showHint ? w - hintW - 1 : w;
|
|
3952
|
+
const leftRoom = right - voiceW - midW;
|
|
3953
|
+
const leftShown = leftRoom >= 8 ? fit(left, leftRoom) : "";
|
|
3954
|
+
let x = puts2(0, sy, leftShown, { bgColor: C.panel, color: C.dim });
|
|
3955
|
+
if (voiceLabel) x = puts2(x, sy, voiceLabel, { bgColor: C.panel, color: live ? C.good : C.accent, bold: live });
|
|
3956
|
+
if (mid) {
|
|
3957
|
+
const shown = fit(mid, Math.max(0, right - x));
|
|
3958
|
+
midW = stringWidth(shown);
|
|
3959
|
+
const centred = Math.floor((w - midW) / 2);
|
|
3960
|
+
const mx = centred >= x && centred + midW <= right ? centred : x;
|
|
3961
|
+
puts(mx, sy, shown, { bgColor: C.panel, color: msg ? C.warn : C.dim });
|
|
3962
|
+
}
|
|
3963
|
+
if (showHint) puts(w - hintW, sy, hint, { bgColor: C.panel, color: C.dim });
|
|
2870
3964
|
}
|
|
2871
3965
|
function renderChat(top, mainW, rung) {
|
|
2872
3966
|
const h = H();
|
|
@@ -2895,25 +3989,19 @@ function renderChat(top, mainW, rung) {
|
|
|
2895
3989
|
});
|
|
2896
3990
|
}
|
|
2897
3991
|
const gs = activeGhosts().slice(0, ghostRows);
|
|
2898
|
-
for (let i = 0; i < gs.length; i++)
|
|
2899
|
-
const g = gs[i];
|
|
2900
|
-
const m = memberOf(g.userId);
|
|
2901
|
-
const name = m?.name ?? "?";
|
|
2902
|
-
const y = ghostTop + i;
|
|
2903
|
-
puts(1, y, name, { color: readableFg(m?.color ?? C.dim), bold: true });
|
|
2904
|
-
puts(1 + name.length, y, " \u270E ", { color: C.sys });
|
|
2905
|
-
const maxDraft = mainW - name.length - 6;
|
|
2906
|
-
const text = [...g.text].slice(0, 120).join("");
|
|
2907
|
-
const shown = text.length > maxDraft ? [...text].slice(0, Math.max(0, maxDraft - 1)).join("") + "\u2026" : text;
|
|
2908
|
-
puts(1 + name.length + 3, y, shown, { color: dimOf(m?.color ?? 245), italic: true });
|
|
2909
|
-
}
|
|
3992
|
+
for (let i = 0; i < gs.length; i++) drawGhostRow(gs[i], ghostTop + i, mainW);
|
|
2910
3993
|
renderInputLine(inputRow, mainW);
|
|
2911
3994
|
if (popup.open) renderPopup(inputRow, mainW);
|
|
2912
3995
|
}
|
|
3996
|
+
function columnsOf(chars) {
|
|
3997
|
+
if (isPlainText(chars.join(""))) return (i) => i;
|
|
3998
|
+
return (i) => stringWidth(chars.slice(0, i).join(""));
|
|
3999
|
+
}
|
|
2913
4000
|
function drawSpannedLine(x, y, text, spans, width, centered) {
|
|
2914
4001
|
const chars = [...text];
|
|
4002
|
+
const col = columnsOf(chars);
|
|
2915
4003
|
let ox = x;
|
|
2916
|
-
if (centered) ox = x + Math.max(0, Math.floor((width - chars.length) / 2));
|
|
4004
|
+
if (centered) ox = x + Math.max(0, Math.floor((width - col(chars.length)) / 2));
|
|
2917
4005
|
puts(ox, y, text, { color: C.text, bgColor: C.bg });
|
|
2918
4006
|
for (const sp of spans) {
|
|
2919
4007
|
const seg = chars.slice(sp.start, sp.end).join("");
|
|
@@ -2925,14 +4013,15 @@ function drawSpannedLine(x, y, text, spans, width, centered) {
|
|
|
2925
4013
|
color = C.dim;
|
|
2926
4014
|
text2 = SPINNER[frame % SPINNER.length];
|
|
2927
4015
|
} else if (sp.color === -2) color = C.sys;
|
|
4016
|
+
else if (sp.color === NOTICE_COLOR) color = readableFg(C.warn, bg);
|
|
2928
4017
|
else color = readableFg(sp.color, bg);
|
|
2929
|
-
puts(ox + sp.start, y, text2, { color, bold: sp.bold, italic: sp.italic, bgColor: bg });
|
|
4018
|
+
puts(ox + col(sp.start), y, text2, { color, bold: sp.bold, italic: sp.italic, bgColor: bg });
|
|
2930
4019
|
}
|
|
2931
4020
|
}
|
|
2932
4021
|
function renderPopup(inputRow, mainW) {
|
|
2933
4022
|
const items = popup.visible();
|
|
2934
4023
|
if (!items.length) return;
|
|
2935
|
-
const boxW = Math.min(Math.max(...items.map((c) => c.name
|
|
4024
|
+
const boxW = Math.min(Math.max(...items.map((c) => stringWidth(c.name) + stringWidth(c.detail ?? "") + 8), 20), mainW - 4);
|
|
2936
4025
|
const x0 = 2;
|
|
2937
4026
|
const y0 = inputRow - items.length - 2;
|
|
2938
4027
|
puts(x0, y0, "\u250C mention " + "\u2500".repeat(Math.max(0, boxW - 11)) + "\u2510", { color: C.accent, bgColor: C.popupBg });
|
|
@@ -2944,8 +4033,9 @@ function renderPopup(inputRow, mainW) {
|
|
|
2944
4033
|
puts(x0 + boxW - 1, y, "\u2502", { color: C.accent, bgColor: C.popupBg });
|
|
2945
4034
|
puts(x0 + 1, y, " ".repeat(boxW - 2), { bgColor: sel ? C.popupSel : C.popupBg });
|
|
2946
4035
|
puts(x0 + 2, y, c.kind === "agent" ? "\u2726" : "\u25CF", { color: nameColor, bgColor: sel ? C.popupSel : C.popupBg });
|
|
2947
|
-
|
|
2948
|
-
|
|
4036
|
+
const detail = fit(c.detail ?? "", Math.max(0, boxW - 8));
|
|
4037
|
+
puts(x0 + 4, y, fit(c.name, Math.max(1, boxW - 7 - stringWidth(detail))), { color: nameColor, bold: true, italic: c.kind === "agent", bgColor: sel ? C.popupSel : C.popupBg });
|
|
4038
|
+
if (detail) puts(x0 + boxW - 2 - stringWidth(detail), y, detail, { color: sel ? C.selFg : C.dim, bgColor: sel ? C.popupSel : C.popupBg });
|
|
2949
4039
|
});
|
|
2950
4040
|
puts(x0, y0 + 1 + items.length, "\u2514" + "\u2500".repeat(Math.max(0, boxW - 2)) + "\u2518", { color: C.accent, bgColor: C.popupBg });
|
|
2951
4041
|
}
|
|
@@ -2954,8 +4044,38 @@ function activeGhosts() {
|
|
|
2954
4044
|
for (const [sid, g] of ghosts) if (now - g.at > 3e3) ghosts.delete(sid);
|
|
2955
4045
|
return [...ghosts.values()].sort((a, b) => b.at - a.at);
|
|
2956
4046
|
}
|
|
2957
|
-
|
|
2958
|
-
|
|
4047
|
+
var brush = "\u2588";
|
|
4048
|
+
var canvasPan = { x: 0, y: 0 };
|
|
4049
|
+
var canvasView = { x0: IO_X, y0: IO_Y, w: 0, h: 0 };
|
|
4050
|
+
function canvasViewport(top, mainW, h) {
|
|
4051
|
+
const by0 = top + 1;
|
|
4052
|
+
return {
|
|
4053
|
+
x0: IO_X,
|
|
4054
|
+
y0: by0 + 1,
|
|
4055
|
+
w: Math.max(1, Math.min(CANVAS_W, mainW - 2)),
|
|
4056
|
+
h: Math.max(1, Math.min(CANVAS_H, h - 3 - by0))
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
function clampPan(pan, v) {
|
|
4060
|
+
return {
|
|
4061
|
+
x: Math.max(0, Math.min(CANVAS_W - Math.max(1, v.w), pan.x)),
|
|
4062
|
+
y: Math.max(0, Math.min(CANVAS_H - Math.max(1, v.h), pan.y))
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
function canvasCellAt(mx, my, v, pan) {
|
|
4066
|
+
if (mx < v.x0 || mx >= v.x0 + v.w || my < v.y0 || my >= v.y0 + v.h) return null;
|
|
4067
|
+
const x = mx - v.x0 + pan.x, y = my - v.y0 + pan.y;
|
|
4068
|
+
if (x < 0 || y < 0 || x >= CANVAS_W || y >= CANVAS_H) return null;
|
|
4069
|
+
return { x, y };
|
|
4070
|
+
}
|
|
4071
|
+
function panCanvas(dx, dy) {
|
|
4072
|
+
canvasPan = clampPan({ x: canvasPan.x + dx, y: canvasPan.y + dy }, canvasView);
|
|
4073
|
+
}
|
|
4074
|
+
function canvasGlyph(ch) {
|
|
4075
|
+
return ch.length > 0 && isPlainText(ch) && stringWidth(ch) === 1 ? ch : "?";
|
|
4076
|
+
}
|
|
4077
|
+
function renderCanvas(top, mainW) {
|
|
4078
|
+
const h = H();
|
|
2959
4079
|
const ty = top;
|
|
2960
4080
|
puts(1, ty, "brush", { color: C.dim });
|
|
2961
4081
|
let px = 7;
|
|
@@ -2966,35 +4086,45 @@ function renderCanvas(top) {
|
|
|
2966
4086
|
});
|
|
2967
4087
|
px += 3;
|
|
2968
4088
|
}
|
|
2969
|
-
button(px + 1, ty, " \u232B erase ", { bgColor: brush === " " ? C.bright : C.panel, color: brush === " " ? onFill(C.bright) : C.text }, () => {
|
|
4089
|
+
const eraseEnd = button(px + 1, ty, " \u232B erase ", { bgColor: brush === " " ? C.bright : C.panel, color: brush === " " ? onFill(C.bright) : C.text }, () => {
|
|
2970
4090
|
brush = " ";
|
|
2971
4091
|
});
|
|
2972
|
-
|
|
2973
|
-
const by0 =
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
const
|
|
4092
|
+
canvasView = canvasViewport(ty, mainW, h);
|
|
4093
|
+
const by0 = canvasView.y0 - 1, vw = canvasView.w, vh = canvasView.h;
|
|
4094
|
+
const bx1 = vw + 1, by1 = by0 + vh + 1;
|
|
4095
|
+
panCanvas(0, 0);
|
|
4096
|
+
const pan = canvasPan;
|
|
4097
|
+
button(Math.max(eraseEnd + 2, bx1 - 9), ty, " clear ", { bgColor: C.warn, color: onFill(C.warn), bold: true }, () => sendMsg({ t: "clear" }));
|
|
4098
|
+
box(0, by0, bx1, by1, { color: C.rule, bgColor: C.bg }, `canvas ${CANVAS_W}\xD7${CANVAS_H}`);
|
|
4099
|
+
const v = canvasView;
|
|
4100
|
+
const clip = (sx, sy) => sx >= v.x0 && sx < v.x0 + v.w && sy >= v.y0 && sy < v.y0 + v.h;
|
|
4101
|
+
const toScreen = (cx, cy) => ({ sx: v.x0 + cx - pan.x, sy: v.y0 + cy - pan.y });
|
|
2977
4102
|
for (const key in grid) {
|
|
2978
4103
|
const [cx, cy] = key.split(",").map(Number);
|
|
2979
|
-
const sx
|
|
2980
|
-
if (clip(sx, sy)) puts(sx, sy, grid[key][0], { color: grid[key][1], bgColor: C.bg });
|
|
4104
|
+
const { sx, sy } = toScreen(cx, cy);
|
|
4105
|
+
if (clip(sx, sy)) puts(sx, sy, canvasGlyph(grid[key][0]), { color: grid[key][1], bgColor: C.bg });
|
|
2981
4106
|
}
|
|
2982
4107
|
for (const [, c] of cursors) {
|
|
2983
|
-
const sx
|
|
4108
|
+
const { sx, sy } = toScreen(Math.round(c.x), Math.round(c.y));
|
|
2984
4109
|
if (!clip(sx, sy)) continue;
|
|
2985
4110
|
const m = memberOf(c.userId);
|
|
2986
4111
|
puts(sx, sy, "\u25B2", { color: readableFg(m?.color ?? 7), bold: true });
|
|
2987
|
-
const label = " " + (m?.name ?? "?");
|
|
2988
|
-
if (sx + 1 + label
|
|
4112
|
+
const label = " " + fit(m?.name ?? "?", 12);
|
|
4113
|
+
if (sx + 1 + stringWidth(label) <= v.x0 + v.w - 1) puts(sx + 1, sy, label, { bgColor: m?.color ?? 7, color: onFill(m?.color ?? 7), bold: true });
|
|
2989
4114
|
}
|
|
2990
4115
|
if (myLogical) {
|
|
2991
|
-
const sx
|
|
4116
|
+
const { sx, sy } = toScreen(myLogical.x, myLogical.y);
|
|
2992
4117
|
if (clip(sx, sy)) puts(sx, sy, brush === " " ? "\u25AB" : brush, { color: readableFg(me.color), bold: true });
|
|
2993
4118
|
}
|
|
2994
|
-
|
|
4119
|
+
if (vw < CANVAS_W || vh < CANVAS_H) {
|
|
4120
|
+
const midY = v.y0 + Math.floor(v.h / 2);
|
|
4121
|
+
if (pan.x > 0) puts(0, midY, "\u2039", { color: C.accent, bold: true, bgColor: C.bg });
|
|
4122
|
+
if (pan.x + vw < CANVAS_W) puts(bx1, midY, "\u203A", { color: C.accent, bold: true, bgColor: C.bg });
|
|
4123
|
+
const where = ` cols ${pan.x + 1}-${pan.x + vw} \xB7 rows ${pan.y + 1}-${pan.y + vh} \xB7 arrow keys move the view `;
|
|
4124
|
+
const label = stringWidth(where) <= vw - 2 ? where : " arrow keys move the view ";
|
|
4125
|
+
if (stringWidth(label) <= vw - 2) puts(bx1 - 1 - stringWidth(label), by1, label, { color: C.dim, bgColor: C.bg });
|
|
4126
|
+
}
|
|
2995
4127
|
}
|
|
2996
|
-
var brush = "\u2588";
|
|
2997
|
-
var canvasTop = IO_Y;
|
|
2998
4128
|
function renderSecrets(top) {
|
|
2999
4129
|
puts(2, top + 1, "SECRETS", { color: C.bright, bold: true });
|
|
3000
4130
|
puts(2, top + 3, "shared api keys and logins land here soon.", { color: C.dim, italic: true });
|
|
@@ -3021,15 +4151,15 @@ function blitMapLine(x0, y, line) {
|
|
|
3021
4151
|
if (seg) puts(x0 + sp.start, y, seg, { color: readableFg(sp.color, C.bg, 2.6), bold: true, bgColor: C.bg });
|
|
3022
4152
|
}
|
|
3023
4153
|
}
|
|
3024
|
-
function renderTeam(top) {
|
|
4154
|
+
function renderTeam(top, mainW) {
|
|
3025
4155
|
if (!state.project) return;
|
|
3026
4156
|
teamMap = null;
|
|
3027
4157
|
let y = top + 1;
|
|
3028
4158
|
const online = onlineUserIds();
|
|
3029
4159
|
const availH = H() - top - 3;
|
|
3030
|
-
if (
|
|
4160
|
+
if (mainW >= 48 && availH >= 22) {
|
|
3031
4161
|
const dots = mapDotsFromSessions();
|
|
3032
|
-
const mapW = Math.min(
|
|
4162
|
+
const mapW = Math.min(mainW - 4, 130);
|
|
3033
4163
|
const mapH = Math.min(15, Math.max(8, Math.floor(availH * 0.45)));
|
|
3034
4164
|
const title = `WHERE \xB7 ${dots.length}/${online.size} placed`;
|
|
3035
4165
|
puts(2, y, title, { color: C.bright, bold: true });
|
|
@@ -3047,11 +4177,11 @@ function renderTeam(top) {
|
|
|
3047
4177
|
const sess = state.sessions.filter((s) => s.userId === m.id);
|
|
3048
4178
|
const on = sess.length > 0;
|
|
3049
4179
|
puts(2, y, on ? "\u25CF" : "\u25CB", { color: on ? C.good : C.dim });
|
|
3050
|
-
puts(4, y, m.name + (m.id === me.id ? " \xB7 you" : ""), { color: readableFg(m.color), bold: true });
|
|
4180
|
+
puts(4, y, fit(m.name + (m.id === me.id ? " \xB7 you" : ""), 19), { color: readableFg(m.color), bold: true });
|
|
3051
4181
|
const where = on ? sess.some((s) => s.tab !== "away") ? "viewing " + (sess.find((s) => s.tab !== "away")?.tab ?? "chat") : "parked" : "offline";
|
|
3052
4182
|
puts(24, y, where, { color: on ? C.text : C.dim });
|
|
3053
4183
|
const city = tzCity(sess.find((s) => s.tz)?.tz);
|
|
3054
|
-
if (city &&
|
|
4184
|
+
if (city && mainW >= 56) puts(42, y, fit(city, mainW - 44), { color: C.dim });
|
|
3055
4185
|
y++;
|
|
3056
4186
|
}
|
|
3057
4187
|
y += 1;
|
|
@@ -3076,8 +4206,8 @@ function renderTeam(top) {
|
|
|
3076
4206
|
} else {
|
|
3077
4207
|
puts(2, y, label, { color: readableFg(owner?.color ?? C.dim), bold: true, italic: true });
|
|
3078
4208
|
}
|
|
3079
|
-
puts(24, y, `${owner?.name ?? "?"} \xB7 ${a.provider} \xB7 ${status}`, { color: C.dim });
|
|
3080
|
-
puts(52, y, access, { color: a.access?.mode === "all" ? C.good : C.dim });
|
|
4209
|
+
puts(24, y, fit(`${owner?.name ?? "?"} \xB7 ${a.provider} \xB7 ${status}`, 27), { color: C.dim });
|
|
4210
|
+
puts(52, y, fit(access, Math.max(0, mainW - 54)), { color: a.access?.mode === "all" ? C.good : C.dim });
|
|
3081
4211
|
y++;
|
|
3082
4212
|
}
|
|
3083
4213
|
y += 1;
|
|
@@ -3095,7 +4225,10 @@ function renderTeam(top) {
|
|
|
3095
4225
|
}
|
|
3096
4226
|
function renderSidebar(x0, top) {
|
|
3097
4227
|
const h = H();
|
|
3098
|
-
for (let y2 = top; y2 < h - 1; y2++)
|
|
4228
|
+
for (let y2 = top; y2 < h - 1; y2++) {
|
|
4229
|
+
puts(x0, y2, "\u2502", { color: C.rule, bgColor: C.bg });
|
|
4230
|
+
puts(x0 + 1, y2, " ".repeat(Math.max(0, W() - x0 - 1)), { bgColor: C.bg });
|
|
4231
|
+
}
|
|
3099
4232
|
const x = x0 + 2;
|
|
3100
4233
|
let y = top;
|
|
3101
4234
|
const online = onlineUserIds();
|
|
@@ -3105,7 +4238,7 @@ function renderSidebar(x0, top) {
|
|
|
3105
4238
|
const sess = state.sessions.filter((s) => s.userId === m.id);
|
|
3106
4239
|
const on = sess.length > 0;
|
|
3107
4240
|
puts(x, y, on ? "\u25CF" : "\u25CB", { color: on ? C.good : C.dim });
|
|
3108
|
-
puts(x + 2, y, m.name
|
|
4241
|
+
puts(x + 2, y, fit(m.name, 10), { color: readableFg(m.color), bold: true });
|
|
3109
4242
|
const where = m.id === me.id ? "you" : on ? sess.find((s) => s.tab !== "away")?.tab ?? "parked" : "";
|
|
3110
4243
|
puts(x + 13, y, String(where).slice(0, 7), { color: C.dim });
|
|
3111
4244
|
y++;
|
|
@@ -3142,7 +4275,14 @@ function box(x0, y0, x1, y1, attr, label) {
|
|
|
3142
4275
|
if (label) puts(x0 + 2, y0, " " + label + " ", { ...attr, bold: true, color: C.bright });
|
|
3143
4276
|
}
|
|
3144
4277
|
var MODELS = {
|
|
3145
|
-
|
|
4278
|
+
// Only models that answer without thinking when the request sends no
|
|
4279
|
+
// `thinking` field. The executor sends none, no output_config.effort, and
|
|
4280
|
+
// max_tokens 1024 (room.ts), so a model that thinks by default (Opus 5.5
|
|
4281
|
+
// and Fable 5.1 always think, Sonnet 5.5, Haiku 5.5 and Sonnet 5 do unless
|
|
4282
|
+
// told otherwise) spends that budget thinking: the reply comes back cut off
|
|
4283
|
+
// or empty. Offer the 5.x line once the executor sets effort and a bigger
|
|
4284
|
+
// max_tokens. claude-opus-4-8 is DEFAULT_MODEL, the first pick.
|
|
4285
|
+
anthropic: ["claude-opus-4-8", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
|
3146
4286
|
openai: ["gpt-4o", "gpt-4o-mini", "o1"]
|
|
3147
4287
|
};
|
|
3148
4288
|
function openAccounts() {
|
|
@@ -3173,9 +4313,13 @@ function renderAccounts() {
|
|
|
3173
4313
|
puts(0, 1, "\u2500".repeat(w), { color: C.rule, bgColor: C.bg });
|
|
3174
4314
|
const ix = 2;
|
|
3175
4315
|
let y = 3;
|
|
3176
|
-
puts(ix, y, "connected accounts become named agents you can @mention.
|
|
4316
|
+
puts(ix, y, "connected accounts become named agents you can @mention.", { color: C.dim });
|
|
3177
4317
|
y += 1;
|
|
3178
|
-
puts(ix, y,
|
|
4318
|
+
puts(ix, y, firstFit([
|
|
4319
|
+
"agents chat only for now \xB7 plan, approve and auto are not enabled yet",
|
|
4320
|
+
"agents chat only for now \xB7 no plan, approve or auto yet",
|
|
4321
|
+
"agents chat only for now"
|
|
4322
|
+
], w - ix - 2), { color: C.dim, italic: true });
|
|
3179
4323
|
y += 2;
|
|
3180
4324
|
puts(ix, y, "CONNECTED", { color: C.bright, bold: true });
|
|
3181
4325
|
y += 1;
|
|
@@ -3191,12 +4335,12 @@ function renderAccounts() {
|
|
|
3191
4335
|
const dotColor = st === "verifying" ? C.warn : st === "error" ? C.danger : online ? C.good : C.dim;
|
|
3192
4336
|
const status = st === "verifying" ? "verifying" : st === "error" ? "error" : online ? "online" : "ready";
|
|
3193
4337
|
puts(ix, y, dot, { color: dotColor });
|
|
3194
|
-
puts(ix + 2, y, "\u2726" + a.handle, { color: C.accent, bold: true, italic: true });
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
puts(ix +
|
|
3198
|
-
|
|
3199
|
-
|
|
4338
|
+
puts(ix + 2, y, fit("\u2726" + a.handle, 15), { color: C.accent, bold: true, italic: true });
|
|
4339
|
+
const statusX = w - 32;
|
|
4340
|
+
const detail = firstFit(a.auth.kind === "claude_code_login" ? ["claude login \xB7 model from Claude Code", "Claude Code login + model", "claude login"] : [`${a.provider} api key \xB7 ${a.model}`, `api key \xB7 ${a.model}`, a.model], Math.max(0, statusX - ix - 19));
|
|
4341
|
+
puts(ix + 18, y, detail, { color: C.text });
|
|
4342
|
+
puts(statusX, y, status, { color: dotColor });
|
|
4343
|
+
puts(w - 22, y, "chat only", { color: C.dim, italic: true });
|
|
3200
4344
|
button(w - 11, y, " remove ", { bgColor: C.tabOff, color: C.text }, () => removeAgentAccount(a));
|
|
3201
4345
|
y += 1;
|
|
3202
4346
|
}
|
|
@@ -3243,7 +4387,7 @@ function renderConnectWizard(ix, y, rx) {
|
|
|
3243
4387
|
});
|
|
3244
4388
|
} else if (d.step === "model") {
|
|
3245
4389
|
if (d.authKind === "claude_code_login") {
|
|
3246
|
-
puts(ix, y, "model
|
|
4390
|
+
puts(ix, y, "model: from your Claude Code settings.", { color: C.dim, bgColor: C.bg });
|
|
3247
4391
|
y += 1;
|
|
3248
4392
|
button(ix, y, " finish connecting ", { bgColor: C.good, color: onFill(C.good), bold: true }, () => finishConnect(DEFAULT_MODEL.anthropic));
|
|
3249
4393
|
return;
|
|
@@ -3509,13 +4653,6 @@ function renderAgentAccess() {
|
|
|
3509
4653
|
puts(ix, y, "no other teammates yet.", { color: C.dim, italic: true });
|
|
3510
4654
|
}
|
|
3511
4655
|
}
|
|
3512
|
-
function cycleMode(a) {
|
|
3513
|
-
const order = ["approve", "auto", "plan"];
|
|
3514
|
-
const next = order[(order.indexOf(a.mode) + 1) % order.length];
|
|
3515
|
-
saveAccounts(myAccounts.map((x) => x.id === a.id ? { ...x, mode: next } : x));
|
|
3516
|
-
myAccounts = safeLoadAccounts();
|
|
3517
|
-
state.status = `\u2726${a.handle}: ${next} mode`;
|
|
3518
|
-
}
|
|
3519
4656
|
function removeAgentAccount(a) {
|
|
3520
4657
|
const agentId = state.agents.find((ag) => ag.handle === a.handle && ag.ownerId === me.id)?.agentId;
|
|
3521
4658
|
if (agentId) sendMsg({ t: "agentDetach", agentId, token: loadTokens()[`${state.project?.id}:${a.handle}`] ?? "" });
|
|
@@ -3532,8 +4669,16 @@ function renderModal() {
|
|
|
3532
4669
|
for (let y = y0; y < y0 + bh; y++) puts(x0, y, " ".repeat(bw), { bgColor: C.panel });
|
|
3533
4670
|
box(x0, y0, x0 + bw - 1, y0 + bh - 1, { color: C.accent, bgColor: C.panel }, m.title);
|
|
3534
4671
|
const raw = m.mask ? "\u2022".repeat([...m.value].length) : m.value;
|
|
3535
|
-
const
|
|
3536
|
-
|
|
4672
|
+
const fieldW = bw - 4;
|
|
4673
|
+
const gs = clusters(raw + "\u258F");
|
|
4674
|
+
let shown = "";
|
|
4675
|
+
for (let i = gs.length - 1, used = 0; i >= 0; i--) {
|
|
4676
|
+
const gw = stringWidth(gs[i]);
|
|
4677
|
+
if (used + gw > fieldW) break;
|
|
4678
|
+
shown = gs[i] + shown;
|
|
4679
|
+
used += gw;
|
|
4680
|
+
}
|
|
4681
|
+
puts(x0 + 2, y0 + 2, padWidth(shown, fieldW), { bgColor: C.inputBg, color: readableFg(C.bright, C.inputBg) });
|
|
3537
4682
|
}
|
|
3538
4683
|
function promptCreate() {
|
|
3539
4684
|
state.modal = { title: "new project name", value: "", onSubmit: (v) => {
|
|
@@ -3621,6 +4766,9 @@ function onMouse(name, data2) {
|
|
|
3621
4766
|
chatLog.scrollDown(3, wrapW, viewH);
|
|
3622
4767
|
if (chatLog.pinned) clearUnread();
|
|
3623
4768
|
}
|
|
4769
|
+
} else if (state.screen === "room" && state.tab === "canvas") {
|
|
4770
|
+
panCanvas(0, name === "MOUSE_WHEEL_UP" ? -3 : 3);
|
|
4771
|
+
updateMyCursor(mx, my);
|
|
3624
4772
|
}
|
|
3625
4773
|
return;
|
|
3626
4774
|
}
|
|
@@ -3643,11 +4791,7 @@ function onMouse(name, data2) {
|
|
|
3643
4791
|
}
|
|
3644
4792
|
}
|
|
3645
4793
|
function toLogical(mx, my) {
|
|
3646
|
-
|
|
3647
|
-
if (cx < 0 || cy < 0 || cx >= CANVAS_W || cy >= CANVAS_H) return null;
|
|
3648
|
-
if (my < canvasTop || my > H() - 3) return null;
|
|
3649
|
-
if (mx > W() - 2) return null;
|
|
3650
|
-
return { x: cx, y: cy };
|
|
4794
|
+
return canvasCellAt(mx, my, canvasView, canvasPan);
|
|
3651
4795
|
}
|
|
3652
4796
|
function updateMyCursor(mx, my) {
|
|
3653
4797
|
if (state.screen !== "room" || state.tab !== "canvas") {
|
|
@@ -3698,34 +4842,43 @@ function slashCommand(text) {
|
|
|
3698
4842
|
ghostsOff = cmd.endsWith("off");
|
|
3699
4843
|
state.status = ghostsOff ? "draft broadcasting off" : "draft broadcasting on";
|
|
3700
4844
|
draftDirty = true;
|
|
4845
|
+
} else if (cmd.startsWith("/remove ")) {
|
|
4846
|
+
const who = cmd.slice("/remove ".length).trim().replace(/^@/, "");
|
|
4847
|
+
const hits = state.members.filter((m) => m.id !== me.id && m.name.toLowerCase() === who);
|
|
4848
|
+
if (!state.project) state.status = "open a project first";
|
|
4849
|
+
else if (hits.length !== 1) state.status = hits.length ? `more than one member is called ${who}` : `nobody here is called ${who}`;
|
|
4850
|
+
else {
|
|
4851
|
+
sendMsg({ t: "removeMember", userId: hits[0].id });
|
|
4852
|
+
state.status = `removing ${hits[0].name}...`;
|
|
4853
|
+
}
|
|
3701
4854
|
} else if (cmd === "/voice") {
|
|
3702
4855
|
toggleVoice();
|
|
3703
|
-
} else if (cmd === "/
|
|
3704
|
-
|
|
4856
|
+
} else if (cmd === "/voice headphones") {
|
|
4857
|
+
if (voice.active) state.status = "already in voice \xB7 /voice to leave first";
|
|
4858
|
+
else toggleVoice(true);
|
|
4859
|
+
} else if (cmd === "/talk") {
|
|
4860
|
+
toggleTalk(true);
|
|
4861
|
+
} else if (cmd === "/mute") {
|
|
4862
|
+
toggleTalk(false);
|
|
3705
4863
|
} else if (cmd === "/voice debug") {
|
|
3706
|
-
const recvTotal = [...voiceRecv.values()].reduce((a, b) => a + b, 0);
|
|
3707
4864
|
sidecarBlock = { title: "voice debug", at: Date.now(), lines: [
|
|
3708
|
-
|
|
3709
|
-
`tools: ffmpeg ${voiceTools?.ffmpegPath ? "ok" : "MISSING"} \xB7 ffplay ${voiceTools?.ffplayPath ? "ok" : "MISSING"}`,
|
|
3710
|
-
`mic: ${voiceDevice ?? "default"} \xB7 peers: ${state.voicePeers.length}`,
|
|
3711
|
-
`sent: ${voiceSent} \xB7 received: ${recvTotal} from ${voiceRecv.size}`,
|
|
4865
|
+
...voice.debugLines(state.voicePeers.length),
|
|
3712
4866
|
`log: ${voiceLogPath()}`,
|
|
3713
4867
|
"send me that file if voice fails"
|
|
3714
4868
|
] };
|
|
3715
4869
|
state.status = "voice debug \xB7 see the panel \xB7 log at ~/.accelr8/voice.log";
|
|
3716
4870
|
} else if (cmd === "/voice mics" || cmd === "/mics") {
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
4871
|
+
void voice.listDevices().then((mics) => {
|
|
4872
|
+
sidecarBlock = { title: "microphones", at: Date.now(), lines: mics.length ? mics.map((m, i) => `${voice.liveKitKind === "window" ? i + 1 : m.index} ${m.name}`).concat("use: /voice mic <number>") : [voice.liveKitKind === "window" ? "none found \xB7 allow the mic in the voice window first" : "none found (need ffmpeg)"] };
|
|
4873
|
+
micChoices = mics.map((m) => m.index);
|
|
4874
|
+
state.status = `${mics.length} mics \xB7 /voice mic <number> to pick`;
|
|
4875
|
+
});
|
|
3720
4876
|
} else if (cmd.startsWith("/voice mic ")) {
|
|
3721
|
-
const
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
voiceRetries = 0;
|
|
3727
|
-
startCapture();
|
|
3728
|
-
}
|
|
4877
|
+
const pick = cmd.slice("/voice mic ".length).trim();
|
|
4878
|
+
const n = Number(pick);
|
|
4879
|
+
const id = voice.liveKitKind === "window" && Number.isInteger(n) && micChoices[n - 1] ? micChoices[n - 1] : pick;
|
|
4880
|
+
voice.setDevice(id || void 0);
|
|
4881
|
+
state.status = `mic set to [${pick}]` + (voice.active ? " \xB7 switching" : "");
|
|
3729
4882
|
} else {
|
|
3730
4883
|
state.status = `unknown command: ${cmd.split(" ")[0]}`;
|
|
3731
4884
|
}
|
|
@@ -3752,7 +4905,7 @@ function sendChat() {
|
|
|
3752
4905
|
}
|
|
3753
4906
|
function onKey(name) {
|
|
3754
4907
|
if (name === "CTRL_C") return shutdown();
|
|
3755
|
-
if ((name === "CTRL_G" || name === "CTRL_ALT_G") &&
|
|
4908
|
+
if ((name === "CTRL_G" || name === "CTRL_ALT_G") && voice.active) return toggleTalk();
|
|
3756
4909
|
if (cine || boot) return skipBoot();
|
|
3757
4910
|
const rung = rungFor(W(), H());
|
|
3758
4911
|
if (rung === "micro" || rung === "orb") return;
|
|
@@ -3839,11 +4992,23 @@ function onKey(name) {
|
|
|
3839
4992
|
return;
|
|
3840
4993
|
}
|
|
3841
4994
|
if (name === "CTRL_P") {
|
|
3842
|
-
|
|
3843
|
-
sendMsg({ t: "tab", tab: "home" });
|
|
4995
|
+
goHome();
|
|
3844
4996
|
return;
|
|
3845
4997
|
}
|
|
3846
4998
|
if (!onChat) {
|
|
4999
|
+
if (state.tab === "canvas") {
|
|
5000
|
+
const page = Math.max(1, canvasView.h - 1);
|
|
5001
|
+
if (name === "LEFT") return panCanvas(-1, 0);
|
|
5002
|
+
if (name === "RIGHT") return panCanvas(1, 0);
|
|
5003
|
+
if (name === "UP") return panCanvas(0, -1);
|
|
5004
|
+
if (name === "DOWN") return panCanvas(0, 1);
|
|
5005
|
+
if (name === "PAGE_UP") return panCanvas(0, -page);
|
|
5006
|
+
if (name === "PAGE_DOWN") return panCanvas(0, page);
|
|
5007
|
+
if (name === "HOME") {
|
|
5008
|
+
canvasPan = { x: 0, y: 0 };
|
|
5009
|
+
return;
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
3847
5012
|
if (name === "1") return setTab("chat");
|
|
3848
5013
|
if (name === "2") return setTab("canvas");
|
|
3849
5014
|
if (name === "3") return setTab("secrets");
|
|
@@ -3996,8 +5161,7 @@ function restoreTerminal() {
|
|
|
3996
5161
|
function shutdown() {
|
|
3997
5162
|
restoreTerminal();
|
|
3998
5163
|
try {
|
|
3999
|
-
|
|
4000
|
-
voicePlayback?.stopAll();
|
|
5164
|
+
voice.shutdown();
|
|
4001
5165
|
} catch {
|
|
4002
5166
|
}
|
|
4003
5167
|
for (const h of runningExec.values()) {
|
|
@@ -4021,7 +5185,7 @@ function shutdownWithMessage(text) {
|
|
|
4021
5185
|
}
|
|
4022
5186
|
process.exit(1);
|
|
4023
5187
|
}
|
|
4024
|
-
function runClient(opts = {}) {
|
|
5188
|
+
async function runClient(opts = {}) {
|
|
4025
5189
|
loadSettings();
|
|
4026
5190
|
updateInfo = opts.update ?? null;
|
|
4027
5191
|
try {
|
|
@@ -4029,7 +5193,6 @@ function runClient(opts = {}) {
|
|
|
4029
5193
|
} catch {
|
|
4030
5194
|
myAccounts = [];
|
|
4031
5195
|
}
|
|
4032
|
-
me = loadIdentity(opts.name);
|
|
4033
5196
|
SERVER = opts.server || process.env.ACCELR8_SERVER || DEFAULT_SERVER;
|
|
4034
5197
|
if (!/^wss?:\/\//.test(SERVER)) SERVER = "ws://" + SERVER;
|
|
4035
5198
|
try {
|
|
@@ -4039,6 +5202,10 @@ function runClient(opts = {}) {
|
|
|
4039
5202
|
process.exit(1);
|
|
4040
5203
|
}
|
|
4041
5204
|
autoJoinCode = opts.joinCode;
|
|
5205
|
+
const creds = await ensureSignedIn(SERVER, { name: opts.name });
|
|
5206
|
+
if (!creds) process.exit(1);
|
|
5207
|
+
me = { ...creds.user };
|
|
5208
|
+
if (opts.name && opts.name.trim() && opts.name.trim() !== creds.user.name) pendingRename = opts.name.trim();
|
|
4042
5209
|
const die = (err) => {
|
|
4043
5210
|
restoreTerminal();
|
|
4044
5211
|
console.error("accelr8 crashed:", err instanceof Error ? err.stack || err.message : err);
|
|
@@ -4069,25 +5236,42 @@ export {
|
|
|
4069
5236
|
THEMES,
|
|
4070
5237
|
__accent,
|
|
4071
5238
|
__applyTheme,
|
|
5239
|
+
__bootAt,
|
|
5240
|
+
__bootOff,
|
|
5241
|
+
__canvasPan,
|
|
5242
|
+
__canvasView,
|
|
4072
5243
|
__click,
|
|
4073
5244
|
__closeOverlay,
|
|
4074
5245
|
__curTheme,
|
|
4075
5246
|
__dump,
|
|
4076
5247
|
__dumpCells,
|
|
5248
|
+
__grid,
|
|
5249
|
+
homeLogoSeat as __homeLogoSeat,
|
|
5250
|
+
__key,
|
|
5251
|
+
__mouse,
|
|
4077
5252
|
__openAccounts,
|
|
4078
5253
|
__openAgentAccess,
|
|
4079
5254
|
__openSettings,
|
|
5255
|
+
__puts,
|
|
5256
|
+
__server,
|
|
4080
5257
|
__setAccounts,
|
|
4081
5258
|
__setHeadlessScreen,
|
|
4082
5259
|
__setMe,
|
|
4083
5260
|
__setMySid,
|
|
4084
5261
|
__setNav,
|
|
4085
5262
|
__setUpdate,
|
|
5263
|
+
__setVoice,
|
|
5264
|
+
__startUpdate,
|
|
4086
5265
|
__taskDemo,
|
|
5266
|
+
__updateRunning,
|
|
5267
|
+
canvasCellAt,
|
|
5268
|
+
canvasViewport,
|
|
4087
5269
|
chatLog,
|
|
5270
|
+
clampPan,
|
|
4088
5271
|
cursors,
|
|
4089
5272
|
editor,
|
|
4090
5273
|
ghosts,
|
|
5274
|
+
inputWindow,
|
|
4091
5275
|
popup,
|
|
4092
5276
|
render,
|
|
4093
5277
|
runClient,
|