fix(ghidra): recover stale clients on same-target reopen (#2014)

* fix(ghidra): recover stale clients on same-target reopen

* test(ghidra): allow supervised shutdown in health regression

* fix(ghidra): read process exit without diagnostic snapshots

Health and startup exit checks called the supervisor snapshot, which
concatenates retained launcher output only to read the exit code.

* test(adb): align fixtures with literal output and directory commands

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 14:06:52 +08:00
1 parent 69e097a8ca
commit 4573e8cf35
9 files changed
+498 -24

No files matched your search

+52 -1
View File
@@ -1,6 +1,6 @@
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import { expect, it } from "vitest";
import { expect, it, vi } from "vitest";
import { ProviderProcessSupervisor } from "./ProviderProcess.js";
it("exit does not establish complete output when a producer closes its streams later", async () => {
@@ -34,6 +34,57 @@ it("exit does not establish complete output when a producer closes its streams l
}
});
it("reads an observed exit without materializing stream diagnostics", () => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: null,
signalCode: null,
kill: () => false,
});
const supervisor = new ProviderProcessSupervisor({
process: producer,
ownsProcessLifetime: false,
});
try {
producer.stdout.write("retained-diagnostic");
const snapshot = vi.spyOn(supervisor, "snapshot");
expect(supervisor.observedExit()).toBeUndefined();
producer.emit("exit", null, "SIGKILL");
expect(supervisor.observedExit()).toEqual({
code: null,
signal: "SIGKILL",
});
expect(snapshot).not.toHaveBeenCalled();
expect(supervisor.snapshot()).toMatchObject({
stdout: { text: "retained-diagnostic" },
exitCode: null,
signal: "SIGKILL",
});
} finally {
supervisor.dispose();
}
});
it("observes an exit that completed before supervision", () => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: 2,
signalCode: null,
kill: () => false,
});
const supervisor = new ProviderProcessSupervisor({
process: producer,
ownsProcessLifetime: false,
});
try {
expect(supervisor.observedExit()).toEqual({ code: 2, signal: null });
} finally {
supervisor.dispose();
}
});
it("reports a reached process memory limit only when the handle observed one", () => {
let reached = false;
const producer = Object.assign(new EventEmitter(), {