aboutsummaryrefslogtreecommitdiff
path: root/src/integration/assets
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-14 04:10:38 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-14 04:10:43 +0300
commit9e4fdd6102e6f68e42fbe419d447b758c161f0db (patch)
tree66df19412e2fcd152611ffcd6543ddf5db09df58 /src/integration/assets
parentca7ae086e8d6e80f6efc969d541b390328a6d143 (diff)
fix: retry dropped omp lifecycle reports
refs #1310
Diffstat (limited to 'src/integration/assets')
-rw-r--r--src/integration/assets/herdr-agent-state.test.ts54
-rw-r--r--src/integration/assets/omp/herdr-agent-state.ts29
2 files changed, 71 insertions, 12 deletions
diff --git a/src/integration/assets/herdr-agent-state.test.ts b/src/integration/assets/herdr-agent-state.test.ts
index 077d1894..c662e132 100644
--- a/src/integration/assets/herdr-agent-state.test.ts
+++ b/src/integration/assets/herdr-agent-state.test.ts
@@ -6,6 +6,7 @@ import { join } from "node:path";
const originalEnvironment = {
HERDR_ENV: process.env.HERDR_ENV,
+ HERDR_OMP_IDLE_DEBOUNCE_MS: process.env.HERDR_OMP_IDLE_DEBOUNCE_MS,
HERDR_PANE_ID: process.env.HERDR_PANE_ID,
HERDR_SOCKET_PATH: process.env.HERDR_SOCKET_PATH,
};
@@ -256,8 +257,8 @@ test("Pi waits for a replacement session report before publishing state", async
]);
});
-test("Pi retries working state after an unanswered socket attempt", async () => {
- const recordingSocketPath = join(tmpdir(), `herdr-pi-retry-${process.pid}.sock`);
+async function startDroppedFirstResponseServer(name: string) {
+ const recordingSocketPath = join(tmpdir(), `herdr-${name}-${process.pid}.sock`);
socketPath = recordingSocketPath;
await rm(recordingSocketPath, { force: true });
@@ -291,6 +292,46 @@ test("Pi retries working state after an unanswered socket attempt", async () =>
});
configureIntegrationEnvironment(recordingSocketPath);
+ return {
+ attemptedRequests,
+ deliveredRequests,
+ connectionCount: () => connectionCount,
+ };
+}
+
+test("Oh My Pi retries working before a queued idle state", async () => {
+ const { attemptedRequests } = await startDroppedFirstResponseServer("omp-retry");
+ process.env.HERDR_OMP_IDLE_DEBOUNCE_MS = "0";
+ const { handlers, pi } = createExtensionHarness();
+
+ const { default: install } = await importFresh("./omp/herdr-agent-state.ts");
+ install(pi);
+
+ const context = {
+ hasUI: true,
+ isIdle: () => false,
+ sessionManager: {
+ getSessionFile: () => undefined,
+ getSessionId: () => undefined,
+ },
+ };
+ handlers.get("session_start")?.({ reason: "startup" }, context);
+ handlers.get("agent_end")?.({ messages: [] }, context);
+
+ const deadline = Date.now() + 2_500;
+ while (Date.now() < deadline && attemptedRequests.length < 3) {
+ await Bun.sleep(5);
+ }
+
+ expect(attemptedRequests).toHaveLength(3);
+ expect(attemptedRequests[1]).toEqual(attemptedRequests[0]);
+ expect(requestState(attemptedRequests[0])).toBe("working");
+ expect(requestState(attemptedRequests[2])).toBe("idle");
+});
+
+test("Pi retries working state after an unanswered socket attempt", async () => {
+ const { attemptedRequests, deliveredRequests, connectionCount } =
+ await startDroppedFirstResponseServer("pi-retry");
const { handlers, pi } = createExtensionHarness();
const { default: install } = await importFresh("./pi/herdr-agent-state.ts");
@@ -324,12 +365,19 @@ test("Pi retries working state after an unanswered socket attempt", async () =>
await Bun.sleep(5);
}
- expect(connectionCount).toBeGreaterThanOrEqual(2);
+ expect(connectionCount()).toBeGreaterThanOrEqual(2);
expect(attemptedRequests.length).toBeGreaterThanOrEqual(2);
expect(attemptedRequests[1]).toEqual(attemptedRequests[0]);
expect(reportedWorking()).toBe(true);
});
+function requestState(request: unknown): unknown {
+ if (!isRecord(request) || !isRecord(request.params)) {
+ return undefined;
+ }
+ return request.params.state;
+}
+
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
diff --git a/src/integration/assets/omp/herdr-agent-state.ts b/src/integration/assets/omp/herdr-agent-state.ts
index 1b760a90..9bb6671a 100644
--- a/src/integration/assets/omp/herdr-agent-state.ts
+++ b/src/integration/assets/omp/herdr-agent-state.ts
@@ -2,7 +2,7 @@
// managed by herdr; reinstalling or updating the integration overwrites this file.
// add custom hooks/plugins beside this file instead of editing it.
// HERDR_INTEGRATION_ID=omp
-// HERDR_INTEGRATION_VERSION=4
+// HERDR_INTEGRATION_VERSION=5
// @ts-nocheck
import { createConnection } from "node:net";
@@ -18,30 +18,41 @@ function enabled() {
let requestQueue = Promise.resolve();
-function sendRequestNow(request: unknown): Promise<void> {
+function sendRequestAttempt(request: unknown, timeoutMs: number): Promise<boolean> {
if (!enabled()) {
- return Promise.resolve();
+ return Promise.resolve(true);
}
return new Promise((resolve) => {
let done = false;
- const finish = () => {
+ let timeout: ReturnType<typeof setTimeout> | undefined;
+ const finish = (delivered: boolean) => {
if (done) return;
done = true;
+ if (timeout) {
+ clearTimeout(timeout);
+ }
socket.destroy();
- resolve();
+ resolve(delivered);
};
const socket = createConnection(socketPath!);
- socket.on("error", finish);
+ socket.on("error", () => finish(false));
socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`));
- socket.on("data", finish);
- socket.on("end", finish);
- const timeout = setTimeout(finish, 500);
+ socket.on("data", () => finish(true));
+ socket.on("end", () => finish(false));
+ timeout = setTimeout(() => finish(false), timeoutMs);
timeout.unref?.();
});
}
+async function sendRequestNow(request: unknown): Promise<void> {
+ if (await sendRequestAttempt(request, 500)) {
+ return;
+ }
+ await sendRequestAttempt(request, 1500);
+}
+
function sendRequest(request: unknown): Promise<void> {
requestQueue = requestQueue.then(
() => sendRequestNow(request),