Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to `@openclaw/libterminal` will be documented in this file.

### Fixed

- Preserve explicitly null cancellation reasons in browser terminal startup and Node PTY creation.

- Recheck whether the application still needs a terminal hub connection when a delayed reconnect fires.

- Split large Worker output acknowledgements into decodable chunks so flow-control accounting stays valid above one MiB.
Expand Down
2 changes: 1 addition & 1 deletion src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ async function loadRuntimeFromModule(

function throwIfAborted(signal?: AbortSignal): void {
if (signal?.aborted) {
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
throw signal.reason;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ async function findBrowserExternalAsset(distPath: string): Promise<string> {

function throwIfAborted(signal?: AbortSignal): void {
if (signal?.aborted) {
throw signal.reason ?? new Error("The operation was aborted");
throw signal.reason;
}
}

Expand Down
21 changes: 21 additions & 0 deletions test/browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ describe("createGhosttyTerminal", () => {
await expect(created).rejects.toBe(reason);
});

it("preserves an explicit null abort reason during startup", async () => {
const abort = new AbortController();
const dispose = vi.fn();
class Terminal extends TestGhosttyTerminal {
dispose = dispose;
}
await expect(
createGhosttyTerminal({
parent: {} as HTMLElement,
runtime: { ghostty: {}, Terminal, FitAddon: TestGhosttyFitAddon } as never,
signal: abort.signal,
onStatus: ({ state }) => {
if (state === "ready") {
abort.abort(null);
}
},
}),
).rejects.toBeNull();
expect(dispose).toHaveBeenCalledOnce();
});

it("removes the caller abort listener when the terminal is disposed", async () => {
const listeners = new Set<() => void>();
const signal = {
Expand Down
10 changes: 10 additions & 0 deletions test/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ import {
import { collectTerminalOutput, terminalText } from "../src/testing.js";

describe("spawnLocalPty", () => {
it("preserves an explicit null abort reason before spawning", async () => {
const abort = new AbortController();
abort.abort(null);
const driver = new FakePtyDriver();
await expect(
spawnLocalPty({ command: "sh", cwd: "/workspace", driver, signal: abort.signal }),
).rejects.toBeNull();
expect(driver.spawnCalls).toBe(0);
});

it("exchanges UTF-8 input and observes exit through the native PTY", async ({
onTestFinished,
}) => {
Expand Down
Loading