mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 08:01:06 +00:00
@@ -26,6 +26,7 @@
|
||||
- Experimental pane graphics now support bounded named layers, acknowledged full-RGBA primary-layer direct file frames on audited local terminals, owned BGRA fallback, exact pixel mouse input, and placement-only resize replay.
|
||||
|
||||
### Fixed
|
||||
- The Pi integration now preserves short blocked transitions while an earlier lifecycle report is still in flight. (#2971)
|
||||
- High-rate output from many hidden panes no longer floods the server loop with redundant wakeups, and terminal input-mode synchronization no longer formats pane scrollback to read one keyboard flag.
|
||||
- Chinese IME commits now reach panes on macOS when the focused application requests printable key-release events. (#2924)
|
||||
- Windows now recognizes `Ctrl+1` through `Ctrl+9` keybindings instead of decoding those key records as control characters. (#2910)
|
||||
|
||||
@@ -315,6 +315,52 @@ test("Pi settlement preserves explicit blocked-state precedence", async () => {
|
||||
expect(requestStates(requests)).toEqual(["idle", "working", "blocked", "idle"]);
|
||||
});
|
||||
|
||||
test("Pi preserves a blocked transition while a state report is in flight", async () => {
|
||||
const recordingSocketPath = join(tmpdir(), `herdr-pi-blocked-order-${process.pid}.sock`);
|
||||
socketPath = recordingSocketPath;
|
||||
await rm(recordingSocketPath, { force: true });
|
||||
|
||||
const requests: unknown[] = [];
|
||||
let acknowledgeFirstReport: (() => void) | undefined;
|
||||
const recordingServer = createServer((socket) => {
|
||||
let input = "";
|
||||
socket.setEncoding("utf8");
|
||||
socket.on("data", (chunk) => {
|
||||
input += chunk;
|
||||
const newline = input.indexOf("\n");
|
||||
if (newline === -1) {
|
||||
return;
|
||||
}
|
||||
requests.push(JSON.parse(input.slice(0, newline)));
|
||||
if (requestStates(requests).length === 1) {
|
||||
acknowledgeFirstReport = () => socket.end("{}\n");
|
||||
return;
|
||||
}
|
||||
socket.end("{}\n");
|
||||
});
|
||||
});
|
||||
server = recordingServer;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
recordingServer.once("error", reject);
|
||||
recordingServer.listen(recordingSocketPath, resolve);
|
||||
});
|
||||
|
||||
configureIntegrationEnvironment(recordingSocketPath);
|
||||
const { eventHandlers, handlers, pi } = createExtensionHarness();
|
||||
const { default: install } = await importFresh("./pi/herdr-agent-state.ts");
|
||||
install(pi);
|
||||
|
||||
await handlers.get("session_start")?.({ reason: "startup" }, piContext(() => false));
|
||||
await waitFor(() => acknowledgeFirstReport !== undefined);
|
||||
|
||||
eventHandlers.get("herdr:blocked")?.({ active: true, label: "approval" }, {});
|
||||
eventHandlers.get("herdr:blocked")?.({ active: false }, {});
|
||||
acknowledgeFirstReport?.();
|
||||
|
||||
await waitFor(() => requestStates(requests).length === 3);
|
||||
expect(requestStates(requests)).toEqual(["working", "blocked", "working"]);
|
||||
});
|
||||
|
||||
test("Pi reports the session replacement source", async () => {
|
||||
const requests = await startRecordingServer("pi-session-source");
|
||||
const { handlers, pi } = createExtensionHarness();
|
||||
|
||||
@@ -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=pi
|
||||
// HERDR_INTEGRATION_VERSION=8
|
||||
// HERDR_INTEGRATION_VERSION=9
|
||||
// @ts-nocheck
|
||||
|
||||
import net from "node:net";
|
||||
@@ -143,10 +143,10 @@ function sendState(state: AgentState, message?: string, seq = nextReportSeq()):
|
||||
}
|
||||
|
||||
let sendInFlight = false;
|
||||
let queuedState: QueuedState | undefined;
|
||||
const queuedStates: QueuedState[] = [];
|
||||
|
||||
function queueState(state: AgentState, message?: string): void {
|
||||
queuedState = { state, message, seq: nextReportSeq() };
|
||||
queuedStates.push({ state, message, seq: nextReportSeq() });
|
||||
if (!sendInFlight) {
|
||||
void drainStateQueue();
|
||||
}
|
||||
@@ -159,14 +159,16 @@ async function drainStateQueue(): Promise<void> {
|
||||
|
||||
sendInFlight = true;
|
||||
try {
|
||||
while (queuedState) {
|
||||
const next = queuedState;
|
||||
queuedState = undefined;
|
||||
while (true) {
|
||||
const next = queuedStates.shift();
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
await sendState(next.state, next.message, next.seq);
|
||||
}
|
||||
} finally {
|
||||
sendInFlight = false;
|
||||
if (queuedState) {
|
||||
if (queuedStates.length > 0) {
|
||||
void drainStateQueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ pub(crate) use types::{IntegrationRecommendation, IntegrationStatus, Integration
|
||||
|
||||
const PI_EXTENSION_INSTALL_NAME: &str = "herdr-agent-state.ts";
|
||||
const PI_EXTENSION_ASSET: &str = include_str!("assets/pi/herdr-agent-state.ts");
|
||||
const PI_INTEGRATION_VERSION: u32 = 8;
|
||||
const PI_INTEGRATION_VERSION: u32 = 9;
|
||||
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 = 8;
|
||||
|
||||
@@ -293,7 +293,7 @@ fn integration_commands_run_locally_when_server_is_missing() {
|
||||
.unwrap();
|
||||
assert_eq!(integration_status.status.code(), Some(0));
|
||||
let status_stdout = String::from_utf8_lossy(&integration_status.stdout);
|
||||
assert!(status_stdout.contains("pi: current (v8)"));
|
||||
assert!(status_stdout.contains("pi: current (v9)"));
|
||||
assert!(status_stdout.contains("claude: not installed"));
|
||||
|
||||
let integration_uninstall = Command::new(env!("CARGO_BIN_EXE_herdr"))
|
||||
|
||||
Reference in New Issue
Block a user