mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 14:11:18 +00:00
* feat(gateway): stream node.invoke progress with idle timeouts and cancel * feat(node-host): opt-in approval-gated claude cli agent run command * feat(agents): run node-placed claude-cli turns through streaming node invoke * feat(anthropic): continue claude catalog sessions on advertising nodes * docs(nodes): document paired-node claude agent runs and continuation * chore(plugin-sdk): admit spawn-invocation helper into surface budget * fix(nodes): harden streaming invoke and node run policy from review findings * test(node-host): prove multi-token tool-policy argv fails closed * fix(nodes): allowlist cancel event for apps and break node-host type cycle * chore(protocol): regenerate Swift gateway models for node invoke progress * chore(node-host): drop test-only export surface flagged by deadcode ratchet * style(node-host): merge invoke-types imports * refactor(nodes): split node agent run modules to satisfy the LOC ratchet * chore(protocol): regenerate android gateway models and sync export baseline * chore(plugin-sdk): admit spawn-invocation helper and inherited deprecated drift into surface budget * chore(plugin-sdk): tolerate inherited agent-core deprecated export * fix(anthropic): drop node param parser orphaned by upstream catalog split * ci: sync unused-export baseline with healed main * test(agents): adopt renamed cli backend prepare helper * style(node-host): format claude run spawn invocation * ci: retrigger checks * chore(plugin-sdk): pin surface budgets to healed-base counts * fix(protocol): restore untyped lazy validators after upstream style change
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
import type { GatewayClientRequestOptions } from "../gateway/client.js";
|
|
import type { NodeHostClient } from "./client.js";
|
|
|
|
export type NodeHostWorkerGatewayResponse =
|
|
| { type: "gateway-response"; id: string; ok: true; result: unknown }
|
|
| { type: "gateway-response"; id: string; ok: false; error: string };
|
|
|
|
type PendingGatewayRequest = {
|
|
resolve: (value: unknown) => void;
|
|
reject: (error: Error) => void;
|
|
timer: NodeJS.Timeout;
|
|
};
|
|
|
|
export class NodeHostWorkerBridgeClient implements NodeHostClient {
|
|
private nextRequestId = 1;
|
|
private readonly pending = new Map<string, PendingGatewayRequest>();
|
|
|
|
constructor(private readonly writeMessage: (message: unknown) => void) {}
|
|
|
|
async request<T = Record<string, unknown>>(
|
|
method: string,
|
|
params?: unknown,
|
|
opts?: GatewayClientRequestOptions,
|
|
): Promise<T> {
|
|
if (method === "node.invoke.result") {
|
|
this.writeMessage({ type: "invoke-result", result: params ?? {} });
|
|
return {} as T;
|
|
}
|
|
if (method === "node.invoke.progress") {
|
|
// Unreachable while the embedded app worker never enables agent runs
|
|
// (the only progress emitter); the Mac host bridge has no case for it.
|
|
this.writeMessage({ type: "invoke-progress", progress: params ?? {} });
|
|
return {} as T;
|
|
}
|
|
if (method === "node.event") {
|
|
this.writeMessage({ type: "node-event", event: params ?? {} });
|
|
return {} as T;
|
|
}
|
|
|
|
const id = `gateway-${this.nextRequestId++}`;
|
|
const timeoutMs = Math.max(1, opts?.timeoutMs ?? 15_000);
|
|
const response = new Promise<unknown>((resolve, reject) => {
|
|
const timer = setTimeout(() => {
|
|
this.pending.delete(id);
|
|
reject(new Error(`Gateway request timed out: ${method}`));
|
|
}, timeoutMs);
|
|
this.pending.set(id, { resolve, reject, timer });
|
|
});
|
|
this.writeMessage({ type: "gateway-request", id, method, params: params ?? {}, timeoutMs });
|
|
return (await response) as T;
|
|
}
|
|
|
|
handleResponse(message: NodeHostWorkerGatewayResponse): boolean {
|
|
const pending = this.pending.get(message.id);
|
|
if (!pending) {
|
|
return false;
|
|
}
|
|
this.pending.delete(message.id);
|
|
clearTimeout(pending.timer);
|
|
if (message.ok) {
|
|
pending.resolve(message.result);
|
|
} else {
|
|
pending.reject(new Error(message.error));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
close(): void {
|
|
for (const pending of this.pending.values()) {
|
|
clearTimeout(pending.timer);
|
|
pending.reject(new Error("node-host worker stopped"));
|
|
}
|
|
this.pending.clear();
|
|
}
|
|
}
|
|
|
|
export async function stopNodeHostWorkerFromSignal(
|
|
input: { close(): void },
|
|
stop: (exitCode: number) => Promise<void>,
|
|
exitCode: number,
|
|
): Promise<void> {
|
|
const stopped = stop(exitCode);
|
|
input.close();
|
|
await stopped;
|
|
}
|