fix: ACP client permission handling and process cleanup

Two bugs fixed:
1. request_permission messages (JSON-RPC requests with both id+method) were
   silently swallowed by the response handler, causing hermes to hang waiting
   for permission approval. Now properly distinguish responses (id only) from
   server requests (id+method).
2. uwf-hermes process never exited after completing because the hermes ACP
   subprocess was still alive. Now explicitly close the ACP client after
   agent completion so the subprocess terminates.

小橘 <xiaoju@shazhou.work>
This commit is contained in:
2026-05-22 14:51:43 +00:00
parent 844f5438fe
commit d5d05334f5
2 changed files with 32 additions and 18 deletions
@@ -226,8 +226,11 @@ export class HermesAcpClient {
const msg = parsed as Record<string, unknown>;
// JSON-RPC response (has "id")
if ("id" in msg && msg.id !== undefined && msg.id !== null) {
const hasId = "id" in msg && msg.id !== undefined && msg.id !== null;
const hasMethod = typeof msg.method === "string";
// JSON-RPC response to one of our requests (has "id" but no "method")
if (hasId && !hasMethod) {
const response = msg as unknown as JsonRpcResponse;
const handler = this.pending.get(response.id);
if (handler !== undefined) {
@@ -237,7 +240,22 @@ export class HermesAcpClient {
return;
}
// JSON-RPC notification — session/update
// Server-initiated JSON-RPC request: session/request_permission (has "id" + "method")
if (msg.method === "session/request_permission" && hasId) {
const params = msg.params as Record<string, unknown> | undefined;
const options = (params?.options ?? []) as Array<{ optionId?: string }>;
const firstOptionId = options[0]?.optionId ?? "";
this.writeLine(
JSON.stringify({
jsonrpc: "2.0",
id: msg.id,
result: { outcome: { outcome: "selected", optionId: firstOptionId } },
}),
);
return;
}
// JSON-RPC notification — session/update (no "id")
if (msg.method === "session/update") {
const params = msg.params as Record<string, unknown> | undefined;
const update = params?.update as Record<string, unknown> | undefined;
@@ -246,20 +264,6 @@ export class HermesAcpClient {
}
return;
}
// session/request_permission — auto-approve (yolo mode)
if (msg.method === "session/request_permission") {
const params = msg.params as Record<string, unknown> | undefined;
const options = (params?.options ?? []) as Array<{ optionId?: string }>;
const firstOptionId = options[0]?.optionId ?? "";
// Respond with the selected option
const responseMsg = {
jsonrpc: "2.0",
id: (msg as Record<string, unknown>).id,
result: { outcome: { outcome: "selected", optionId: firstOptionId } },
};
this.writeLine(JSON.stringify(responseMsg));
}
}
// ---- Session update → structured messages ----
+11 -1
View File
@@ -82,9 +82,19 @@ export function createHermesAgent(): () => Promise<void> {
return { output: text, detailHash, sessionId };
}
return createAgent({
const agentMain = createAgent({
name: "hermes",
run: runHermes,
continue: continueHermes,
});
// Wrap to ensure ACP client is closed after agent completes,
// so the hermes subprocess exits and bun can terminate.
return async () => {
try {
await agentMain();
} finally {
await client.close();
}
};
}