aicodeman 1.26.1 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/config/buffer-limits.d.ts +20 -0
- package/dist/config/buffer-limits.d.ts.map +1 -1
- package/dist/config/buffer-limits.js +42 -0
- package/dist/config/buffer-limits.js.map +1 -1
- package/dist/mux-interface.d.ts +6 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +19 -0
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +41 -5
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +156 -40
- package/dist/tmux-manager.js.map +1 -1
- package/dist/utils/omp-session-resolver.d.ts +14 -0
- package/dist/utils/omp-session-resolver.d.ts.map +1 -1
- package/dist/utils/omp-session-resolver.js +21 -2
- package/dist/utils/omp-session-resolver.js.map +1 -1
- 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.78bf0bf8.js +38 -0
- package/dist/web/public/app.78bf0bf8.js.br +0 -0
- package/dist/web/public/app.78bf0bf8.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.7a635e34.js → constants.9cd8ed69.js} +23 -1
- package/dist/web/public/constants.9cd8ed69.js.br +0 -0
- package/dist/web/public/constants.9cd8ed69.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js +9 -1
- package/dist/web/public/entrance-animations.js.br +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/i18n.9db8c4e0.js.gz +0 -0
- package/dist/web/public/{image-input.ee16ad88.js → image-input.cd4b97c4.js} +13 -2
- package/dist/web/public/image-input.cd4b97c4.js.br +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
- package/dist/web/public/index.html +24 -11
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.87f14251.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.daf18302.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.0f83a608.js → mobile-handlers.2e9c214a.js} +7 -0
- package/dist/web/public/mobile-handlers.2e9c214a.js.br +0 -0
- package/dist/web/public/mobile-handlers.2e9c214a.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.65176832.css +1 -0
- package/dist/web/public/mobile.65176832.css.br +0 -0
- package/dist/web/public/mobile.65176832.css.gz +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.9eb5b235.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.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/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.cd222710.js.gz +0 -0
- package/dist/web/public/settings-ui.c175db9a.js +67 -0
- package/dist/web/public/settings-ui.c175db9a.js.br +0 -0
- package/dist/web/public/settings-ui.c175db9a.js.gz +0 -0
- package/dist/web/public/styles.6fed0e1b.css +1 -0
- package/dist/web/public/styles.6fed0e1b.css.br +0 -0
- package/dist/web/public/styles.6fed0e1b.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js +1 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.gz +0 -0
- package/dist/web/public/terminal-ui.9480b2a1.js +2 -0
- package/dist/web/public/terminal-ui.9480b2a1.js.br +0 -0
- package/dist/web/public/terminal-ui.9480b2a1.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 +199 -9
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/response-viewer-transcript.d.ts +20 -0
- package/dist/web/response-viewer-transcript.d.ts.map +1 -1
- package/dist/web/response-viewer-transcript.js +27 -0
- package/dist/web/response-viewer-transcript.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +16 -26
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +41 -7
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +4 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +9 -0
- package/dist/web/schemas.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/reference/endpoints.md +1 -0
- package/dist/web/public/app.3a89ac57.js +0 -38
- package/dist/web/public/app.3a89ac57.js.br +0 -0
- package/dist/web/public/app.3a89ac57.js.gz +0 -0
- package/dist/web/public/constants.7a635e34.js.br +0 -0
- package/dist/web/public/constants.7a635e34.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.br +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/mobile-handlers.0f83a608.js.br +0 -0
- package/dist/web/public/mobile-handlers.0f83a608.js.gz +0 -0
- package/dist/web/public/mobile.7112f27d.css +0 -1
- package/dist/web/public/mobile.7112f27d.css.br +0 -0
- package/dist/web/public/mobile.7112f27d.css.gz +0 -0
- package/dist/web/public/settings-ui.554f9cdd.js +0 -67
- package/dist/web/public/settings-ui.554f9cdd.js.br +0 -0
- package/dist/web/public/settings-ui.554f9cdd.js.gz +0 -0
- package/dist/web/public/styles.298f81f2.css +0 -1
- package/dist/web/public/styles.298f81f2.css.br +0 -0
- package/dist/web/public/styles.298f81f2.css.gz +0 -0
- package/dist/web/public/terminal-ui.a39b5aa7.js +0 -2
- package/dist/web/public/terminal-ui.a39b5aa7.js.br +0 -0
- package/dist/web/public/terminal-ui.a39b5aa7.js.gz +0 -0
package/dist/tmux-manager.js
CHANGED
|
@@ -422,6 +422,60 @@ function truncatePaneLineByVisibleColumns(line, maxColumns) {
|
|
|
422
422
|
export function normalizeScrollbackEol(buffer) {
|
|
423
423
|
return buffer.replace(/\r?\n/g, '\r\n');
|
|
424
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Read the pane's cursor and size, or null when tmux cannot say.
|
|
427
|
+
*
|
|
428
|
+
* Every field is validated together: a caller that gets a value back can place
|
|
429
|
+
* a caret with it, and one that gets null must not try.
|
|
430
|
+
*/
|
|
431
|
+
export function queryPaneCursor(run) {
|
|
432
|
+
let raw;
|
|
433
|
+
try {
|
|
434
|
+
raw = run().trim();
|
|
435
|
+
}
|
|
436
|
+
catch (cursorErr) {
|
|
437
|
+
console.error('[TmuxManager] Failed to query pane cursor after capture:', cursorErr);
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const [cursorX, cursorY, cols, rows] = raw.split(/\s+/).map((value) => parseInt(value, 10));
|
|
441
|
+
if (!Number.isFinite(cursorX) ||
|
|
442
|
+
!Number.isFinite(cursorY) ||
|
|
443
|
+
!Number.isFinite(cols) ||
|
|
444
|
+
!Number.isFinite(rows) ||
|
|
445
|
+
cursorX < 0 ||
|
|
446
|
+
cursorY < 0 ||
|
|
447
|
+
cols <= 0 ||
|
|
448
|
+
rows <= 0) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
return { cols, rows, cursorX, cursorY };
|
|
452
|
+
}
|
|
453
|
+
/** SGR attributes, which is all `capture-pane -e` emits. */
|
|
454
|
+
// eslint-disable-next-line no-control-regex
|
|
455
|
+
const CAPTURE_STYLE_SEQUENCE = /\x1b\[[0-9;:]*m/g;
|
|
456
|
+
/** Whether a capture holds anything a reader would see, styles discounted. */
|
|
457
|
+
export function hasVisibleContent(capture) {
|
|
458
|
+
return /\S/.test(capture.replace(CAPTURE_STYLE_SEQUENCE, ''));
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Put the caret back where the pane has it, counting UP from the bottom of what
|
|
462
|
+
* was just replayed.
|
|
463
|
+
*
|
|
464
|
+
* Relative rather than absolute (`CUP`) on purpose. `\x1b[<row>;<col>H` numbers
|
|
465
|
+
* rows from the top of the browser's screen, so it only lands correctly while
|
|
466
|
+
* the browser's row count equals the pane's — and it need not, because
|
|
467
|
+
* `resizeWindow` fires its tmux resize without waiting, so a capture can be
|
|
468
|
+
* taken before a requested resize has been applied. Counting up from the last
|
|
469
|
+
* replayed row is anchored to the content instead, which is the thing both ends
|
|
470
|
+
* genuinely share.
|
|
471
|
+
*/
|
|
472
|
+
export function formatCursorRestore(geometry) {
|
|
473
|
+
const up = Math.max(0, geometry.rows - 1 - geometry.cursorY);
|
|
474
|
+
const right = Math.max(0, geometry.cursorX);
|
|
475
|
+
// `\r` first so the column is known: the replay leaves the caret wherever the
|
|
476
|
+
// last row's text ended.
|
|
477
|
+
return `${up > 0 ? `\x1b[${up}A` : ''}\r${right > 0 ? `\x1b[${right}C` : ''}`;
|
|
478
|
+
}
|
|
425
479
|
export function formatPaneSnapshot(lines, geometry) {
|
|
426
480
|
const cols = Math.max(1, geometry.cols);
|
|
427
481
|
// Paint the full pane width. Earlier this dropped the rightmost column
|
|
@@ -610,7 +664,7 @@ export function remoteTmuxSessionName(sessionId) {
|
|
|
610
664
|
* even when the remote path contains spaces.
|
|
611
665
|
*/
|
|
612
666
|
export function buildRemoteLaunchCommand(options) {
|
|
613
|
-
const { mode, remote, sessionId, claudeMode, allowedTools } = options;
|
|
667
|
+
const { mode, remote, sessionId, claudeMode, allowedTools, ompConfig, resumeSessionId } = options;
|
|
614
668
|
// §6.3: honor the session's EFFECTIVE claude permission mode on remote instead of
|
|
615
669
|
// hardcoding --dangerously-skip-permissions, so a non-granted multi-user user's
|
|
616
670
|
// downgraded 'auto' actually reaches the remote agent (the default command otherwise
|
|
@@ -619,11 +673,68 @@ export function buildRemoteLaunchCommand(options) {
|
|
|
619
673
|
// `defaultRemoteCommandForMode`: `claude` lives under a per-user PATH entry that
|
|
620
674
|
// only an interactive login shell resolves (see that function's comment).
|
|
621
675
|
const override = remote.commands?.[mode];
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
676
|
+
let modeCommand;
|
|
677
|
+
if (override) {
|
|
678
|
+
modeCommand = override;
|
|
679
|
+
}
|
|
680
|
+
else if (mode === 'claude') {
|
|
681
|
+
// Deterministic conversation pinning for SSH-remote claude (mirrors the
|
|
682
|
+
// docker-claude shape in claudeDockerPaneCommand, INCLUDING the distinct
|
|
683
|
+
// resumeId branch it declares — this used to only mirror the same-id
|
|
684
|
+
// fallback shape, silently dropping an explicit resumeSessionId that
|
|
685
|
+
// differs from sessionId, e.g. a resume-from-history launch): the FIRST
|
|
686
|
+
// run creates the conversation under --session-id <sessionId>; a respawn
|
|
687
|
+
// / reattach re-runs the same idempotent command, --session-id exits
|
|
688
|
+
// non-zero ("already in use"), and the `||` fallback RESUMES that same
|
|
689
|
+
// conversation. Without a pinned id, every reattach relaunched a bare
|
|
690
|
+
// `claude` and started a NEW conversation (found live 2026-08-29: remote
|
|
691
|
+
// claude ctrl-d / ctrl-c relaunched a fresh session). A per-host
|
|
692
|
+
// `commands.claude` override stays authoritative (admin's explicit
|
|
693
|
+
// choice) and skips this entirely.
|
|
694
|
+
const permFlags = buildClaudePermissionFlags(claudeMode, allowedTools);
|
|
695
|
+
const cmd = `claude${permFlags}`;
|
|
696
|
+
// Defense in depth, mirroring claudeDockerPaneCommand's own belt-and-braces check:
|
|
697
|
+
// sessionId is server-minted and always safe in practice, but this command is built
|
|
698
|
+
// as a single shellescaped string and then executed as shell code on the remote
|
|
699
|
+
// host, so an unsafe value here is validated rather than trusted.
|
|
700
|
+
if (!RESUME_ID_SAFE.test(sessionId)) {
|
|
701
|
+
modeCommand = remoteLoginShellCommand(cmd);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
const rid = resumeSessionId && RESUME_ID_SAFE.test(resumeSessionId) ? resumeSessionId : undefined;
|
|
705
|
+
modeCommand = remoteLoginShellCommand(rid && rid !== sessionId
|
|
706
|
+
? `${cmd} --resume ${rid} || ${cmd} --session-id ${sessionId}`
|
|
707
|
+
: `${cmd} --session-id ${sessionId} || ${cmd} --resume ${sessionId}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else if (mode === 'omp') {
|
|
711
|
+
// Remote OMP respawn must RESUME the same conversation instead of
|
|
712
|
+
// relaunching fresh (found live 2026-08-29: remote ctrl-c/ctrl-d relaunched
|
|
713
|
+
// a brand-new omp session). The pinned id, when known, is passed as an
|
|
714
|
+
// explicit --resume; otherwise fall back to omp's own "most recent"
|
|
715
|
+
// --continue so a dead-pane respawn still lands back in the conversation.
|
|
716
|
+
// Rendered through the CLI registry (buildSpawnCommandFromRegistry), the
|
|
717
|
+
// SAME mode-agnostic path local/docker spawns use — not appendResumeFlag(),
|
|
718
|
+
// which would hand the id to the login shell as $0 after the quoted `-c
|
|
719
|
+
// 'omp'`, and not a raw buildOmpCommand() call, which the registry refactor
|
|
720
|
+
// (#347) deleted. Gives every registry CLI with a resume form this
|
|
721
|
+
// behaviour for free, and the flags can't drift from the local builder.
|
|
722
|
+
const ompEntry = getCli('omp');
|
|
723
|
+
const ompCmd = ompEntry
|
|
724
|
+
? (buildSpawnCommandFromRegistry(ompEntry, {
|
|
725
|
+
mode: 'omp',
|
|
726
|
+
sessionId,
|
|
727
|
+
ompConfig: {
|
|
728
|
+
...ompConfig,
|
|
729
|
+
resumeSessionId: resumeSessionId || ompConfig?.resumeSessionId,
|
|
730
|
+
},
|
|
731
|
+
}) ?? 'omp')
|
|
732
|
+
: 'omp';
|
|
733
|
+
modeCommand = remoteLoginShellCommand(ompCmd);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
modeCommand = defaultRemoteCommandForMode(mode);
|
|
737
|
+
}
|
|
627
738
|
const remoteName = remoteTmuxSessionName(sessionId);
|
|
628
739
|
// Innermost: the command tmux runs in the new pane. Run via `/bin/sh -c` by
|
|
629
740
|
// tmux, so the path needs shell-quoting here. `exec` replaces the shell with
|
|
@@ -1554,7 +1665,7 @@ export class TmuxManager extends EventEmitter {
|
|
|
1554
1665
|
const fullCmd = docker
|
|
1555
1666
|
? buildDockerLaunchCommand(resolveDockerLaunchOptions(mode, docker, sessionId, resumeSessionId))
|
|
1556
1667
|
: remote
|
|
1557
|
-
? buildRemoteSessionCommand({ mode, remote, sessionId, claudeMode, allowedTools })
|
|
1668
|
+
? buildRemoteSessionCommand({ mode, remote, sessionId, claudeMode, allowedTools, ompConfig, resumeSessionId })
|
|
1558
1669
|
: localFullCmd;
|
|
1559
1670
|
// Create tmux session in three steps to handle cold-start (no server running)
|
|
1560
1671
|
// and avoid the race where the command exits before remain-on-exit is set:
|
|
@@ -1758,7 +1869,7 @@ export class TmuxManager extends EventEmitter {
|
|
|
1758
1869
|
const fullCmd = docker
|
|
1759
1870
|
? buildDockerLaunchCommand(resolveDockerLaunchOptions(mode, docker, sessionId, resumeSessionId))
|
|
1760
1871
|
: remote
|
|
1761
|
-
? buildRemoteSessionCommand({ mode, remote, sessionId, claudeMode, allowedTools })
|
|
1872
|
+
? buildRemoteSessionCommand({ mode, remote, sessionId, claudeMode, allowedTools, ompConfig, resumeSessionId })
|
|
1762
1873
|
: localFullCmd;
|
|
1763
1874
|
try {
|
|
1764
1875
|
// Same per-CLI env setup as createSession, re-applied so the respawned pane inherits it.
|
|
@@ -2809,11 +2920,14 @@ export class TmuxManager extends EventEmitter {
|
|
|
2809
2920
|
* the browser xterm reproduces the live frame. Used for fast tab switches.
|
|
2810
2921
|
* - Full history (`opts.fullHistory`): `capture-pane -p -e -J -S -<N>` grabs
|
|
2811
2922
|
* the tmux scrollback (COD-47, bounded to the configured history limit),
|
|
2812
|
-
* returned as linear scrollback text with SGR codes preserved
|
|
2813
|
-
*
|
|
2814
|
-
* visible frame
|
|
2815
|
-
*
|
|
2816
|
-
*
|
|
2923
|
+
* returned as linear scrollback text with SGR codes preserved. Rows are not
|
|
2924
|
+
* repainted at absolute positions — a multi-screen history can't be painted
|
|
2925
|
+
* into a single visible frame — but the capture DOES end with a cursor move
|
|
2926
|
+
* putting the caret back where the pane has it, counted up from the last
|
|
2927
|
+
* replayed row. `-J` re-joins lines hard-wrapped at the pane width so they
|
|
2928
|
+
* reflow in the browser xterm. Used for full page reloads so the user gets
|
|
2929
|
+
* back their scroll history. Returns '' for a pane holding nothing visible,
|
|
2930
|
+
* so the caller keeps whatever history it already had.
|
|
2817
2931
|
* Caveat: lines tmux has already evicted past its history-limit are gone.
|
|
2818
2932
|
*/
|
|
2819
2933
|
/**
|
|
@@ -2872,35 +2986,37 @@ export class TmuxManager extends EventEmitter {
|
|
|
2872
2986
|
execOpts.maxBuffer =
|
|
2873
2987
|
(opts?.maxCaptureBytes ?? DEFAULT_TERMINAL_BUFFER_MAX_BYTES) + FULL_HISTORY_CAPTURE_SLACK_BYTES;
|
|
2874
2988
|
}
|
|
2875
|
-
const
|
|
2876
|
-
//
|
|
2877
|
-
//
|
|
2878
|
-
//
|
|
2879
|
-
//
|
|
2880
|
-
|
|
2989
|
+
const rawCapture = execSync(`${this.tmux()} ${captureFlags} -t ${shellescape(target)}`, execOpts);
|
|
2990
|
+
// Query the cursor BEFORE deciding anything else. On the full-history path
|
|
2991
|
+
// it settles both how the capture is trimmed and whether a cursor move is
|
|
2992
|
+
// appended, and those two have to agree: trailing blank rows are only safe
|
|
2993
|
+
// to keep when a move follows to put the caret back above them.
|
|
2994
|
+
const geometry = queryPaneCursor(() => execSync(`${this.tmux()} display-message -p -t ${shellescape(target)} '#{cursor_x} #{cursor_y} #{pane_width} #{pane_height}'`, { encoding: 'utf-8', timeout: EXEC_TIMEOUT_MS }));
|
|
2881
2995
|
if (fullHistory) {
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
console.error('[TmuxManager] Failed to query pane cursor after capture:', cursorErr);
|
|
2996
|
+
// Without geometry there is no cursor move, so fall back to the old trim.
|
|
2997
|
+
// Keeping the blank rows here would park the caret at the bottom of the
|
|
2998
|
+
// pane with nothing to correct it — worse than not trying at all.
|
|
2999
|
+
if (!geometry)
|
|
3000
|
+
return normalizeScrollbackEol(rawCapture.replace(/\n+$/g, ''));
|
|
3001
|
+
// Take the line terminator off and nothing else. The trailing blank rows
|
|
3002
|
+
// that remain are the real bottom of the screen, and the cursor move
|
|
3003
|
+
// below counts up from it. tmux joins rows with a bare `\n`; normalize to
|
|
3004
|
+
// `\r\n` so a fresh xterm (convertEol:false) starts each replayed line at
|
|
3005
|
+
// column 0 instead of staircasing diagonally (COD-138).
|
|
3006
|
+
const trimmed = rawCapture.replace(/\n$/, '');
|
|
3007
|
+
// An all-blank pane has to keep reading as "nothing to replay". The caller
|
|
3008
|
+
// treats an empty string as "capture unavailable" and keeps the byte
|
|
3009
|
+
// history; blank rows plus a cursor move are not empty, so without this a
|
|
3010
|
+
// blank pane REPLACES that history with a blank screen — the downgrade
|
|
3011
|
+
// `_replayWouldShrinkBuffer` exists to refuse, arriving from the server
|
|
3012
|
+
// side where that guard cannot see it.
|
|
3013
|
+
if (!hasVisibleContent(trimmed))
|
|
3014
|
+
return '';
|
|
3015
|
+
return `${normalizeScrollbackEol(trimmed)}${formatCursorRestore(geometry)}`;
|
|
2903
3016
|
}
|
|
3017
|
+
const buffer = rawCapture.replace(/\n+$/g, '');
|
|
3018
|
+
if (geometry)
|
|
3019
|
+
return formatPaneSnapshot(buffer.split('\n'), geometry);
|
|
2904
3020
|
// Cursor query failed or geometry was invalid, so we skip the absolute-
|
|
2905
3021
|
// positioned snapshot repaint and fall back to the raw capture. Normalize
|
|
2906
3022
|
// its bare `\n` line endings to `\r\n` so the replay doesn't staircase
|