

























@@ -201,6 +201,7 @@ const MAX_PERMISSION_ALLOW_ALWAYS_ENTRIES = 512;
201201const MAX_NATIVE_HOOK_BRIDGE_BODY_BYTES = 5_000_000;
202202const MAX_NATIVE_HOOK_BRIDGE_RESPONSE_BYTES = 5_000_000;
203203const NATIVE_HOOK_BRIDGE_RETRY_INTERVAL_MS = 25;
204+const NATIVE_HOOK_BRIDGE_REPLACEMENT_RECORD_GRACE_MS = 250;
204205const NATIVE_HOOK_RELAY_BRIDGE_STALE_REGISTRATION_ERROR =
205206"native hook relay bridge stale registration";
206207const ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
@@ -717,7 +718,12 @@ export async function invokeNativeHookRelayBridge(
717718) {
718719break;
719720}
720-if (!isRetryableNativeHookRelayBridgeError(error)) {
721+if (
722+!isRetryableNativeHookRelayBridgeLookupError({
723+ error,
724+elapsedMs: Date.now() - startedAt,
725+})
726+) {
721727break;
722728}
723729await delay(
@@ -894,7 +900,9 @@ function registerNativeHookRelayBridge(registration: ActiveNativeHookRelayRegist
894900} catch (error) {
895901log.debug("native hook relay bridge dir prune skipped", { error });
896902}
897-unregisterNativeHookRelayBridge(registration.relayId);
903+unregisterNativeHookRelayBridge(registration.relayId, {
904+deferRegistryRemovalMs: NATIVE_HOOK_BRIDGE_REPLACEMENT_RECORD_GRACE_MS,
905+});
898906const token = randomUUID();
899907const bridgeDir = ensureNativeHookRelayBridgeDir();
900908const bridgeKey = nativeHookRelayBridgeKey(registration.relayId);
@@ -954,7 +962,10 @@ function writeNativeHookRelayBridgeRecordForRegistration(
954962writeNativeHookRelayBridgeRecord(bridge.registryPath, record);
955963}
956964957-function unregisterNativeHookRelayBridge(relayId: string): void {
965+function unregisterNativeHookRelayBridge(
966+relayId: string,
967+options?: { deferRegistryRemovalMs?: number },
968+): void {
958969const bridge = relayBridges.get(relayId);
959970if (!bridge) {
960971return;
@@ -963,6 +974,19 @@ function unregisterNativeHookRelayBridge(relayId: string): void {
963974bridge.server.close();
964975const record = readNativeHookRelayBridgeRecordIfExists(relayId);
965976if (record?.token === bridge.token) {
977+const deferRegistryRemovalMs = normalizePositiveInteger(options?.deferRegistryRemovalMs, 0);
978+if (deferRegistryRemovalMs > 0) {
979+// During stable-id replacement, leave the old record in place until the
980+// new bridge writes over it. Hook subprocesses can then retry
981+// ECONNREFUSED/stale-registration instead of observing a missing relay.
982+const timeout = setTimeout(() => {
983+if (readNativeHookRelayBridgeRecordIfExists(relayId)?.token === bridge.token) {
984+rmSync(bridge.registryPath, { force: true });
985+}
986+}, deferRegistryRemovalMs);
987+timeout.unref();
988+return;
989+}
966990rmSync(bridge.registryPath, { force: true });
967991}
968992}
@@ -1221,6 +1245,17 @@ function isRetryableNativeHookRelayBridgeError(error: unknown): boolean {
12211245);
12221246}
122312471248+function isRetryableNativeHookRelayBridgeLookupError(params: {
1249+error: unknown;
1250+elapsedMs: number;
1251+}): boolean {
1252+return (
1253+isRetryableNativeHookRelayBridgeError(params.error) ||
1254+(params.elapsedMs < NATIVE_HOOK_BRIDGE_REPLACEMENT_RECORD_GRACE_MS &&
1255+isNativeHookRelayBridgeStaleRegistrationError(params.error))
1256+);
1257+}
1258+12241259function nativeHookRelayBridgeDir(): string {
12251260const uid = typeof process.getuid === "function" ? process.getuid() : "nouid";
12261261return path.join(tmpdir(), `openclaw-native-hook-relays-${uid}`);
@@ -2235,6 +2270,9 @@ export const testing = {
22352270const record = readNativeHookRelayBridgeRecordIfExists(relayId);
22362271return record ? { ...record } : undefined;
22372272},
2273+isNativeHookRelayBridgeLookupRetryableForTests(error: unknown, elapsedMs = 0): boolean {
2274+return isRetryableNativeHookRelayBridgeLookupError({ error, elapsedMs });
2275+},
22382276formatPermissionApprovalDescriptionForTests(
22392277request: NativeHookRelayPermissionApprovalRequest,
22402278): string {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。