fix(cycles): split plugin runtime contract leaf types

This commit is contained in:
Vincent Koc
2026-04-11 15:33:15 +01:00
parent aaae1aeb8f
commit 97d1b88e3f
14 changed files with 211 additions and 136 deletions

View File

@@ -0,0 +1,8 @@
import type { RunEmbeddedPiAgentParams } from "./pi-embedded-runner/run/params.js";
import type { EmbeddedPiRunResult } from "./pi-embedded-runner/types.js";
export type RunEmbeddedPiAgentFn = (
params: RunEmbeddedPiAgentParams,
) => Promise<EmbeddedPiRunResult>;
export type RunEmbeddedAgentFn = RunEmbeddedPiAgentFn;

View File

@@ -0,0 +1,3 @@
import type { ShouldHandleTextCommandsParams } from "./commands-registry.types.js";
export type ShouldHandleTextCommands = (params: ShouldHandleTextCommandsParams) => boolean;

View File

@@ -11,6 +11,13 @@ import {
} from "../../shared/string-coerce.js";
import { escapeRegExp } from "../../utils.js";
import type { MsgContext } from "../templating.js";
import type { ExplicitMentionSignal } from "./mentions.types.js";
export type {
BuildMentionRegexes,
ExplicitMentionSignal,
MatchesMentionPatterns,
MatchesMentionWithExplicit,
} from "./mentions.types.js";
function deriveMentionPatterns(identity?: { name?: string; emoji?: string }) {
const patterns: string[] = [];
@@ -150,12 +157,6 @@ export function matchesMentionPatterns(text: string, mentionRegexes: RegExp[]):
return mentionRegexes.some((re) => re.test(cleaned));
}
export type ExplicitMentionSignal = {
hasAnyMention: boolean;
isExplicitlyMentioned: boolean;
canResolveExplicit: boolean;
};
export function matchesMentionWithExplicit(params: {
text: string;
mentionRegexes: RegExp[];

View File

@@ -0,0 +1,18 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js";
export type BuildMentionRegexes = (cfg: OpenClawConfig | undefined, agentId?: string) => RegExp[];
export type MatchesMentionPatterns = (text: string, mentionRegexes: RegExp[]) => boolean;
export type ExplicitMentionSignal = {
hasAnyMention: boolean;
isExplicitlyMentioned: boolean;
canResolveExplicit: boolean;
};
export type MatchesMentionWithExplicit = (params: {
text: string;
mentionRegexes: RegExp[];
explicit?: ExplicitMentionSignal;
transcript?: string;
}) => boolean;

View File

@@ -1,6 +1,8 @@
import type { MsgContext } from "../auto-reply/templating.js";
import type { GroupKeyResolution, SessionEntry } from "../config/sessions/types.js";
import type { GroupKeyResolution } from "../config/sessions/types.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import type { InboundLastRouteUpdate } from "./session.types.js";
export type { InboundLastRouteUpdate, RecordInboundSession } from "./session.types.js";
let inboundSessionRuntimePromise: Promise<
typeof import("../config/sessions/inbound.runtime.js")
@@ -11,19 +13,6 @@ function loadInboundSessionRuntime() {
return inboundSessionRuntimePromise;
}
export type InboundLastRouteUpdate = {
sessionKey: string;
channel: SessionEntry["lastChannel"];
to: string;
accountId?: string;
threadId?: string | number;
mainDmOwnerPin?: {
ownerRecipient: string;
senderRecipient: string;
onSkip?: (params: { ownerRecipient: string; senderRecipient: string }) => void;
};
};
function shouldSkipPinnedMainDmRouteUpdate(
pin: InboundLastRouteUpdate["mainDmOwnerPin"] | undefined,
): boolean {

View File

@@ -0,0 +1,25 @@
import type { MsgContext } from "../auto-reply/templating.js";
import type { GroupKeyResolution, SessionEntry } from "../config/sessions/types.js";
export type InboundLastRouteUpdate = {
sessionKey: string;
channel: SessionEntry["lastChannel"];
to: string;
accountId?: string;
threadId?: string | number;
mainDmOwnerPin?: {
ownerRecipient: string;
senderRecipient: string;
onSkip?: (params: { ownerRecipient: string; senderRecipient: string }) => void;
};
};
export type RecordInboundSession = (params: {
storePath: string;
sessionKey: string;
ctx: MsgContext;
groupResolution?: GroupKeyResolution | null;
createIfMissing?: boolean;
updateLastRoute?: InboundLastRouteUpdate;
onRecordError: (err: unknown) => void;
}) => Promise<void>;

View File

@@ -2,7 +2,7 @@ export * from "./internal-hooks.js";
export type HookEventType = import("./internal-hooks.js").InternalHookEventType;
export type HookEvent = import("./internal-hooks.js").InternalHookEvent;
export type HookHandler = import("./internal-hooks.js").InternalHookHandler;
export type HookHandler = import("./internal-hook-types.js").InternalHookHandler;
export {
registerInternalHook as registerHook,

View File

@@ -0,0 +1,18 @@
export type InternalHookEventType = "command" | "session" | "agent" | "gateway" | "message";
export interface InternalHookEvent {
/** The type of event (command, session, agent, gateway, etc.) */
type: InternalHookEventType;
/** The specific action within the type (e.g., 'new', 'reset', 'stop') */
action: string;
/** The session key this event relates to */
sessionKey: string;
/** Additional context specific to the event */
context: Record<string, unknown>;
/** Timestamp when the event occurred */
timestamp: Date;
/** Messages to send back to the user (hooks can push to this array) */
messages: string[];
}
export type InternalHookHandler = (event: InternalHookEvent) => Promise<void> | void;

View File

@@ -9,12 +9,16 @@ import type { WorkspaceBootstrapFile } from "../agents/workspace.js";
import type { CliDeps } from "../cli/outbound-send-deps.js";
import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { SessionsPatchParams } from "../gateway/protocol/index.js";
import type { SessionsPatchParams } from "../gateway/protocol/schema/types.js";
import { formatErrorMessage } from "../infra/errors.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
export type InternalHookEventType = "command" | "session" | "agent" | "gateway" | "message";
import type {
InternalHookEvent,
InternalHookEventType,
InternalHookHandler,
} from "./internal-hook-types.js";
export type { InternalHookEvent, InternalHookEventType, InternalHookHandler };
export type AgentBootstrapHookContext = {
workspaceDir: string;
@@ -172,23 +176,6 @@ export type SessionPatchHookEvent = InternalHookEvent & {
context: SessionPatchHookContext;
};
export interface InternalHookEvent {
/** The type of event (command, session, agent, gateway, etc.) */
type: InternalHookEventType;
/** The specific action within the type (e.g., 'new', 'reset', 'stop') */
action: string;
/** The session key this event relates to */
sessionKey: string;
/** Additional context specific to the event */
context: Record<string, unknown>;
/** Timestamp when the event occurred */
timestamp: Date;
/** Messages to send back to the user (hooks can push to this array) */
messages: string[];
}
export type InternalHookHandler = (event: InternalHookEvent) => Promise<void> | void;
/**
* Registry of hook handlers by event key.
*

View File

@@ -0,0 +1,87 @@
import type { CliBackendConfig } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
export type PluginTextReplacement = {
from: string | RegExp;
to: string;
};
export type PluginTextTransforms = {
/** Rewrites applied to outbound prompt text before provider/CLI transport. */
input?: PluginTextReplacement[];
/** Rewrites applied to inbound assistant text before OpenClaw consumes it. */
output?: PluginTextReplacement[];
};
export type CliBundleMcpMode =
| "claude-config-file"
| "codex-config-overrides"
| "gemini-system-settings";
/** Plugin-owned CLI backend defaults used by the text-only CLI runner. */
export type CliBackendPlugin = {
/** Provider id used in model refs, for example `claude-cli/opus`. */
id: string;
/** Default backend config before user overrides from `agents.defaults.cliBackends`. */
config: CliBackendConfig;
/**
* Optional live-smoke metadata owned by the backend plugin.
*
* Keep provider-specific test wiring here instead of scattering it across
* Docker wrappers, docs, and gateway live tests.
*/
liveTest?: {
defaultModelRef?: string;
defaultImageProbe?: boolean;
defaultMcpProbe?: boolean;
docker?: {
npmPackage?: string;
binaryName?: string;
};
};
/**
* Whether OpenClaw should inject bundle MCP config for this backend.
*
* Keep this opt-in. Only backends that explicitly consume OpenClaw's bundle
* MCP bridge should enable it.
*/
bundleMcp?: boolean;
/**
* Provider-owned bundle MCP integration strategy.
*
* Different CLIs wire MCP through different surfaces:
* - Claude: `--strict-mcp-config --mcp-config`
* - Codex: `-c mcp_servers=...`
* - Gemini: system-level `settings.json`
*/
bundleMcpMode?: CliBundleMcpMode;
/**
* Optional config normalizer applied after user overrides merge.
*
* Use this for backend-specific compatibility rewrites when old config
* shapes need to stay working.
*/
normalizeConfig?: (config: CliBackendConfig) => CliBackendConfig;
/**
* Backend-owned final system-prompt transform.
*
* Use this for tiny CLI-specific compatibility rewrites without replacing
* the generic CLI runner or prompt builder.
*/
transformSystemPrompt?: (ctx: {
config?: OpenClawConfig;
workspaceDir?: string;
provider: string;
modelId: string;
modelDisplay: string;
agentId?: string;
systemPrompt: string;
}) => string | null | undefined;
/**
* Backend-owned bidirectional text replacements.
*
* `input` applies to the system prompt and user prompt passed to the CLI.
* `output` applies to parsed/streamed assistant text from the CLI.
*/
textTransforms?: PluginTextTransforms;
};

View File

@@ -1,5 +1,5 @@
import { createRequire } from "node:module";
import type { CliBackendPlugin } from "./types.js";
import type { CliBackendPlugin } from "./cli-backend.types.js";
export type PluginCliBackendEntry = CliBackendPlugin & {
pluginId: string;

View File

@@ -9,10 +9,18 @@ type ReadChannelAllowFromStore =
typeof import("../../pairing/pairing-store.js").readChannelAllowFromStore;
type UpsertChannelPairingRequest =
typeof import("../../pairing/pairing-store.js").upsertChannelPairingRequest;
type ShouldHandleTextCommands =
import("../../auto-reply/commands-registry.runtime-types.js").ShouldHandleTextCommands;
type BuildMentionRegexes = import("../../auto-reply/reply/mentions.types.js").BuildMentionRegexes;
type MatchesMentionPatterns =
import("../../auto-reply/reply/mentions.types.js").MatchesMentionPatterns;
type MatchesMentionWithExplicit =
import("../../auto-reply/reply/mentions.types.js").MatchesMentionWithExplicit;
type ReadSessionUpdatedAt = import("../../config/sessions/runtime-types.js").ReadSessionUpdatedAt;
type RecordSessionMetaFromInbound =
import("../../config/sessions/runtime-types.js").RecordSessionMetaFromInbound;
type UpdateLastRoute = import("../../config/sessions/runtime-types.js").UpdateLastRoute;
type RecordInboundSession = import("../../channels/session.types.js").RecordInboundSession;
type ReadChannelAllowFromStoreForAccount = (params: {
channel: Parameters<ReadChannelAllowFromStore>[0];
@@ -112,13 +120,13 @@ export type PluginRuntimeChannel = {
resolveStorePath: typeof import("../../config/sessions/paths.js").resolveStorePath;
readSessionUpdatedAt: ReadSessionUpdatedAt;
recordSessionMetaFromInbound: RecordSessionMetaFromInbound;
recordInboundSession: typeof import("../../channels/session.js").recordInboundSession;
recordInboundSession: RecordInboundSession;
updateLastRoute: UpdateLastRoute;
};
mentions: {
buildMentionRegexes: typeof import("../../auto-reply/reply/mentions.js").buildMentionRegexes;
matchesMentionPatterns: typeof import("../../auto-reply/reply/mentions.js").matchesMentionPatterns;
matchesMentionWithExplicit: typeof import("../../auto-reply/reply/mentions.js").matchesMentionWithExplicit;
buildMentionRegexes: BuildMentionRegexes;
matchesMentionPatterns: MatchesMentionPatterns;
matchesMentionWithExplicit: MatchesMentionWithExplicit;
implicitMentionKindWhen: typeof import("../../channels/mention-gating.js").implicitMentionKindWhen;
resolveInboundMentionDecision: typeof import("../../channels/mention-gating.js").resolveInboundMentionDecision;
};
@@ -138,7 +146,7 @@ export type PluginRuntimeChannel = {
resolveCommandAuthorizedFromAuthorizers: typeof import("../../channels/command-gating.js").resolveCommandAuthorizedFromAuthorizers;
isControlCommandMessage: typeof import("../../auto-reply/command-detection.js").isControlCommandMessage;
shouldComputeCommandAuthorized: typeof import("../../auto-reply/command-detection.js").shouldComputeCommandAuthorized;
shouldHandleTextCommands: typeof import("../../auto-reply/commands-registry.js").shouldHandleTextCommands;
shouldHandleTextCommands: ShouldHandleTextCommands;
};
outbound: {
loadAdapter: import("../../channels/plugins/outbound/load.types.js").LoadChannelOutboundAdapter;

View File

@@ -1,3 +1,7 @@
import type {
RunEmbeddedAgentFn,
RunEmbeddedPiAgentFn,
} from "../../agents/pi-embedded-runtime.types.js";
import type { HeartbeatRunResult } from "../../infra/heartbeat-wake.js";
import type { LogLevel } from "../../logging/levels.js";
@@ -49,8 +53,8 @@ export type PluginRuntimeCore = {
model: string;
catalog?: import("../../agents/model-catalog.types.js").ModelCatalogEntry[];
}) => import("../../auto-reply/thinking.js").ThinkLevel;
runEmbeddedAgent: typeof import("../../agents/embedded-agent.js").runEmbeddedAgent;
runEmbeddedPiAgent: typeof import("../../agents/pi-embedded.js").runEmbeddedPiAgent;
runEmbeddedAgent: RunEmbeddedAgentFn;
runEmbeddedPiAgent: RunEmbeddedPiAgentFn;
resolveAgentTimeoutMs: typeof import("../../agents/timeout.js").resolveAgentTimeoutMs;
ensureAgentWorkspace: typeof import("../../agents/workspace.js").ensureAgentWorkspace;
session: {

View File

@@ -23,13 +23,13 @@ import type { ThinkLevel } from "../auto-reply/thinking.shared.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import type { ChannelId } from "../channels/plugins/types.public.js";
import type { CliBackendConfig, ModelProviderConfig } from "../config/types.js";
import type { ModelProviderConfig } from "../config/types.js";
import type { ModelCompatConfig } from "../config/types.models.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { TtsAutoMode } from "../config/types.tts.js";
import type { OperatorScope } from "../gateway/operator-scopes.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import type { InternalHookHandler } from "../hooks/internal-hooks.js";
import type { InternalHookHandler } from "../hooks/internal-hook-types.js";
import type { ImageGenerationProvider } from "../image-generation/types.js";
import type { ProviderUsageSnapshot } from "../infra/provider-usage.types.js";
import type { MediaUnderstandingProvider } from "../media-understanding/types.js";
@@ -70,6 +70,12 @@ import type {
} from "../tts/provider-types.js";
import type { VideoGenerationProvider } from "../video-generation/types.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import type {
CliBackendPlugin,
CliBundleMcpMode,
PluginTextReplacement,
PluginTextTransforms,
} from "./cli-backend.types.js";
import type {
PluginConversationBinding,
PluginConversationBindingRequestParams,
@@ -165,6 +171,12 @@ export type {
PluginConversationBindingResolvedEvent,
PluginConversationBindingResolutionDecision,
} from "./conversation-binding.types.js";
export type {
CliBackendPlugin,
CliBundleMcpMode,
PluginTextReplacement,
PluginTextTransforms,
} from "./cli-backend.types.js";
export type ProviderAuthOptionBag = {
token?: string;
@@ -1110,18 +1122,6 @@ export type ProviderTransformSystemPromptContext = ProviderSystemPromptContribut
systemPrompt: string;
};
export type PluginTextReplacement = {
from: string | RegExp;
to: string;
};
export type PluginTextTransforms = {
/** Rewrites applied to outbound prompt text before provider/CLI transport. */
input?: PluginTextReplacement[];
/** Rewrites applied to inbound assistant text before OpenClaw consumes it. */
output?: PluginTextReplacement[];
};
export type PluginTextTransformRegistration = PluginTextTransforms;
/** Text-inference provider capability registered by a plugin. */
@@ -1898,79 +1898,6 @@ export type OpenClawPluginService = {
stop?: (ctx: OpenClawPluginServiceContext) => void | Promise<void>;
};
export type CliBundleMcpMode =
| "claude-config-file"
| "codex-config-overrides"
| "gemini-system-settings";
/** Plugin-owned CLI backend defaults used by the text-only CLI runner. */
export type CliBackendPlugin = {
/** Provider id used in model refs, for example `claude-cli/opus`. */
id: string;
/** Default backend config before user overrides from `agents.defaults.cliBackends`. */
config: CliBackendConfig;
/**
* Optional live-smoke metadata owned by the backend plugin.
*
* Keep provider-specific test wiring here instead of scattering it across
* Docker wrappers, docs, and gateway live tests.
*/
liveTest?: {
defaultModelRef?: string;
defaultImageProbe?: boolean;
defaultMcpProbe?: boolean;
docker?: {
npmPackage?: string;
binaryName?: string;
};
};
/**
* Whether OpenClaw should inject bundle MCP config for this backend.
*
* Keep this opt-in. Only backends that explicitly consume OpenClaw's bundle
* MCP bridge should enable it.
*/
bundleMcp?: boolean;
/**
* Provider-owned bundle MCP integration strategy.
*
* Different CLIs wire MCP through different surfaces:
* - Claude: `--strict-mcp-config --mcp-config`
* - Codex: `-c mcp_servers=...`
* - Gemini: system-level `settings.json`
*/
bundleMcpMode?: CliBundleMcpMode;
/**
* Optional config normalizer applied after user overrides merge.
*
* Use this for backend-specific compatibility rewrites when old config
* shapes need to stay working.
*/
normalizeConfig?: (config: CliBackendConfig) => CliBackendConfig;
/**
* Backend-owned final system-prompt transform.
*
* Use this for tiny CLI-specific compatibility rewrites without replacing
* the generic CLI runner or prompt builder.
*/
transformSystemPrompt?: (ctx: {
config?: OpenClawConfig;
workspaceDir?: string;
provider: string;
modelId: string;
modelDisplay: string;
agentId?: string;
systemPrompt: string;
}) => string | null | undefined;
/**
* Backend-owned bidirectional text replacements.
*
* `input` applies to the system prompt and user prompt passed to the CLI.
* `output` applies to parsed/streamed assistant text from the CLI.
*/
textTransforms?: PluginTextTransforms;
};
export type OpenClawPluginChannelRegistration = {
plugin: ChannelPlugin;
};