mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(dashboard): gate resume hydration on sanitized PTY payload
The resume wait notice cleared on the first nonempty raw PTY frame, but the terminal is written sanitizer.next(text). The sanitizer collapses an erase-only, all-newline, or partial-CSI resume frame to "", so a control- only first frame hid the notice while xterm was still blank. Gate hydration completion on the rendered payload actually written to the terminal, and cover the control-only-first-frame case with a regression test over the real sanitizer. Co-authored-by: teknium1 <teknium@nousresearch.com>
This commit is contained in:
parent
bb189bf6fe
commit
539e9b5c1b
2 changed files with 42 additions and 2 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { PtyResumeSanitizer } from "./pty-resume-sanitizer";
|
||||
import {
|
||||
PTY_RESUME_LOADING_MAX_MS,
|
||||
shouldFinishResumeHydrationOnChunk,
|
||||
|
|
@ -17,6 +18,40 @@ describe("shouldFinishResumeHydrationOnChunk", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("resume hydration gate over the real sanitizer", () => {
|
||||
// Regression: the gate must key off the payload actually written to xterm,
|
||||
// not the raw frame. The sanitizer collapses an erase-only / all-newline /
|
||||
// partial-CSI resume frame to "", so a nonempty raw first frame would
|
||||
// otherwise clear the wait notice while the terminal is still blank.
|
||||
const ESC = String.fromCharCode(27);
|
||||
const CRLF = String.fromCharCode(13, 10);
|
||||
const VISIBLE = `Hello world${CRLF}`;
|
||||
|
||||
const controlOnlyFirstFrames: Array<[string, string]> = [
|
||||
["erase-only", `${ESC}[2K`],
|
||||
["all-newline", `${CRLF}${CRLF}`],
|
||||
["partial-CSI", `${ESC}[`],
|
||||
];
|
||||
|
||||
it.each(controlOnlyFirstFrames)(
|
||||
"keeps hydrating on a %s first frame, then finishes on visible replay",
|
||||
(_label, firstFrame) => {
|
||||
const sanitizer = new PtyResumeSanitizer();
|
||||
|
||||
// Raw first frame is nonempty, but nothing is written to xterm...
|
||||
expect(firstFrame.length).toBeGreaterThan(0);
|
||||
const firstRendered = sanitizer.next(firstFrame);
|
||||
expect(firstRendered).toBe("");
|
||||
expect(shouldFinishResumeHydrationOnChunk(firstRendered)).toBe(false);
|
||||
|
||||
// ...so the notice only clears once real replay output arrives.
|
||||
const secondRendered = sanitizer.next(VISIBLE);
|
||||
expect(secondRendered.length).toBeGreaterThan(0);
|
||||
expect(shouldFinishResumeHydrationOnChunk(secondRendered)).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("shouldShowResumeLoadingOverlay", () => {
|
||||
it("shows while a resume target is connecting or open and still hydrating", () => {
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -1058,8 +1058,13 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
: decoder.decode(new Uint8Array(ev.data as ArrayBuffer), {
|
||||
stream: true,
|
||||
});
|
||||
term.write(resumeParam ? sanitizer.next(text) : text);
|
||||
noteResumePtyChunk(text);
|
||||
// Gate hydration on the payload actually written to xterm. The
|
||||
// sanitizer can turn a nonempty erase-only / all-newline / partial-CSI
|
||||
// resume frame into "" (pty-resume-sanitizer.ts); keying off raw `text`
|
||||
// would hide the wait notice while the terminal is still blank.
|
||||
const rendered = resumeParam ? sanitizer.next(text) : text;
|
||||
term.write(rendered);
|
||||
noteResumePtyChunk(rendered);
|
||||
};
|
||||
|
||||
ws.onclose = (ev) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue