aicodeman 1.33.2 → 1.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.0879466b.js +43 -0
- package/dist/web/public/app.0879466b.js.br +0 -0
- package/dist/web/public/app.0879466b.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.cc927eb8.js → constants.d95fade9.js} +19 -2
- package/dist/web/public/constants.d95fade9.js.br +0 -0
- package/dist/web/public/constants.d95fade9.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.7f8be985.js +1 -0
- package/dist/web/public/i18n.7f8be985.js.br +0 -0
- package/dist/web/public/i18n.7f8be985.js.gz +0 -0
- package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
- package/dist/web/public/index.html +11 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
- package/dist/web/public/mobile-handlers.23a85db1.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.1aa4952d.css.gz +0 -0
- package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.67646701.js → panels-ui.6a5898d9.js} +69 -67
- package/dist/web/public/panels-ui.6a5898d9.js.br +0 -0
- package/dist/web/public/panels-ui.6a5898d9.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js +1 -0
- package/dist/web/public/sanitize-html.e7147e76.js.br +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.7575a231.js.gz +0 -0
- package/dist/web/public/settings-ui.c68e7f59.js.gz +0 -0
- package/dist/web/public/{styles.3fe3312d.css → styles.f2b78b86.css} +1 -1
- package/dist/web/public/styles.f2b78b86.css.br +0 -0
- package/dist/web/public/styles.f2b78b86.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.dcdc5772.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/terminal-split.js +192 -7
- package/dist/web/public/terminal-split.js.br +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-ui.d5223c0e.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +15 -4
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +2 -1
- package/dist/web/routes/file-routes.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.91131d2c.js +0 -43
- package/dist/web/public/app.91131d2c.js.br +0 -0
- package/dist/web/public/app.91131d2c.js.gz +0 -0
- package/dist/web/public/constants.cc927eb8.js.br +0 -0
- package/dist/web/public/constants.cc927eb8.js.gz +0 -0
- package/dist/web/public/i18n.f094d01f.js +0 -1
- package/dist/web/public/i18n.f094d01f.js.br +0 -0
- package/dist/web/public/i18n.f094d01f.js.gz +0 -0
- package/dist/web/public/panels-ui.67646701.js.br +0 -0
- package/dist/web/public/panels-ui.67646701.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js +0 -1
- package/dist/web/public/sanitize-html.bc7078d6.js.br +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/styles.3fe3312d.css.br +0 -0
- package/dist/web/public/styles.3fe3312d.css.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/web/public/sw.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// not the current one, so the old constant key meant that cleanup never deleted
|
|
27
27
|
// anything — hashed assets from every release ever deployed accumulated in one
|
|
28
28
|
// bucket until the origin hit its storage quota.
|
|
29
|
-
const BUILD_ID = '
|
|
29
|
+
const BUILD_ID = '454a058ea4d8';
|
|
30
30
|
const CACHE_NAME = `codeman-${BUILD_ID}`;
|
|
31
31
|
|
|
32
32
|
// Reverse-proxy base path: the worker is served at `<base>/sw.js`, so its own
|
|
@@ -48,7 +48,7 @@ const B = (p) => (p && p[0] === '/' ? SW_BASE + p : p);
|
|
|
48
48
|
// handler caches every successful GET at runtime, but the precache warmed
|
|
49
49
|
// nothing while looking like it did. Deriving it from the same manifest that
|
|
50
50
|
// renames the files is the only thing that keeps the two from drifting again.
|
|
51
|
-
const HASHED_ASSETS = ["styles.
|
|
51
|
+
const HASHED_ASSETS = ["styles.f2b78b86.css", "mobile.1aa4952d.css", "constants.d95fade9.js", "i18n.7f8be985.js", "mobile-handlers.23a85db1.js", "voice-input.c4b51eb6.js", "notification-manager.50bb87d8.js", "keyboard-accessory.c1e7c7b7.js", "input-cjk.8bc46081.js", "terminal-keycode229-recovery.fb91b25b.js", "sanitize-html.e7147e76.js", "app.0879466b.js", "tab-rail-resize.dcdc5772.js", "terminal-ui.d5223c0e.js", "respawn-ui.ff0dae4c.js", "ralph-panel.b350e2ef.js", "settings-ui.c68e7f59.js", "panels-ui.6a5898d9.js", "session-ui.7575a231.js", "ralph-wizard.13a1831e.js", "api-client.c9b1cddc.js", "subagent-windows.e6ca799f.js", "image-input.a216cf9e.js", "vendor/xterm-zerolag-input.6fee72f2.js", "vendor/xterm-predictive-echo.bd6882b8.js"];
|
|
52
52
|
|
|
53
53
|
// Core app shell -- cached on install for instant startup
|
|
54
54
|
const APP_SHELL = [
|
package/dist/web/public/sw.js.br
CHANGED
|
Binary file
|
package/dist/web/public/sw.js.gz
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
(function (global) {
|
|
17
|
+
// How long a scroll-to-top history pull may hold Pane B's live output.
|
|
18
|
+
const HISTORY_PULL_TIMEOUT_MS = 10000;
|
|
19
|
+
|
|
17
20
|
/**
|
|
18
21
|
* Minimal chunked write for Pane B's own xterm instance — write() in
|
|
19
22
|
* TERMINAL_CHUNK_SIZE slices, yielding a frame between each, instead of one
|
|
@@ -68,10 +71,18 @@
|
|
|
68
71
|
this.fitAddon = null;
|
|
69
72
|
this.ws = null;
|
|
70
73
|
this._wsReady = false;
|
|
74
|
+
this._wsClosed = false;
|
|
71
75
|
this._destroyed = false;
|
|
72
76
|
// Single-flight state for _loadBuffer()/_refreshBuffer() below.
|
|
73
77
|
this._bufferLoading = false;
|
|
74
78
|
this._bufferRefreshPending = false;
|
|
79
|
+
// Scroll-to-top history pull (shell panes only), see _maybeLoadMoreHistory().
|
|
80
|
+
// `_liveQueue` is non-null exactly while a pull is replaying: live frames
|
|
81
|
+
// are held there with their arrival time instead of written under it.
|
|
82
|
+
this._historyPullAt = 0;
|
|
83
|
+
this._historyPullUseless = false;
|
|
84
|
+
this._liveQueue = null;
|
|
85
|
+
this._onWheel = null;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
async connect() {
|
|
@@ -95,6 +106,8 @@
|
|
|
95
106
|
this.terminal.open(this.mountEl);
|
|
96
107
|
this.fitAddon.fit();
|
|
97
108
|
|
|
109
|
+
this._installWheelListener();
|
|
110
|
+
|
|
98
111
|
this.terminal.onData((data) => {
|
|
99
112
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
100
113
|
this.ws.send(JSON.stringify({ t: 'i', d: data }));
|
|
@@ -254,9 +267,9 @@
|
|
|
254
267
|
try {
|
|
255
268
|
const msg = JSON.parse(event.data);
|
|
256
269
|
if (msg.t === 'o') {
|
|
257
|
-
this.
|
|
270
|
+
this._onLiveOutput(msg.d);
|
|
258
271
|
} else if (msg.t === 'c') {
|
|
259
|
-
this.
|
|
272
|
+
this._onLiveClear();
|
|
260
273
|
} else if (msg.t === 'r') {
|
|
261
274
|
// Server-triggered refresh (SSE backpressure cleared, terminal
|
|
262
275
|
// data was dropped). The primary pane routes this to
|
|
@@ -280,16 +293,30 @@
|
|
|
280
293
|
// normal while it quietly ate everything typed into it. v1 scope is
|
|
281
294
|
// "say so", not reconnect — collapsing the split would lose the
|
|
282
295
|
// user's place in Pane B's scrollback for a transient blip.
|
|
283
|
-
this.ws.onclose = () =>
|
|
284
|
-
this._wsReady = false;
|
|
285
|
-
this.terminal?.write('\r\n\x1b[2m[Pane B disconnected — close and reopen the split to reconnect]\x1b[0m\r\n');
|
|
286
|
-
};
|
|
296
|
+
this.ws.onclose = () => this._onSocketClosed();
|
|
287
297
|
|
|
288
298
|
this.ws.onerror = () => {
|
|
289
299
|
// onclose fires after onerror — cleanup happens there.
|
|
290
300
|
};
|
|
291
301
|
}
|
|
292
302
|
|
|
303
|
+
// The socket's close, split out of connect() so the tests can drive it.
|
|
304
|
+
// While a history pull is running the marker waits for the pull's finally
|
|
305
|
+
// block: written now, it would sit above the output the pull is still
|
|
306
|
+
// holding (flushed after it on a skip, a downgrade or a failed fetch) or
|
|
307
|
+
// land in the middle of a chunked replay.
|
|
308
|
+
_onSocketClosed() {
|
|
309
|
+
this._wsReady = false;
|
|
310
|
+
this._wsClosed = true;
|
|
311
|
+
if (!this._liveQueue) this._writeDisconnectedMarker();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Extracted so both _onSocketClosed() and a history pull that ends on a
|
|
315
|
+
// closed socket can write it (see _pullHistory()'s finally block).
|
|
316
|
+
_writeDisconnectedMarker() {
|
|
317
|
+
this.terminal?.write('\r\n\x1b[2m[Pane B disconnected — close and reopen the split to reconnect]\x1b[0m\r\n');
|
|
318
|
+
}
|
|
319
|
+
|
|
293
320
|
// Fetches and writes the session's current scrollback. Used both by
|
|
294
321
|
// connect() (initial load) and by the `{t:'r'}` server-refresh frame
|
|
295
322
|
// (below) — the primary pane's own _onSessionNeedsRefresh (app.js) is
|
|
@@ -324,14 +351,168 @@
|
|
|
324
351
|
} catch {
|
|
325
352
|
/* Best-effort — live output still arrives once the socket connects. */
|
|
326
353
|
} finally {
|
|
327
|
-
this.
|
|
354
|
+
this._endBufferLoad();
|
|
328
355
|
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Ends a single-flight load (initial, refresh or history pull): clears the
|
|
359
|
+
// flag, then runs the ONE trailing refresh that arrived while it was busy.
|
|
360
|
+
_endBufferLoad() {
|
|
361
|
+
this._bufferLoading = false;
|
|
329
362
|
if (this._bufferRefreshPending && !this._destroyed) {
|
|
330
363
|
this._bufferRefreshPending = false;
|
|
331
364
|
this._refreshBuffer();
|
|
332
365
|
}
|
|
333
366
|
}
|
|
334
367
|
|
|
368
|
+
// Live terminal output. Written straight through, except while a history
|
|
369
|
+
// pull is replaying: a capture is current only up to the instant tmux took
|
|
370
|
+
// it, so a frame arriving mid-replay is held with its arrival time and
|
|
371
|
+
// replayed behind the snapshot by _pullHistory() (the primary pane's
|
|
372
|
+
// _finishBufferLoad `since` rule), never written underneath it.
|
|
373
|
+
_onLiveOutput(data) {
|
|
374
|
+
if (this._liveQueue) this._liveQueue.push({ at: performance.now(), data });
|
|
375
|
+
else this.terminal?.write(data);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// The server's `{t:'c'}` clear frame takes the same route as output, for the
|
|
379
|
+
// same reason: clearing straight away, mid-replay, would wipe the half-written
|
|
380
|
+
// snapshot and leave _pullHistory() measuring a buffer that is no longer the
|
|
381
|
+
// one it is restoring. Queued, it lands in order with the frames around it.
|
|
382
|
+
_onLiveClear() {
|
|
383
|
+
if (this._liveQueue) this._liveQueue.push({ at: performance.now(), clear: true });
|
|
384
|
+
else this.terminal?.clear();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Capture phase, because xterm's own wheel handler stopPropagation()s every
|
|
388
|
+
// event it consumes, so a bubbling listener here would never see the wheel
|
|
389
|
+
// while the pane still has scrollback to scroll. Passive: this only observes,
|
|
390
|
+
// xterm keeps doing the scrolling.
|
|
391
|
+
_installWheelListener() {
|
|
392
|
+
this._onWheel = (ev) => {
|
|
393
|
+
if (ev.deltaY < 0) this._maybeLoadMoreHistory();
|
|
394
|
+
};
|
|
395
|
+
this.mountEl.addEventListener('wheel', this._onWheel, { capture: true, passive: true });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Wheel-up at the top of a SHELL pane's scrollback. tmux repaints a burst of
|
|
399
|
+
// output (`cat` of a file longer than the screen) instead of scrolling it,
|
|
400
|
+
// so this pane's xterm ends up with about one screen of scrollback while
|
|
401
|
+
// tmux holds every line — and nothing here ever went back to ask, so the
|
|
402
|
+
// history was unreachable. The primary pane has the same pull
|
|
403
|
+
// (app.js _maybeRefetchFullHistory); Pane B is a separate xterm and needs its
|
|
404
|
+
// own. Shell only: a non-shell CLI's history is out of scope for this pull
|
|
405
|
+
// (its load already takes `full=1`; codex and Claude's inline renderer do
|
|
406
|
+
// grow tmux history, this just isn't how they recover it). The alternate-
|
|
407
|
+
// screen skip (nano, vim, less) only matters for a direct-PTY shell — under
|
|
408
|
+
// tmux the browser xterm never enters the alternate buffer.
|
|
409
|
+
_maybeLoadMoreHistory() {
|
|
410
|
+
if (this.sessionMode !== 'shell' || this._destroyed || !this.terminal) return;
|
|
411
|
+
if (this._bufferLoading) return;
|
|
412
|
+
// Mirrors app.js _maybeRefetchFullHistory and this pane's own
|
|
413
|
+
// _sendResize(): a detached session's own window already owns its PTY
|
|
414
|
+
// size and scrollback, so Pane B has nothing of its own to reconcile.
|
|
415
|
+
if (this.detachedSessions?.has(this.sessionId)) return;
|
|
416
|
+
const active = this.terminal.buffer.active;
|
|
417
|
+
if (active.type !== 'normal' || active.viewportY !== 0) return;
|
|
418
|
+
// Momentum scrolling fires this dozens of times per flick, so cooldown
|
|
419
|
+
// rather than latch; a pull that could only have downgraded the pane
|
|
420
|
+
// waits far longer.
|
|
421
|
+
const cooldown = this._historyPullUseless ? 60000 : 4000;
|
|
422
|
+
const now = Date.now();
|
|
423
|
+
if (now - this._historyPullAt < cooldown) return;
|
|
424
|
+
this._historyPullAt = now;
|
|
425
|
+
void this._pullHistory();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Pulls a BOUNDED window of tmux's full history (the same TERMINAL_TAIL_SIZE
|
|
429
|
+
// a tab switch loads, so a multi-megabyte capture never lands on xterm's
|
|
430
|
+
// main thread) and replays it under the reader's current place. Holds the
|
|
431
|
+
// single-flight flag across the fetch AND the replay, like _loadBuffer().
|
|
432
|
+
async _pullHistory() {
|
|
433
|
+
// A close before the pull already wrote its marker; one during it did not.
|
|
434
|
+
const closedBefore = this._wsClosed;
|
|
435
|
+
this._bufferLoading = true;
|
|
436
|
+
this._liveQueue = [];
|
|
437
|
+
let replayed = false;
|
|
438
|
+
let capturedAt = 0;
|
|
439
|
+
try {
|
|
440
|
+
// A deadline, because live output is held for as long as this runs: a
|
|
441
|
+
// request that hangs would otherwise freeze the whole pane. Aborting
|
|
442
|
+
// lands in the catch below, which releases the flag and the queue. It
|
|
443
|
+
// covers the body read too, not just the headers.
|
|
444
|
+
const res = await fetch(`/api/sessions/${this.sessionId}/terminal?full=1&tail=${TERMINAL_TAIL_SIZE}`, {
|
|
445
|
+
signal: global.AbortSignal?.timeout?.(HISTORY_PULL_TIMEOUT_MS),
|
|
446
|
+
});
|
|
447
|
+
// The cutoff below is the response's arrival, the same `since` rule the
|
|
448
|
+
// primary pane uses (_finishBufferLoad). It is a client clock standing in
|
|
449
|
+
// for the instant tmux took the capture, which lies somewhere in the
|
|
450
|
+
// round trip, so a frame in that window can be lost or doubled. Bounded
|
|
451
|
+
// by one round trip and not closable without a server-side capture time.
|
|
452
|
+
capturedAt = performance.now();
|
|
453
|
+
const payload = (await res.json())?.data;
|
|
454
|
+
const buffer = payload?.terminalBuffer;
|
|
455
|
+
const term = this.terminal;
|
|
456
|
+
if (!buffer || !term || this._destroyed) return;
|
|
457
|
+
const rowsBefore = term.buffer.active.length;
|
|
458
|
+
const rowsIncoming = global.app?._estimateReplayRows?.(buffer, term.cols) ?? buffer.split('\n').length;
|
|
459
|
+
// xterm keeps at most `scrollback + rows` rows while tmux keeps far more
|
|
460
|
+
// lines, so a window of short lines can carry more rows than this pane
|
|
461
|
+
// can ever hold, and `rowsIncoming <= rowsBefore` would never come true.
|
|
462
|
+
const scrollbackCap = term.options?.scrollback || 0;
|
|
463
|
+
const paneFull = scrollbackCap > 0 && rowsBefore >= scrollbackCap + term.rows;
|
|
464
|
+
// Nothing to gain (this also covers a downgrade, which would delete
|
|
465
|
+
// history mid-scroll), and a reset+rewrite would jump the viewport. An
|
|
466
|
+
// untruncated window IS all of tmux's history and the next burst can add
|
|
467
|
+
// more, so keep the 4 s cooldown. A truncated window can never reach past
|
|
468
|
+
// what the pane shows, and every ask costs the server a capture-pane of
|
|
469
|
+
// the whole history (`tail` is cut after it): back off to 60 s, as the
|
|
470
|
+
// primary pane does (app.js _maybeRefetchFullHistory). A full pane backs
|
|
471
|
+
// off too, since no window can ever fit in it.
|
|
472
|
+
if (rowsIncoming <= rowsBefore || paneFull) {
|
|
473
|
+
if (payload.truncated || paneFull) this._historyPullUseless = true;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
this._historyPullUseless = false;
|
|
477
|
+
term.write('\x1bc');
|
|
478
|
+
replayed = true;
|
|
479
|
+
await writeChunked(term, buffer, () => this._destroyed);
|
|
480
|
+
if (this._destroyed || !this.terminal) return;
|
|
481
|
+
// xterm parses asynchronously: an empty write's callback fires only
|
|
482
|
+
// after everything before it, so the row count below is the settled one.
|
|
483
|
+
await new Promise((resolve) => this.terminal.write('', resolve));
|
|
484
|
+
if (this._destroyed || !this.terminal) return;
|
|
485
|
+
// The replay grew the buffer UPWARD, so what was row 0 is now `delta`
|
|
486
|
+
// rows down; land there and the recovered history sits above it.
|
|
487
|
+
const delta = this.terminal.buffer.active.length - rowsBefore;
|
|
488
|
+
if (delta > 0) this.terminal.scrollToLine(delta);
|
|
489
|
+
else this.terminal.scrollToTop();
|
|
490
|
+
} catch {
|
|
491
|
+
/* Best-effort — live output keeps arriving whatever happens here. */
|
|
492
|
+
} finally {
|
|
493
|
+
const queued = this._liveQueue ?? [];
|
|
494
|
+
this._liveQueue = null;
|
|
495
|
+
// After a replay, only frames that arrived after the capture are news;
|
|
496
|
+
// earlier ones are already in it. With no replay, every held frame is.
|
|
497
|
+
const cutoff = replayed ? capturedAt : 0;
|
|
498
|
+
for (const entry of queued) {
|
|
499
|
+
if (entry.at < cutoff) continue;
|
|
500
|
+
if (entry.clear) this.terminal?.clear();
|
|
501
|
+
else this.terminal?.write(entry.data);
|
|
502
|
+
}
|
|
503
|
+
// A replay's own `\x1bc` wipes a marker written before the pull,
|
|
504
|
+
// painting a fresh, current-looking history while onData keeps
|
|
505
|
+
// silently dropping every keystroke on the dead socket, so re-stamp it
|
|
506
|
+
// after a replay. A close DURING the pull wrote no marker at all
|
|
507
|
+
// (_onSocketClosed() defers it while the queue is live), so write it
|
|
508
|
+
// whether or not this pull replayed. Checked after the queue flush so
|
|
509
|
+
// it is the last thing on screen, matching what the close would have
|
|
510
|
+
// left had the pull never run.
|
|
511
|
+
if (this._wsClosed && (replayed || !closedBefore)) this._writeDisconnectedMarker();
|
|
512
|
+
this._endBufferLoad();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
335
516
|
// The `{t:'r'}` server-refresh path: clear, then replay. Two refresh
|
|
336
517
|
// frames in a row used to start two concurrent replays, each clearing
|
|
337
518
|
// the terminal under the other's chunked write. A refresh that arrives
|
|
@@ -381,6 +562,10 @@
|
|
|
381
562
|
|
|
382
563
|
destroy() {
|
|
383
564
|
this._destroyed = true;
|
|
565
|
+
if (this._onWheel) {
|
|
566
|
+
this.mountEl?.removeEventListener('wheel', this._onWheel, { capture: true });
|
|
567
|
+
this._onWheel = null;
|
|
568
|
+
}
|
|
384
569
|
if (this.ws) {
|
|
385
570
|
this.ws.onopen = null;
|
|
386
571
|
this.ws.onmessage = null;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -274,7 +274,10 @@ Object.assign(CodemanApp.prototype, {
|
|
|
274
274
|
// one `/`, and refuse whatever still opens a second one. The proxied form
|
|
275
275
|
// is refused server-side as well (resolveUpstreamUrl).
|
|
276
276
|
const path = data.path.replace(/[\t\n\r]/g, '').replace(/^[/\\]+/, '/');
|
|
277
|
-
void this.openWebview(id, {
|
|
277
|
+
void this.openWebview(id, {
|
|
278
|
+
path: path.startsWith('/') && !/^\/[/\\]/.test(path) ? path : '/',
|
|
279
|
+
auto: true,
|
|
280
|
+
});
|
|
278
281
|
return;
|
|
279
282
|
}
|
|
280
283
|
};
|
|
@@ -356,15 +359,23 @@ Object.assign(CodemanApp.prototype, {
|
|
|
356
359
|
*/
|
|
357
360
|
/**
|
|
358
361
|
* @param {string} id
|
|
359
|
-
* @param {{path?: string}} [options] `path` (pathname+search+hash) opens a
|
|
362
|
+
* @param {{path?: string, auto?: boolean}} [options] `path` (pathname+search+hash) opens a
|
|
360
363
|
* deep link inside the dashboard: appended to the proxy prefix, or resolved
|
|
361
364
|
* against the real URL in direct mode. A mounted frame is navigated there
|
|
362
|
-
* rather than left on whatever page it was showing.
|
|
365
|
+
* rather than left on whatever page it was showing. `auto: true` marks an
|
|
366
|
+
* open the APP made (a frame recovering itself, the fallback after the
|
|
367
|
+
* active web tab closes), as on selectSession().
|
|
363
368
|
*/
|
|
364
369
|
async openWebview(id, options = {}) {
|
|
365
370
|
const webview = this.webviews.get(id);
|
|
366
371
|
if (!webview) return;
|
|
367
372
|
|
|
373
|
+
// Opening a web tab yourself is choosing something else, so a
|
|
374
|
+
// `#session=<id>` link still waiting for its session must not take the
|
|
375
|
+
// screen from this tab later. Retired before the await below, which a
|
|
376
|
+
// session:created could otherwise land inside.
|
|
377
|
+
if (options.auto !== true) this._retireUrlSession?.();
|
|
378
|
+
|
|
368
379
|
if (!this.webviewOrder.includes(id)) {
|
|
369
380
|
this.webviewOrder.push(id);
|
|
370
381
|
this._persistWebviewOrder();
|
|
@@ -513,7 +524,7 @@ Object.assign(CodemanApp.prototype, {
|
|
|
513
524
|
this.activeWebviewId = null;
|
|
514
525
|
const next = this.webviewOrder[0];
|
|
515
526
|
if (next) {
|
|
516
|
-
this.openWebview(next);
|
|
527
|
+
this.openWebview(next, { auto: true });
|
|
517
528
|
} else {
|
|
518
529
|
this._hideWebviewLayer();
|
|
519
530
|
// Fall back to whatever session was last shown, or the welcome screen.
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAyD7D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAsoC5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAyD7D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAsoC5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAmzCxG"}
|
|
@@ -1363,7 +1363,7 @@ export function registerFileRoutes(app, ctx) {
|
|
|
1363
1363
|
// breadth of formats the attachments viewer renders (image/audio/video/pdf)
|
|
1364
1364
|
// so the file viewer can open the same files.
|
|
1365
1365
|
const ext = filePath.split('.').pop()?.toLowerCase() || '';
|
|
1366
|
-
const imageExts = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']);
|
|
1366
|
+
const imageExts = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'svg', 'bmp', 'ico']);
|
|
1367
1367
|
// Shared with the attachment registry so a video plays the same whether it
|
|
1368
1368
|
// sits in the workspace or is reached by id from outside it.
|
|
1369
1369
|
const videoExts = VIDEO_ATTACHMENT_EXTENSIONS;
|
|
@@ -1642,6 +1642,7 @@ export function registerFileRoutes(app, ctx) {
|
|
|
1642
1642
|
jpeg: 'image/jpeg',
|
|
1643
1643
|
gif: 'image/gif',
|
|
1644
1644
|
webp: 'image/webp',
|
|
1645
|
+
avif: 'image/avif',
|
|
1645
1646
|
ico: 'image/x-icon',
|
|
1646
1647
|
bmp: 'image/bmp',
|
|
1647
1648
|
mp4: 'video/mp4',
|