diff --git a/src/agents/pi-embedded-runner/compact.types.ts b/src/agents/pi-embedded-runner/compact.types.ts index a615050f7f0..e0dbd86b2cc 100644 --- a/src/agents/pi-embedded-runner/compact.types.ts +++ b/src/agents/pi-embedded-runner/compact.types.ts @@ -1,7 +1,7 @@ import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import type { enqueueCommand } from "../../process/command-queue.js"; -import type { ExecElevatedDefaults } from "../bash-tools.js"; +import type { CommandQueueEnqueueFn } from "../../process/command-queue.types.js"; +import type { ExecElevatedDefaults } from "../bash-tools.exec-types.js"; import type { SkillSnapshot } from "../skills.js"; export type CompactEmbeddedPiSessionParams = { @@ -50,7 +50,7 @@ export type CompactEmbeddedPiSessionParams = { attempt?: number; maxAttempts?: number; lane?: string; - enqueue?: typeof enqueueCommand; + enqueue?: CommandQueueEnqueueFn; extraSystemPrompt?: string; ownerNumbers?: string[]; abortSignal?: AbortSignal; diff --git a/src/agents/pi-embedded-runner/run/params.ts b/src/agents/pi-embedded-runner/run/params.ts index db98c4672e3..8499456acdd 100644 --- a/src/agents/pi-embedded-runner/run/params.ts +++ b/src/agents/pi-embedded-runner/run/params.ts @@ -4,9 +4,9 @@ import type { ReasoningLevel, ThinkLevel, VerboseLevel } from "../../../auto-rep import type { ReplyPayload } from "../../../auto-reply/types.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import type { PromptImageOrderEntry } from "../../../media/prompt-image-order.js"; -import type { enqueueCommand } from "../../../process/command-queue.js"; +import type { CommandQueueEnqueueFn } from "../../../process/command-queue.types.js"; import type { InputProvenance } from "../../../sessions/input-provenance.js"; -import type { ExecElevatedDefaults, ExecToolDefaults } from "../../bash-tools.js"; +import type { ExecElevatedDefaults, ExecToolDefaults } from "../../bash-tools.exec-types.js"; import type { AgentStreamParams, ClientToolDefinition } from "../../command/shared-types.js"; import type { AgentInternalEvent } from "../../internal-events.js"; import type { BlockReplyPayload } from "../../pi-embedded-payloads.js"; @@ -113,7 +113,7 @@ export type RunEmbeddedPiAgentParams = { onToolResult?: (payload: ReplyPayload) => void | Promise; onAgentEvent?: (evt: { stream: string; data: Record }) => void; lane?: string; - enqueue?: typeof enqueueCommand; + enqueue?: CommandQueueEnqueueFn; extraSystemPrompt?: string; internalEvents?: AgentInternalEvent[]; inputProvenance?: InputProvenance; diff --git a/src/channels/plugins/setup-helpers.ts b/src/channels/plugins/setup-helpers.ts index 88ec18149f0..8605dbf7b71 100644 --- a/src/channels/plugins/setup-helpers.ts +++ b/src/channels/plugins/setup-helpers.ts @@ -1,9 +1,10 @@ import { z, type ZodType } from "zod"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; -import { getBundledChannelPlugin } from "./bundled.js"; -import { getChannelPlugin } from "./registry.js"; +import { + resolveSingleAccountKeysToMove, + resolveSingleAccountPromotionTarget, +} from "./setup-promotion-helpers.js"; import type { ChannelSetupAdapter } from "./types.adapters.js"; import type { ChannelSetupInput } from "./types.core.js"; @@ -377,136 +378,6 @@ type ChannelSectionRecord = Record & { accounts?: Record>; }; -const COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set([ - "name", - "token", - "tokenFile", - "botToken", - "appToken", - "account", - "signalNumber", - "authDir", - "cliPath", - "dbPath", - "httpUrl", - "httpHost", - "httpPort", - "webhookPath", - "webhookUrl", - "webhookSecret", - "service", - "region", - "homeserver", - "userId", - "accessToken", - "password", - "deviceName", - "url", - "code", - "dmPolicy", - "allowFrom", - "groupPolicy", - "groupAllowFrom", - "defaultTo", -]); - -const BUNDLED_SINGLE_ACCOUNT_PROMOTION_FALLBACKS: Record = { - // Some setup/migration paths run before the channel setup surface has been loaded. - telegram: ["streaming"], -}; - -const BUNDLED_NAMED_ACCOUNT_PROMOTION_FALLBACKS: Record = { - // Keep top-level Telegram policy fallback intact when only auth needs seeding. - telegram: ["botToken", "tokenFile"], -}; - -type ChannelSetupPromotionSurface = { - singleAccountKeysToMove?: readonly string[]; - namedAccountPromotionKeys?: readonly string[]; - resolveSingleAccountPromotionTarget?: (params: { - channel: ChannelSectionBase; - }) => string | undefined; -}; - -function getChannelSetupPromotionSurface(channelKey: string): ChannelSetupPromotionSurface | null { - const setup = getChannelPlugin(channelKey)?.setup ?? getBundledChannelPlugin(channelKey)?.setup; - if (!setup || typeof setup !== "object") { - return null; - } - return setup as ChannelSetupPromotionSurface; -} - -export function shouldMoveSingleAccountChannelKey(params: { - channelKey: string; - key: string; -}): boolean { - if (COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE.has(params.key)) { - return true; - } - const contractKeys = getChannelSetupPromotionSurface(params.channelKey)?.singleAccountKeysToMove; - if (contractKeys?.includes(params.key)) { - return true; - } - const fallbackKeys = BUNDLED_SINGLE_ACCOUNT_PROMOTION_FALLBACKS[params.channelKey]; - if (fallbackKeys?.includes(params.key)) { - return true; - } - return false; -} - -export function resolveSingleAccountKeysToMove(params: { - channelKey: string; - channel: Record; -}): string[] { - const hasNamedAccounts = - Object.keys((params.channel.accounts as Record) ?? {}).filter(Boolean).length > - 0; - const namedAccountPromotionKeys = - getChannelSetupPromotionSurface(params.channelKey)?.namedAccountPromotionKeys ?? - BUNDLED_NAMED_ACCOUNT_PROMOTION_FALLBACKS[params.channelKey]; - return Object.entries(params.channel) - .filter(([key, value]) => { - if (key === "accounts" || key === "enabled" || value === undefined) { - return false; - } - if (!shouldMoveSingleAccountChannelKey({ channelKey: params.channelKey, key })) { - return false; - } - if ( - hasNamedAccounts && - namedAccountPromotionKeys && - !namedAccountPromotionKeys.includes(key) - ) { - return false; - } - return true; - }) - .map(([key]) => key); -} - -export function resolveSingleAccountPromotionTarget(params: { - channelKey: string; - channel: ChannelSectionBase; -}): string { - const accounts = params.channel.accounts ?? {}; - const resolveExistingAccountId = (targetAccountId: string): string => { - const normalizedTargetAccountId = normalizeAccountId(targetAccountId); - const matchedAccountId = Object.keys(accounts).find( - (accountId) => normalizeAccountId(accountId) === normalizedTargetAccountId, - ); - return matchedAccountId ?? normalizedTargetAccountId; - }; - const surface = getChannelSetupPromotionSurface(params.channelKey); - const resolved = surface?.resolveSingleAccountPromotionTarget?.({ - channel: params.channel, - }); - const normalizedResolved = normalizeOptionalString(resolved); - if (normalizedResolved) { - return resolveExistingAccountId(normalizedResolved); - } - return resolveExistingAccountId(DEFAULT_ACCOUNT_ID); -} - function cloneIfObject(value: T): T { if (value && typeof value === "object") { return structuredClone(value); diff --git a/src/channels/plugins/setup-promotion-helpers.ts b/src/channels/plugins/setup-promotion-helpers.ts new file mode 100644 index 00000000000..76dfa17cda1 --- /dev/null +++ b/src/channels/plugins/setup-promotion-helpers.ts @@ -0,0 +1,139 @@ +import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { getBundledChannelPlugin } from "./bundled.js"; +import { getChannelPlugin } from "./registry.js"; + +type ChannelSectionBase = { + defaultAccount?: string; + accounts?: Record>; +}; + +const COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set([ + "name", + "token", + "tokenFile", + "botToken", + "appToken", + "account", + "signalNumber", + "authDir", + "cliPath", + "dbPath", + "httpUrl", + "httpHost", + "httpPort", + "webhookPath", + "webhookUrl", + "webhookSecret", + "service", + "region", + "homeserver", + "userId", + "accessToken", + "password", + "deviceName", + "url", + "code", + "dmPolicy", + "allowFrom", + "groupPolicy", + "groupAllowFrom", + "defaultTo", +]); + +const BUNDLED_SINGLE_ACCOUNT_PROMOTION_FALLBACKS: Record = { + // Some setup/migration paths run before the channel setup surface has been loaded. + telegram: ["streaming"], +}; + +const BUNDLED_NAMED_ACCOUNT_PROMOTION_FALLBACKS: Record = { + // Keep top-level Telegram policy fallback intact when only auth needs seeding. + telegram: ["botToken", "tokenFile"], +}; + +type ChannelSetupPromotionSurface = { + singleAccountKeysToMove?: readonly string[]; + namedAccountPromotionKeys?: readonly string[]; + resolveSingleAccountPromotionTarget?: (params: { + channel: ChannelSectionBase; + }) => string | undefined; +}; + +function getChannelSetupPromotionSurface(channelKey: string): ChannelSetupPromotionSurface | null { + const setup = getChannelPlugin(channelKey)?.setup ?? getBundledChannelPlugin(channelKey)?.setup; + if (!setup || typeof setup !== "object") { + return null; + } + return setup as ChannelSetupPromotionSurface; +} + +export function shouldMoveSingleAccountChannelKey(params: { + channelKey: string; + key: string; +}): boolean { + if (COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE.has(params.key)) { + return true; + } + const contractKeys = getChannelSetupPromotionSurface(params.channelKey)?.singleAccountKeysToMove; + if (contractKeys?.includes(params.key)) { + return true; + } + const fallbackKeys = BUNDLED_SINGLE_ACCOUNT_PROMOTION_FALLBACKS[params.channelKey]; + if (fallbackKeys?.includes(params.key)) { + return true; + } + return false; +} + +export function resolveSingleAccountKeysToMove(params: { + channelKey: string; + channel: Record; +}): string[] { + const hasNamedAccounts = + Object.keys((params.channel.accounts as Record) ?? {}).filter(Boolean).length > + 0; + const namedAccountPromotionKeys = + getChannelSetupPromotionSurface(params.channelKey)?.namedAccountPromotionKeys ?? + BUNDLED_NAMED_ACCOUNT_PROMOTION_FALLBACKS[params.channelKey]; + return Object.entries(params.channel) + .filter(([key, value]) => { + if (key === "accounts" || key === "enabled" || value === undefined) { + return false; + } + if (!shouldMoveSingleAccountChannelKey({ channelKey: params.channelKey, key })) { + return false; + } + if ( + hasNamedAccounts && + namedAccountPromotionKeys && + !namedAccountPromotionKeys.includes(key) + ) { + return false; + } + return true; + }) + .map(([key]) => key); +} + +export function resolveSingleAccountPromotionTarget(params: { + channelKey: string; + channel: ChannelSectionBase; +}): string { + const accounts = params.channel.accounts ?? {}; + const resolveExistingAccountId = (targetAccountId: string): string => { + const normalizedTargetAccountId = normalizeAccountId(targetAccountId); + const matchedAccountId = Object.keys(accounts).find( + (accountId) => normalizeAccountId(accountId) === normalizedTargetAccountId, + ); + return matchedAccountId ?? normalizedTargetAccountId; + }; + const surface = getChannelSetupPromotionSurface(params.channelKey); + const resolved = surface?.resolveSingleAccountPromotionTarget?.({ + channel: params.channel, + }); + const normalizedResolved = normalizeOptionalString(resolved); + if (normalizedResolved) { + return resolveExistingAccountId(normalizedResolved); + } + return resolveExistingAccountId(DEFAULT_ACCOUNT_ID); +} diff --git a/src/commands/doctor/shared/legacy-config-core-normalizers.ts b/src/commands/doctor/shared/legacy-config-core-normalizers.ts index ee961dd2b84..f269832ef89 100644 --- a/src/commands/doctor/shared/legacy-config-core-normalizers.ts +++ b/src/commands/doctor/shared/legacy-config-core-normalizers.ts @@ -1,5 +1,5 @@ import { normalizeProviderId } from "../../../agents/model-selection-normalize.js"; -import { resolveSingleAccountKeysToMove } from "../../../channels/plugins/setup-helpers.js"; +import { resolveSingleAccountKeysToMove } from "../../../channels/plugins/setup-promotion-helpers.js"; import { resolveNormalizedProviderModelMaxTokens } from "../../../config/defaults.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import { DEFAULT_GOOGLE_API_BASE_URL } from "../../../infra/google-api-base-url.js"; diff --git a/src/gateway/method-scopes.ts b/src/gateway/method-scopes.ts index 8cfc424abf3..3f81958cc27 100644 --- a/src/gateway/method-scopes.ts +++ b/src/gateway/method-scopes.ts @@ -1,4 +1,4 @@ -import { getActivePluginRegistry } from "../plugins/runtime.js"; +import { getPluginRegistryState } from "../plugins/runtime-state.js"; import { resolveReservedGatewayMethodScope } from "../shared/gateway-method-policy.js"; import { ADMIN_SCOPE, @@ -185,7 +185,7 @@ function resolveScopedMethod(method: string): OperatorScope | undefined { if (reservedScope) { return reservedScope; } - const pluginScope = getActivePluginRegistry()?.gatewayMethodScopes?.[method]; + const pluginScope = getPluginRegistryState()?.activeRegistry?.gatewayMethodScopes?.[method]; if (pluginScope) { return pluginScope; } diff --git a/src/process/command-queue.types.ts b/src/process/command-queue.types.ts new file mode 100644 index 00000000000..5e600554e02 --- /dev/null +++ b/src/process/command-queue.types.ts @@ -0,0 +1,7 @@ +export type CommandQueueEnqueueFn = ( + task: () => Promise, + opts?: { + warnAfterMs?: number; + onWait?: (waitMs: number, queuedAhead: number) => void; + }, +) => Promise;