parent
ca7ae086e8
commit
9e4fdd6102
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const PI_EXTENSION_ASSET: &str = include_str!("assets/pi/herdr-agent-state.ts");
|
|||
const PI_INTEGRATION_VERSION: u32 = 5;
|
||||
const OMP_EXTENSION_INSTALL_NAME: &str = "herdr-omp-agent-state.ts";
|
||||
const OMP_EXTENSION_ASSET: &str = include_str!("assets/omp/herdr-agent-state.ts");
|
||||
const OMP_INTEGRATION_VERSION: u32 = 4;
|
||||
const OMP_INTEGRATION_VERSION: u32 = 5;
|
||||
const CLAUDE_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
|
||||
"herdr-agent-state.ps1"
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -762,6 +762,36 @@ fn outdated_integrations_detect_previous_pi_version() {
|
|||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outdated_integrations_detect_previous_omp_version() {
|
||||
let _lock = integration_env_lock();
|
||||
let base = unique_base();
|
||||
let home = base.join("home");
|
||||
let ext_dir = home.join(".omp/agent/extensions");
|
||||
fs::create_dir_all(&ext_dir).unwrap();
|
||||
let extension_path = ext_dir.join(OMP_EXTENSION_INSTALL_NAME);
|
||||
fs::write(
|
||||
&extension_path,
|
||||
"// HERDR_INTEGRATION_ID=omp\n// HERDR_INTEGRATION_VERSION=4\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::env::set_var("HOME", &home);
|
||||
|
||||
let outdated = outdated_installed_integrations();
|
||||
|
||||
assert_eq!(outdated.len(), 1);
|
||||
assert_eq!(
|
||||
outdated[0].target,
|
||||
crate::api::schema::IntegrationTarget::Omp
|
||||
);
|
||||
assert_eq!(outdated[0].path, extension_path);
|
||||
assert_eq!(outdated[0].installed_version, Some(4));
|
||||
assert_eq!(outdated[0].expected_version, OMP_INTEGRATION_VERSION);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outdated_integrations_accept_current_version_marker() {
|
||||
let _lock = integration_env_lock();
|
||||
|
|
|
|||
Loading…
Reference in New Issue