Download src/infra/approval-handler-bootstrap.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 7.6 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/infra/approval-handler-bootstrap.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/infra/approval-handler-bootstrap.ts
-
curl -L -o approval-handler-bootstrap.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/infra/approval-handler-bootstrap.ts
7.6 kB
| // Bootstraps approval handlers from channel plugin capabilities. | |
| import { resolveChannelApprovalCapability } from "../channels/plugins/approvals.js"; | |
| import type { ChannelRuntimeSurface } from "../channels/plugins/channel-runtime-surface.types.js"; | |
| import type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; | |
| import type { OpenClawConfig } from "../config/types.openclaw.js"; | |
| import { createSubsystemLogger } from "../logging/subsystem.js"; | |
| import { withGatewayNativeApprovalRuntime } from "./approval-gateway-runtime-context.js"; | |
| import type { GatewayNativeApprovalRuntime } from "./approval-gateway-runtime.types.js"; | |
| import { | |
| CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY, | |
| createChannelApprovalHandlerFromCapability, | |
| type ChannelApprovalHandler, | |
| } from "./approval-handler-runtime.js"; | |
| import { | |
| getChannelRuntimeContext, | |
| watchChannelRuntimeContexts, | |
| } from "./channel-runtime-context.js"; | |
| import { isExecApprovalChannelRuntimeTerminalStartError } from "./exec-approval-channel-runtime.js"; | |
| type ApprovalBootstrapHandler = ChannelApprovalHandler; | |
| const APPROVAL_HANDLER_BOOTSTRAP_RETRY_MS = 1_000; | |
| function isRetryableApprovalBootstrapStartError(error: unknown): boolean { | |
| const message = String(error); | |
| return ( | |
| message.includes("gateway readiness unavailable before approval client start") || | |
| message.includes("gateway approval client start aborted before readiness") || | |
| message.includes("gateway readiness unavailable before exec approval runtime start") || | |
| message.includes("gateway approval runtime start aborted before readiness") || | |
| message.includes("gateway event loop readiness timeout") || | |
| message.includes("gateway starting") || | |
| message.includes("code=1013") || | |
| message.includes("close code 1013") | |
| ); | |
| } | |
| function formatRetryableApprovalBootstrapStartError(error: unknown): string { | |
| const message = String(error); | |
| if (message.includes("gateway event loop readiness timeout")) { | |
| return "gateway readiness unavailable before approval handler start"; | |
| } | |
| return message; | |
| } | |
| /** Starts the native approval handler for a channel runtime context and returns its cleanup hook. */ | |
| export async function startChannelApprovalHandlerBootstrap(params: { | |
| plugin: Pick<ChannelPlugin, "id" | "meta" | "approvalCapability">; | |
| cfg: OpenClawConfig; | |
| accountId: string; | |
| channelRuntime?: ChannelRuntimeSurface; | |
| gatewayRuntime?: GatewayNativeApprovalRuntime; | |
| logger?: ReturnType<typeof createSubsystemLogger>; | |
| }): Promise<() => Promise<void>> { | |
| const capability = resolveChannelApprovalCapability(params.plugin); | |
| if (!capability?.nativeRuntime || !params.channelRuntime) { | |
| return async () => {}; | |
| } | |
| const channelLabel = params.plugin.meta.label || params.plugin.id; | |
| const logger = params.logger ?? createSubsystemLogger(`${params.plugin.id}/approval-bootstrap`); | |
| let activeGeneration = 0; | |
| let activeHandler: ApprovalBootstrapHandler | null = null; | |
| let retryTimer: NodeJS.Timeout | null = null; | |
| const invalidateActiveHandler = () => { | |
| activeGeneration += 1; | |
| }; | |
| const clearRetryTimer = () => { | |
| if (!retryTimer) { | |
| return; | |
| } | |
| clearTimeout(retryTimer); | |
| retryTimer = null; | |
| }; | |
| const stopHandler = async () => { | |
| const handler = activeHandler; | |
| activeHandler = null; | |
| if (!handler) { | |
| return; | |
| } | |
| await handler.stop(); | |
| }; | |
| const startHandlerForContext = async (context: unknown, generation: number) => { | |
| if (generation !== activeGeneration) { | |
| return; | |
| } | |
| await stopHandler(); | |
| if (generation !== activeGeneration) { | |
| return; | |
| } | |
| const handler = await withGatewayNativeApprovalRuntime(params.gatewayRuntime, () => | |
| createChannelApprovalHandlerFromCapability({ | |
| capability, | |
| label: `${params.plugin.id}/native-approvals`, | |
| clientDisplayName: `${channelLabel} Native Approvals (${params.accountId})`, | |
| channel: params.plugin.id, | |
| channelLabel, | |
| cfg: params.cfg, | |
| accountId: params.accountId, | |
| context, | |
| }), | |
| ); | |
| if (!handler) { | |
| return; | |
| } | |
| if (generation !== activeGeneration) { | |
| // Runtime contexts can unregister while the handler factory awaits; stop stale handlers. | |
| await handler.stop().catch(() => {}); | |
| return; | |
| } | |
| activeHandler = handler as ApprovalBootstrapHandler; | |
| try { | |
| await withGatewayNativeApprovalRuntime(params.gatewayRuntime, () => handler.start()); | |
| } catch (error) { | |
| if (activeHandler === handler) { | |
| activeHandler = null; | |
| } | |
| await handler.stop().catch(() => {}); | |
| throw error; | |
| } | |
| }; | |
| const spawn = (label: string, promise: Promise<void>) => { | |
| void promise.catch((error: unknown) => { | |
| logger.error(`${label}: ${String(error)}`); | |
| }); | |
| }; | |
| const scheduleRetryForContext = (context: unknown, generation: number) => { | |
| if (generation !== activeGeneration) { | |
| return; | |
| } | |
| clearRetryTimer(); | |
| retryTimer = setTimeout(() => { | |
| retryTimer = null; | |
| if (generation !== activeGeneration) { | |
| return; | |
| } | |
| spawn( | |
| "failed to retry native approval handler", | |
| startHandlerForRegisteredContext(context, generation), | |
| ); | |
| }, APPROVAL_HANDLER_BOOTSTRAP_RETRY_MS); | |
| retryTimer.unref?.(); | |
| }; | |
| const startHandlerForRegisteredContext = async (context: unknown, generation: number) => { | |
| try { | |
| await startHandlerForContext(context, generation); | |
| } catch (error) { | |
| if (generation === activeGeneration) { | |
| if (isExecApprovalChannelRuntimeTerminalStartError(error)) { | |
| logger.error(`native approval handler disabled: ${String(error)}`); | |
| return; | |
| } | |
| if (isRetryableApprovalBootstrapStartError(error)) { | |
| logger.warn( | |
| `native approval handler deferred until gateway readiness recovers: ${formatRetryableApprovalBootstrapStartError(error)}`, | |
| ); | |
| scheduleRetryForContext(context, generation); | |
| return; | |
| } | |
| logger.error(`failed to start native approval handler: ${String(error)}`); | |
| scheduleRetryForContext(context, generation); | |
| } | |
| } | |
| }; | |
| const unsubscribe = | |
| watchChannelRuntimeContexts({ | |
| channelRuntime: params.channelRuntime, | |
| channelId: params.plugin.id, | |
| accountId: params.accountId, | |
| capability: CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY, | |
| onEvent: (event) => { | |
| if (event.type === "registered") { | |
| clearRetryTimer(); | |
| invalidateActiveHandler(); | |
| const generation = activeGeneration; | |
| spawn( | |
| "failed to start native approval handler", | |
| startHandlerForRegisteredContext(event.context, generation), | |
| ); | |
| return; | |
| } | |
| clearRetryTimer(); | |
| invalidateActiveHandler(); | |
| spawn("failed to stop native approval handler", stopHandler()); | |
| }, | |
| }) ?? (() => {}); | |
| const existingContext = getChannelRuntimeContext({ | |
| channelRuntime: params.channelRuntime, | |
| channelId: params.plugin.id, | |
| accountId: params.accountId, | |
| capability: CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY, | |
| }); | |
| if (existingContext !== undefined) { | |
| clearRetryTimer(); | |
| invalidateActiveHandler(); | |
| const generation = activeGeneration; | |
| spawn( | |
| "failed to start native approval handler", | |
| startHandlerForRegisteredContext(existingContext, generation), | |
| ); | |
| } | |
| return async () => { | |
| unsubscribe(); | |
| clearRetryTimer(); | |
| invalidateActiveHandler(); | |
| await stopHandler(); | |
| }; | |
| } | |