fix(cycles): repair broken type surfaces

This commit is contained in:
Vincent Koc
2026-04-11 13:41:05 +01:00
parent 355794c24a
commit 81535d394d
23 changed files with 167 additions and 119 deletions
@@ -4,7 +4,7 @@ import {
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { loadConfig, writeConfigFile } from "../config/config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveGatewayAuth } from "../gateway/auth.js";
import { ensureGatewayStartupAuth } from "../gateway/startup-auth.js";
+1 -1
View File
@@ -5,7 +5,7 @@ import {
readBrowserVersion,
resolveGoogleChromeExecutableForPlatform,
} from "./browser/chrome.executables.js";
import type { OpenClawConfig } from "./config/types.openclaw.js";
import type { OpenClawConfig } from "./config/config.js";
import { asRecord } from "./record-shared.js";
const CHROME_MCP_MIN_MAJOR = 144;
+4 -1
View File
@@ -1,6 +1,7 @@
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
import {
normalizeLowercaseStringOrEmpty,
@@ -172,7 +173,9 @@ export function createDiscordApprovalCapability(configOverride?: DiscordExecAppr
return createApproverRestrictedNativeApprovalCapability({
channel: "discord",
channelLabel: "Discord",
describeExecApprovalSetup: ({ accountId }) => {
describeExecApprovalSetup: ({
accountId,
}: Parameters<NonNullable<ChannelApprovalCapability["describeExecApprovalSetup"]>>[0]) => {
const prefix =
accountId && accountId !== "default"
? `channels.discord.accounts.${accountId}`
+13 -4
View File
@@ -9,6 +9,7 @@ import {
createChannelNativeOriginTargetResolver,
resolveApprovalRequestSessionConversation,
} from "openclaw/plugin-sdk/approval-native-runtime";
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
import {
normalizeLowercaseStringOrEmpty,
@@ -195,7 +196,9 @@ function resolveMatrixApproverDmTargets(params: {
const matrixNativeApprovalCapability = createApproverRestrictedNativeApprovalCapability({
channel: "matrix",
channelLabel: "Matrix",
describeExecApprovalSetup: ({ accountId }) => {
describeExecApprovalSetup: ({
accountId,
}: Parameters<NonNullable<ChannelApprovalCapability["describeExecApprovalSetup"]>>[0]) => {
const prefix =
accountId && accountId !== "default"
? `channels.matrix.accounts.${accountId}`
@@ -293,7 +296,9 @@ const matrixNativeAdapter = matrixBaseNativeApprovalAdapter && {
};
export const matrixApprovalCapability = createChannelApprovalCapability({
authorizeActorAction: (params) => {
authorizeActorAction: (
params: Parameters<NonNullable<ChannelApprovalCapability["authorizeActorAction"]>>[0],
) => {
if (params.approvalKind !== "plugin") {
return matrixNativeApprovalCapability.authorizeActorAction?.(params) ?? { authorized: true };
}
@@ -310,7 +315,9 @@ export const matrixApprovalCapability = createChannelApprovalCapability({
}
return matrixApprovalAuth.authorizeActorAction(params);
},
getActionAvailabilityState: (params) => {
getActionAvailabilityState: (
params: Parameters<NonNullable<ChannelApprovalCapability["getActionAvailabilityState"]>>[0],
) => {
if (params.approvalKind === "plugin") {
return availabilityState(
hasMatrixPluginApprovers({
@@ -325,7 +332,9 @@ export const matrixApprovalCapability = createChannelApprovalCapability({
}
);
},
getExecInitiatingSurfaceState: (params) =>
getExecInitiatingSurfaceState: (
params: Parameters<NonNullable<ChannelApprovalCapability["getExecInitiatingSurfaceState"]>>[0],
) =>
matrixNativeApprovalCapability.getExecInitiatingSurfaceState?.(params) ??
({ kind: "disabled" } as const),
describeExecApprovalSetup: matrixNativeApprovalCapability.describeExecApprovalSetup,
+4 -1
View File
@@ -9,6 +9,7 @@ import {
createChannelNativeOriginTargetResolver,
resolveApprovalRequestSessionConversation,
} from "openclaw/plugin-sdk/approval-native-runtime";
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
import {
normalizeLowercaseStringOrEmpty,
@@ -148,7 +149,9 @@ const resolveSlackApproverDmTargets = createChannelApproverDmTargetResolver({
export const slackApprovalCapability = createApproverRestrictedNativeApprovalCapability({
channel: "slack",
channelLabel: "Slack",
describeExecApprovalSetup: ({ accountId }) => {
describeExecApprovalSetup: ({
accountId,
}: Parameters<NonNullable<ChannelApprovalCapability["describeExecApprovalSetup"]>>[0]) => {
const prefix =
accountId && accountId !== "default"
? `channels.slack.accounts.${accountId}`
+5 -2
View File
@@ -91,7 +91,7 @@ const resolveTelegramApproverDmTargets = createChannelApproverDmTargetResolver({
const telegramNativeApprovalCapability = createApproverRestrictedNativeApprovalCapability({
channel: "telegram",
channelLabel: "Telegram",
describeExecApprovalSetup: ({ accountId }) => {
describeExecApprovalSetup: ({ accountId }: { accountId?: string | null }) => {
const prefix =
accountId && accountId !== "default"
? `channels.telegram.accounts.${accountId}`
@@ -137,7 +137,10 @@ const telegramNativeApprovalCapability = createApproverRestrictedNativeApprovalC
const resolveTelegramApproveCommandBehavior: NonNullable<
ChannelApprovalCapability["resolveApproveCommandBehavior"]
> = ({ cfg, accountId, senderId, approvalKind }) => {
> = (
params: Parameters<NonNullable<ChannelApprovalCapability["resolveApproveCommandBehavior"]>>[0],
) => {
const { cfg, accountId, senderId, approvalKind } = params;
if (approvalKind !== "exec") {
return undefined;
}
@@ -306,8 +306,9 @@ const telegramApproveTestPlugin: ChannelPlugin = {
},
config: {
listAccountIds: listConfiguredTelegramAccountIds,
resolveAccount: (cfg, accountId) => resolveTelegramTestAccount(cfg, accountId),
defaultAccountId: (cfg) =>
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>
resolveTelegramTestAccount(cfg, accountId),
defaultAccountId: (cfg: OpenClawConfig) =>
(cfg.channels?.telegram as TelegramTestSectionConfig | undefined)?.defaultAccount ??
DEFAULT_ACCOUNT_ID,
},
+7 -1
View File
@@ -1,6 +1,12 @@
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
import {
CHANNEL_MESSAGE_ACTION_NAMES,
type ChannelMessageActionName as ChannelMessageActionNameFromList,
} from "./message-action-names.js";
export type * from "./types.core.js";
export type * from "./types.adapters.js";
export type { ChannelMessageCapability } from "./message-capabilities.js";
export { CHANNEL_MESSAGE_ACTION_NAMES };
export type { ChannelPlugin } from "./types.plugin.js";
export type ChannelMessageActionName = ChannelMessageActionNameFromList;
+1
View File
@@ -6,6 +6,7 @@ import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../shared/string-coerce.js";
export { getChatChannelMeta, listChatChannels } from "./chat-meta.js";
import {
CHANNEL_IDS,
CHAT_CHANNEL_ALIASES,
+2
View File
@@ -2,6 +2,8 @@ import type { OutboundSendDeps } from "../infra/outbound/send-deps.js";
import type { CliDeps } from "./deps.types.js";
import { createOutboundSendDepsFromCliSource } from "./outbound-send-mapping.js";
export type { CliDeps } from "./deps.types.js";
export function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
return createOutboundSendDepsFromCliSource(deps);
}
+1
View File
@@ -17,6 +17,7 @@ import {
} from "../routing/session-key.js";
import type { RuntimeEnv } from "../runtime.js";
import type { ChannelRuntimeSnapshot } from "./server-channel-runtime.types.js";
export type { ChannelRuntimeSnapshot } from "./server-channel-runtime.types.js";
const CHANNEL_RESTART_POLICY: BackoffPolicy = {
initialMs: 5_000,
+1 -1
View File
@@ -6,7 +6,7 @@ import type {
ExpiredApprovalView,
PendingApprovalView,
ResolvedApprovalView,
} from "./approval-view-model.js";
} from "./approval-view-model.types.js";
import type { ExecApprovalChannelRuntimeEventKind } from "./exec-approval-channel-runtime.js";
import type { ExecApprovalRequest, ExecApprovalResolved } from "./exec-approvals.js";
import type { PluginApprovalRequest, PluginApprovalResolved } from "./plugin-approvals.js";
+7 -7
View File
@@ -23,18 +23,18 @@ import {
createChannelNativeApprovalRuntime,
type PreparedChannelNativeApprovalTarget,
} from "./approval-native-runtime.js";
import {
buildExpiredApprovalView,
buildPendingApprovalView,
buildResolvedApprovalView,
} from "./approval-view-model.js";
import type {
ApprovalActionView,
ApprovalMetadataView,
ExpiredApprovalView,
PendingApprovalView,
ResolvedApprovalView,
} from "./approval-view-model.js";
import {
buildExpiredApprovalView,
buildPendingApprovalView,
buildResolvedApprovalView,
} from "./approval-view-model.js";
} from "./approval-view-model.types.js";
import type {
ExecApprovalChannelRuntime,
ExecApprovalChannelRuntimeEventKind,
@@ -53,7 +53,7 @@ export type {
PluginApprovalPendingView,
PluginApprovalResolvedView,
ResolvedApprovalView,
} from "./approval-view-model.js";
} from "./approval-view-model.types.js";
export { resolveApprovalOverGateway };
export {
CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY,
+12 -84
View File
@@ -1,95 +1,23 @@
import type { ChannelApprovalKind } from "./approval-types.js";
import type {
ApprovalMetadataView,
ApprovalRequest,
ApprovalResolved,
ExecApprovalViewBase,
ExpiredApprovalView,
PendingApprovalView,
PluginApprovalViewBase,
ResolvedApprovalView,
} from "./approval-view-model.types.js";
import { resolveExecApprovalCommandDisplay } from "./exec-approval-command-display.js";
import {
buildExecApprovalActionDescriptors,
type ExecApprovalActionDescriptor,
} from "./exec-approval-reply.js";
import { buildExecApprovalActionDescriptors } from "./exec-approval-reply.js";
import {
resolveExecApprovalRequestAllowedDecisions,
type ExecApprovalDecision,
type ExecApprovalRequest,
type ExecApprovalResolved,
} from "./exec-approvals.js";
import type { PluginApprovalRequest, PluginApprovalResolved } from "./plugin-approvals.js";
import type { PluginApprovalRequest } from "./plugin-approvals.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalResolved = ExecApprovalResolved | PluginApprovalResolved;
type ApprovalPhase = "pending" | "resolved" | "expired";
export type ApprovalActionView = ExecApprovalActionDescriptor;
export type ApprovalMetadataView = {
label: string;
value: string;
};
type ApprovalViewBase = {
approvalId: string;
approvalKind: ChannelApprovalKind;
phase: "pending" | "resolved" | "expired";
title: string;
description?: string | null;
metadata: ApprovalMetadataView[];
};
type ExecApprovalViewBase = ApprovalViewBase & {
approvalKind: "exec";
ask?: string | null;
agentId?: string | null;
commandText: string;
commandPreview?: string | null;
cwd?: string | null;
envKeys?: readonly string[];
host?: string | null;
nodeId?: string | null;
sessionKey?: string | null;
};
export type ExecApprovalPendingView = ExecApprovalViewBase & {
phase: "pending";
actions: ApprovalActionView[];
expiresAtMs: number;
};
export type ExecApprovalResolvedView = ExecApprovalViewBase & {
phase: "resolved";
decision: ExecApprovalDecision;
resolvedBy?: string | null;
};
export type ExecApprovalExpiredView = ExecApprovalViewBase & {
phase: "expired";
};
type PluginApprovalViewBase = ApprovalViewBase & {
approvalKind: "plugin";
agentId?: string | null;
pluginId?: string | null;
toolName?: string | null;
severity: "info" | "warning" | "critical";
};
export type PluginApprovalPendingView = PluginApprovalViewBase & {
phase: "pending";
actions: ApprovalActionView[];
expiresAtMs: number;
};
export type PluginApprovalResolvedView = PluginApprovalViewBase & {
phase: "resolved";
decision: ExecApprovalDecision;
resolvedBy?: string | null;
};
export type PluginApprovalExpiredView = PluginApprovalViewBase & {
phase: "expired";
};
export type PendingApprovalView = ExecApprovalPendingView | PluginApprovalPendingView;
export type ResolvedApprovalView = ExecApprovalResolvedView | PluginApprovalResolvedView;
export type ExpiredApprovalView = ExecApprovalExpiredView | PluginApprovalExpiredView;
export type ApprovalViewModel = PendingApprovalView | ResolvedApprovalView | ExpiredApprovalView;
function buildExecMetadata(request: ExecApprovalRequest): ApprovalMetadataView[] {
const metadata: ApprovalMetadataView[] = [];
if (request.request.agentId) {
+87
View File
@@ -0,0 +1,87 @@
import type { ChannelApprovalKind } from "./approval-types.js";
import type { ExecApprovalActionDescriptor } from "./exec-approval-reply.js";
import type {
ExecApprovalDecision,
ExecApprovalRequest,
ExecApprovalResolved,
} from "./exec-approvals.js";
import type { PluginApprovalRequest, PluginApprovalResolved } from "./plugin-approvals.js";
type ApprovalPhase = "pending" | "resolved" | "expired";
export type ApprovalActionView = ExecApprovalActionDescriptor;
export type ApprovalMetadataView = {
label: string;
value: string;
};
export type ApprovalViewBase = {
approvalId: string;
approvalKind: ChannelApprovalKind;
phase: ApprovalPhase;
title: string;
description?: string | null;
metadata: ApprovalMetadataView[];
};
export type ExecApprovalViewBase = ApprovalViewBase & {
approvalKind: "exec";
ask?: string | null;
agentId?: string | null;
commandText: string;
commandPreview?: string | null;
cwd?: string | null;
envKeys?: readonly string[];
host?: string | null;
nodeId?: string | null;
sessionKey?: string | null;
};
export type ExecApprovalPendingView = ExecApprovalViewBase & {
phase: "pending";
actions: ApprovalActionView[];
expiresAtMs: number;
};
export type ExecApprovalResolvedView = ExecApprovalViewBase & {
phase: "resolved";
decision: ExecApprovalDecision;
resolvedBy?: string | null;
};
export type ExecApprovalExpiredView = ExecApprovalViewBase & {
phase: "expired";
};
export type PluginApprovalViewBase = ApprovalViewBase & {
approvalKind: "plugin";
agentId?: string | null;
pluginId?: string | null;
toolName?: string | null;
severity: "info" | "warning" | "critical";
};
export type PluginApprovalPendingView = PluginApprovalViewBase & {
phase: "pending";
actions: ApprovalActionView[];
expiresAtMs: number;
};
export type PluginApprovalResolvedView = PluginApprovalViewBase & {
phase: "resolved";
decision: ExecApprovalDecision;
resolvedBy?: string | null;
};
export type PluginApprovalExpiredView = PluginApprovalViewBase & {
phase: "expired";
};
export type PendingApprovalView = ExecApprovalPendingView | PluginApprovalPendingView;
export type ResolvedApprovalView = ExecApprovalResolvedView | PluginApprovalResolvedView;
export type ExpiredApprovalView = ExecApprovalExpiredView | PluginApprovalExpiredView;
export type ApprovalViewModel = PendingApprovalView | ResolvedApprovalView | ExpiredApprovalView;
export type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
export type ApprovalResolved = ExecApprovalResolved | PluginApprovalResolved;
@@ -114,7 +114,7 @@ function resolveChannelSupportsCurrentConversationBinding(channel: string): bool
if (!normalized) {
return false;
}
const matchesPluginId = (plugin: { id: string; meta?: { aliases?: readonly string[] } }) =>
const matchesPluginId = (plugin: { id: string; meta?: { aliases?: readonly string[] } | null }) =>
plugin.id === normalized ||
(plugin.meta?.aliases ?? []).some(
(alias) => normalizeOptionalLowercaseString(alias) === normalized,
@@ -122,7 +122,7 @@ function resolveChannelSupportsCurrentConversationBinding(channel: string): bool
// Read the already-installed runtime channel registry from shared state only.
// Importing plugins/runtime here creates a module cycle through plugin-sdk
// surfaces during bundled channel discovery.
const plugin = getActivePluginChannelRegistryFromState()?.channels.find((entry) =>
const plugin = (getActivePluginChannelRegistryFromState()?.channels ?? []).find((entry) =>
matchesPluginId(entry.plugin),
)?.plugin;
if (plugin?.conversationBindings?.supportsCurrentConversationBinding === true) {
+1 -1
View File
@@ -205,7 +205,7 @@ export function createChannelApprovalCapability(params: {
render?: ChannelApprovalCapability["render"];
native?: ChannelApprovalCapability["native"];
/** @deprecated Pass delivery/nativeRuntime/render/native directly. */
approvals?: ChannelApprovalCapabilitySurfaces;
approvals?: Partial<ChannelApprovalCapabilitySurfaces>;
}): ChannelApprovalCapability {
const surfaces: ChannelApprovalCapabilitySurfaces = {
delivery: params.delivery ?? params.approvals?.delivery,
-1
View File
@@ -54,7 +54,6 @@ export type {
ChannelStatusIssue,
} from "../channels/plugins/types.public.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { createChannelReplyPipeline } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
+5 -2
View File
@@ -8,11 +8,14 @@ export type RuntimeTrackedPluginRecord = {
export type RuntimeTrackedChannelEntry = {
plugin: {
id?: string | null;
id: string;
meta?: {
aliases?: string[];
aliases?: readonly string[];
markdownCapable?: boolean;
} | null;
conversationBindings?: {
supportsCurrentConversationBinding?: boolean;
} | null;
};
};
+6 -6
View File
@@ -45,7 +45,7 @@ export function recordImportedPluginId(pluginId: string): void {
function installSurfaceRegistry(
surface: RegistrySurfaceState,
registry: PluginRegistry | null,
registry: RegistryState["activeRegistry"],
pinned: boolean,
) {
if (surface.registry === registry && surface.pinned === pinned) {
@@ -58,7 +58,7 @@ function installSurfaceRegistry(
function syncTrackedSurface(
surface: RegistrySurfaceState,
registry: PluginRegistry | null,
registry: RegistryState["activeRegistry"],
refreshVersion = false,
) {
if (surface.pinned) {
@@ -103,7 +103,7 @@ export function requireActivePluginRegistry(): PluginRegistry {
syncTrackedSurface(state.httpRoute, state.activeRegistry);
syncTrackedSurface(state.channel, state.activeRegistry);
}
return state.activeRegistry;
return asPluginRegistry(state.activeRegistry)!;
}
export function pinActivePluginHttpRouteRegistry(registry: PluginRegistry) {
@@ -223,9 +223,9 @@ function collectLoadedPluginIds(
*/
export function listImportedRuntimePluginIds(): string[] {
const imported = new Set(state.importedPluginIds);
collectLoadedPluginIds(state.activeRegistry, imported);
collectLoadedPluginIds(state.channel.registry, imported);
collectLoadedPluginIds(state.httpRoute.registry, imported);
collectLoadedPluginIds(asPluginRegistry(state.activeRegistry), imported);
collectLoadedPluginIds(asPluginRegistry(state.channel.registry), imported);
collectLoadedPluginIds(asPluginRegistry(state.httpRoute.registry), imported);
return [...imported].toSorted((left, right) => left.localeCompare(right));
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { normalizeProviderId } from "../agents/provider-id.js";
import type { PluginRegistry } from "./registry-types.js";
import { getPluginRegistryState } from "./runtime-state.js";
const BUNDLED_SYNTHETIC_AUTH_PROVIDER_REFS = ["claude-cli", "ollama", "xai"] as const;
@@ -18,7 +19,7 @@ function uniqueProviderRefs(values: readonly string[]): string[] {
}
export function resolveRuntimeSyntheticAuthProviderRefs(): string[] {
const registry = getPluginRegistryState()?.activeRegistry;
const registry = getPluginRegistryState()?.activeRegistry as PluginRegistry | null | undefined;
if (registry) {
return uniqueProviderRefs([
...(registry.providers ?? [])
+1
View File
@@ -1,5 +1,6 @@
import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeMessageChannel } from "./message-channel.js";
export {
deliveryContextFromSession,
deliveryContextKey,
+1 -1
View File
@@ -1,6 +1,6 @@
import { getChatChannelMeta } from "../channels/chat-meta.js";
import {
CHANNEL_IDS,
getChatChannelMeta,
getRegisteredChannelPluginMeta,
listRegisteredChannelPluginAliases,
listRegisteredChannelPluginIds,