From 774beb8e5c3ddce093a91cc588cb1dfb3c24788a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 3 Apr 2026 02:11:21 +0900 Subject: [PATCH] refactor(plugin-sdk): add task domain runtime surfaces (#59805) * refactor(plugin-sdk): add task domain runtime views * chore(plugin-sdk): refresh api baseline * fix(plugin-sdk): preserve task runtime owner isolation --- docs/.generated/plugin-sdk-api-baseline.json | 214 +++++++++++++- docs/.generated/plugin-sdk-api-baseline.jsonl | 38 ++- src/plugin-sdk/core.ts | 15 + src/plugin-sdk/index.ts | 15 + src/plugins/runtime/index.test.ts | 10 +- src/plugins/runtime/index.ts | 8 +- src/plugins/runtime/runtime-tasks.test.ts | 236 ++++++++++++++++ src/plugins/runtime/runtime-tasks.ts | 263 ++++++++++++++++++ src/plugins/runtime/task-domain-types.ts | 83 ++++++ src/plugins/runtime/types-core.ts | 4 + src/tasks/task-domain-views.ts | 95 +++++++ test/helpers/plugins/plugin-runtime-mock.ts | 8 + 12 files changed, 969 insertions(+), 20 deletions(-) create mode 100644 src/plugins/runtime/runtime-tasks.test.ts create mode 100644 src/plugins/runtime/runtime-tasks.ts create mode 100644 src/plugins/runtime/task-domain-types.ts create mode 100644 src/tasks/task-domain-views.ts diff --git a/docs/.generated/plugin-sdk-api-baseline.json b/docs/.generated/plugin-sdk-api-baseline.json index b29299b24c1..d3bf03e8e8e 100644 --- a/docs/.generated/plugin-sdk-api-baseline.json +++ b/docs/.generated/plugin-sdk-api-baseline.json @@ -50,6 +50,24 @@ "path": "src/agents/tools/common.ts" } }, + { + "declaration": "export type BoundTaskFlowsRuntime = BoundTaskFlowsRuntime;", + "exportName": "BoundTaskFlowsRuntime", + "kind": "type", + "source": { + "line": 74, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, + { + "declaration": "export type BoundTaskRunsRuntime = BoundTaskRunsRuntime;", + "exportName": "BoundTaskRunsRuntime", + "kind": "type", + "source": { + "line": 54, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, { "declaration": "export type ChannelAccountSnapshot = ChannelAccountSnapshot;", "exportName": "ChannelAccountSnapshot", @@ -446,6 +464,33 @@ "path": "src/plugins/runtime/types.ts" } }, + { + "declaration": "export type PluginRuntimeTaskFlows = PluginRuntimeTaskFlows;", + "exportName": "PluginRuntimeTaskFlows", + "kind": "type", + "source": { + "line": 84, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, + { + "declaration": "export type PluginRuntimeTaskRuns = PluginRuntimeTaskRuns;", + "exportName": "PluginRuntimeTaskRuns", + "kind": "type", + "source": { + "line": 64, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, + { + "declaration": "export type PluginRuntimeTasks = PluginRuntimeTasks;", + "exportName": "PluginRuntimeTasks", + "kind": "type", + "source": { + "line": 94, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, { "declaration": "export type ProviderAuthContext = ProviderAuthContext;", "exportName": "ProviderAuthContext", @@ -590,6 +635,60 @@ "path": "src/plugins/runtime/types.ts" } }, + { + "declaration": "export type TaskFlowDetail = TaskFlowDetail;", + "exportName": "TaskFlowDetail", + "kind": "type", + "source": { + "line": 74, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskFlowView = TaskFlowView;", + "exportName": "TaskFlowView", + "kind": "type", + "source": { + "line": 60, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunAggregateSummary = TaskRunAggregateSummary;", + "exportName": "TaskRunAggregateSummary", + "kind": "type", + "source": { + "line": 14, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunCancelResult = TaskRunCancelResult;", + "exportName": "TaskRunCancelResult", + "kind": "type", + "source": { + "line": 53, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunDetail = TaskRunView;", + "exportName": "TaskRunDetail", + "kind": "type", + "source": { + "line": 51, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunView = TaskRunView;", + "exportName": "TaskRunView", + "kind": "type", + "source": { + "line": 23, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, { "declaration": "export type TranscriptRewriteReplacement = TranscriptRewriteReplacement;", "exportName": "TranscriptRewriteReplacement", @@ -3378,7 +3477,7 @@ "exportName": "buildChannelOutboundSessionRoute", "kind": "function", "source": { - "line": 191, + "line": 206, "path": "src/plugin-sdk/core.ts" } }, @@ -3423,7 +3522,7 @@ "exportName": "createChannelPluginBase", "kind": "function", "source": { - "line": 548, + "line": 563, "path": "src/plugin-sdk/core.ts" } }, @@ -3432,7 +3531,7 @@ "exportName": "createChatChannelPlugin", "kind": "function", "source": { - "line": 521, + "line": 536, "path": "src/plugin-sdk/core.ts" } }, @@ -3459,7 +3558,7 @@ "exportName": "defineChannelPluginEntry", "kind": "function", "source": { - "line": 285, + "line": 300, "path": "src/plugin-sdk/core.ts" } }, @@ -3477,7 +3576,7 @@ "exportName": "defineSetupPluginEntry", "kind": "function", "source": { - "line": 328, + "line": 343, "path": "src/plugin-sdk/core.ts" } }, @@ -3702,7 +3801,7 @@ "exportName": "stripChannelTargetPrefix", "kind": "function", "source": { - "line": 171, + "line": 186, "path": "src/plugin-sdk/core.ts" } }, @@ -3711,7 +3810,7 @@ "exportName": "stripTargetKindPrefix", "kind": "function", "source": { - "line": 183, + "line": 198, "path": "src/plugin-sdk/core.ts" } }, @@ -3751,6 +3850,24 @@ "path": "src/agents/tools/common.ts" } }, + { + "declaration": "export type BoundTaskFlowsRuntime = BoundTaskFlowsRuntime;", + "exportName": "BoundTaskFlowsRuntime", + "kind": "type", + "source": { + "line": 74, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, + { + "declaration": "export type BoundTaskRunsRuntime = BoundTaskRunsRuntime;", + "exportName": "BoundTaskRunsRuntime", + "kind": "type", + "source": { + "line": 54, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, { "declaration": "export type ChannelConfigUiHint = ChannelConfigUiHint;", "exportName": "ChannelConfigUiHint", @@ -3792,7 +3909,7 @@ "exportName": "ChannelOutboundSessionRouteParams", "kind": "type", "source": { - "line": 166, + "line": 181, "path": "src/plugin-sdk/core.ts" } }, @@ -3949,6 +4066,33 @@ "path": "src/plugins/runtime/types.ts" } }, + { + "declaration": "export type PluginRuntimeTaskFlows = PluginRuntimeTaskFlows;", + "exportName": "PluginRuntimeTaskFlows", + "kind": "type", + "source": { + "line": 84, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, + { + "declaration": "export type PluginRuntimeTaskRuns = PluginRuntimeTaskRuns;", + "exportName": "PluginRuntimeTaskRuns", + "kind": "type", + "source": { + "line": 64, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, + { + "declaration": "export type PluginRuntimeTasks = PluginRuntimeTasks;", + "exportName": "PluginRuntimeTasks", + "kind": "type", + "source": { + "line": 94, + "path": "src/plugins/runtime/runtime-tasks.ts" + } + }, { "declaration": "export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;", "exportName": "ProviderAugmentModelCatalogContext", @@ -4336,6 +4480,60 @@ "path": "src/shared/tailscale-status.ts" } }, + { + "declaration": "export type TaskFlowDetail = TaskFlowDetail;", + "exportName": "TaskFlowDetail", + "kind": "type", + "source": { + "line": 74, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskFlowView = TaskFlowView;", + "exportName": "TaskFlowView", + "kind": "type", + "source": { + "line": 60, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunAggregateSummary = TaskRunAggregateSummary;", + "exportName": "TaskRunAggregateSummary", + "kind": "type", + "source": { + "line": 14, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunCancelResult = TaskRunCancelResult;", + "exportName": "TaskRunCancelResult", + "kind": "type", + "source": { + "line": 53, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunDetail = TaskRunView;", + "exportName": "TaskRunDetail", + "kind": "type", + "source": { + "line": 51, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, + { + "declaration": "export type TaskRunView = TaskRunView;", + "exportName": "TaskRunView", + "kind": "type", + "source": { + "line": 23, + "path": "src/plugins/runtime/task-domain-types.ts" + } + }, { "declaration": "export type UsageProviderId = UsageProviderId;", "exportName": "UsageProviderId", diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index c8d51f43cec..c2fab774042 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -4,6 +4,8 @@ {"declaration":"export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => void): () => void;","entrypoint":"index","exportName":"onDiagnosticEvent","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":229,"sourcePath":"src/infra/diagnostic-events.ts"} {"declaration":"export function registerContextEngine(id: string, factory: ContextEngineFactory): ContextEngineRegistrationResult;","entrypoint":"index","exportName":"registerContextEngine","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":377,"sourcePath":"src/context-engine/registry.ts"} {"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"index","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"} +{"declaration":"export type BoundTaskFlowsRuntime = BoundTaskFlowsRuntime;","entrypoint":"index","exportName":"BoundTaskFlowsRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":74,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} +{"declaration":"export type BoundTaskRunsRuntime = BoundTaskRunsRuntime;","entrypoint":"index","exportName":"BoundTaskRunsRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} {"declaration":"export type ChannelAccountSnapshot = ChannelAccountSnapshot;","entrypoint":"index","exportName":"ChannelAccountSnapshot","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":147,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelAgentTool = ChannelAgentTool;","entrypoint":"index","exportName":"ChannelAgentTool","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelAgentToolFactory = ChannelAgentToolFactory;","entrypoint":"index","exportName":"ChannelAgentToolFactory","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":24,"sourcePath":"src/channels/plugins/types.core.ts"} @@ -48,6 +50,9 @@ {"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"index","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":105,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"index","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"index","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"} +{"declaration":"export type PluginRuntimeTaskFlows = PluginRuntimeTaskFlows;","entrypoint":"index","exportName":"PluginRuntimeTaskFlows","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":84,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} +{"declaration":"export type PluginRuntimeTaskRuns = PluginRuntimeTaskRuns;","entrypoint":"index","exportName":"PluginRuntimeTaskRuns","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":64,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} +{"declaration":"export type PluginRuntimeTasks = PluginRuntimeTasks;","entrypoint":"index","exportName":"PluginRuntimeTasks","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":94,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} {"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"index","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":180,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"index","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":165,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"index","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":321,"sourcePath":"src/plugins/types.ts"} @@ -64,6 +69,12 @@ {"declaration":"export type StatefulBindingTargetSessionResult = StatefulBindingTargetSessionResult;","entrypoint":"index","exportName":"StatefulBindingTargetSessionResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":8,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"} {"declaration":"export type SubagentRunParams = SubagentRunParams;","entrypoint":"index","exportName":"SubagentRunParams","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":8,"sourcePath":"src/plugins/runtime/types.ts"} {"declaration":"export type SubagentRunResult = SubagentRunResult;","entrypoint":"index","exportName":"SubagentRunResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/plugins/runtime/types.ts"} +{"declaration":"export type TaskFlowDetail = TaskFlowDetail;","entrypoint":"index","exportName":"TaskFlowDetail","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":74,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskFlowView = TaskFlowView;","entrypoint":"index","exportName":"TaskFlowView","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunAggregateSummary = TaskRunAggregateSummary;","entrypoint":"index","exportName":"TaskRunAggregateSummary","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":14,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunCancelResult = TaskRunCancelResult;","entrypoint":"index","exportName":"TaskRunCancelResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunDetail = TaskRunView;","entrypoint":"index","exportName":"TaskRunDetail","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":51,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunView = TaskRunView;","entrypoint":"index","exportName":"TaskRunView","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":23,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} {"declaration":"export type TranscriptRewriteReplacement = TranscriptRewriteReplacement;","entrypoint":"index","exportName":"TranscriptRewriteReplacement","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":61,"sourcePath":"src/context-engine/types.ts"} {"declaration":"export type TranscriptRewriteRequest = TranscriptRewriteRequest;","entrypoint":"index","exportName":"TranscriptRewriteRequest","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":68,"sourcePath":"src/context-engine/types.ts"} {"declaration":"export type TranscriptRewriteResult = TranscriptRewriteResult;","entrypoint":"index","exportName":"TranscriptRewriteResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":73,"sourcePath":"src/context-engine/types.ts"} @@ -371,18 +382,18 @@ {"declaration":"export function applyAccountNameToChannelSection(params: { cfg: OpenClawConfig; channelKey: string; accountId: string; name?: string | undefined; alwaysUseAccounts?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"applyAccountNameToChannelSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":34,"sourcePath":"src/channels/plugins/setup-helpers.ts"} {"declaration":"export function buildAgentSessionKey(params: { agentId: string; channel: string; accountId?: string | null | undefined; peer?: RoutePeer | null | undefined; dmScope?: \"main\" | \"per-peer\" | \"per-channel-peer\" | \"per-account-channel-peer\" | undefined; identityLinks?: Record<...> | undefined; }): string;","entrypoint":"core","exportName":"buildAgentSessionKey","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":92,"sourcePath":"src/routing/resolve-route.ts"} {"declaration":"export function buildChannelConfigSchema(schema: ZodType>, options?: BuildChannelConfigSchemaOptions | undefined): ChannelConfigSchema;","entrypoint":"core","exportName":"buildChannelConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":76,"sourcePath":"src/channels/plugins/config-schema.ts"} -{"declaration":"export function buildChannelOutboundSessionRoute(params: { cfg: OpenClawConfig; agentId: string; channel: string; accountId?: string | null | undefined; peer: { kind: \"direct\" | \"group\" | \"channel\"; id: string; }; chatType: \"direct\" | \"group\" | \"channel\"; from: string; to: string; threadId?: string | ... 1 more ... | undefined; }): ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"buildChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":191,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function buildChannelOutboundSessionRoute(params: { cfg: OpenClawConfig; agentId: string; channel: string; accountId?: string | null | undefined; peer: { kind: \"direct\" | \"group\" | \"channel\"; id: string; }; chatType: \"direct\" | \"group\" | \"channel\"; from: string; to: string; threadId?: string | ... 1 more ... | undefined; }): ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"buildChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":206,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function buildPluginConfigSchema(schema: ZodType>, options?: BuildPluginConfigSchemaOptions | undefined): OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"buildPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":78,"sourcePath":"src/plugins/config-schema.ts"} {"declaration":"export function channelTargetSchema(options?: { description?: string | undefined; } | undefined): TString;","entrypoint":"core","exportName":"channelTargetSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/agents/schema/typebox.ts"} {"declaration":"export function channelTargetsSchema(options?: { description?: string | undefined; } | undefined): TArray;","entrypoint":"core","exportName":"channelTargetsSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":44,"sourcePath":"src/agents/schema/typebox.ts"} {"declaration":"export function clearAccountEntryFields(params: { accounts?: Record | undefined; accountId: string; fields: string[]; isValueSet?: ((value: unknown) => boolean) | undefined; markClearedOnFieldPresence?: boolean | undefined; }): { ...; };","entrypoint":"core","exportName":"clearAccountEntryFields","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/config-helpers.ts"} -{"declaration":"export function createChannelPluginBase(params: CreateChannelPluginBaseOptions): CreatedChannelPluginBase;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":548,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":521,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createChannelPluginBase(params: CreateChannelPluginBaseOptions): CreatedChannelPluginBase;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":563,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":536,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function createDedupeCache(options: DedupeCacheOptions): DedupeCache;","entrypoint":"core","exportName":"createDedupeCache","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":17,"sourcePath":"src/infra/dedupe.ts"} {"declaration":"export function createSubsystemLogger(subsystem: string): SubsystemLogger;","entrypoint":"core","exportName":"createSubsystemLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":308,"sourcePath":"src/logging/subsystem.ts"} -{"declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":285,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":300,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":151,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} -{"declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":328,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":343,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function delegateCompactionToRuntime(params: { sessionId: string; sessionKey?: string | undefined; sessionFile: string; tokenBudget?: number | undefined; force?: boolean | undefined; currentTokenCount?: number | undefined; compactionTarget?: \"budget\" | ... 1 more ... | undefined; customInstructions?: string | undefined; runtimeContext?: ContextEngineRuntimeContext | undefined; }): Promise<...>;","entrypoint":"core","exportName":"delegateCompactionToRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/context-engine/delegate.ts"} {"declaration":"export function deleteAccountFromConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; clearBaseFields?: string[] | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"deleteAccountFromConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/config-helpers.ts"} {"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":108,"sourcePath":"src/plugins/config-schema.ts"} @@ -407,17 +418,19 @@ {"declaration":"export function resolveThreadSessionKeys(params: { baseSessionKey: string; threadId?: string | null | undefined; parentSessionKey?: string | undefined; useSuffix?: boolean | undefined; normalizeThreadId?: ((threadId: string) => string) | undefined; }): { ...; };","entrypoint":"core","exportName":"resolveThreadSessionKeys","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":234,"sourcePath":"src/routing/session-key.ts"} {"declaration":"export function setAccountEnabledInConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; enabled: boolean; allowTopLevel?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"setAccountEnabledInConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/channels/plugins/config-helpers.ts"} {"declaration":"export function stringEnum(values: T, options?: StringEnumOptions): TUnsafe;","entrypoint":"core","exportName":"stringEnum","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":15,"sourcePath":"src/agents/schema/typebox.ts"} -{"declaration":"export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string;","entrypoint":"core","exportName":"stripChannelTargetPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":171,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function stripTargetKindPrefix(raw: string): string;","entrypoint":"core","exportName":"stripTargetKindPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":183,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string;","entrypoint":"core","exportName":"stripChannelTargetPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":186,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function stripTargetKindPrefix(raw: string): string;","entrypoint":"core","exportName":"stripTargetKindPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":198,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function tryReadSecretFileSync(filePath: string | undefined, label: string, options?: SecretFileReadOptions): string | undefined;","entrypoint":"core","exportName":"tryReadSecretFileSync","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":130,"sourcePath":"src/infra/secret-file.ts"} {"declaration":"export const DEFAULT_ACCOUNT_ID: \"default\";","entrypoint":"core","exportName":"DEFAULT_ACCOUNT_ID","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export","sourceLine":3,"sourcePath":"src/routing/account-id.ts"} {"declaration":"export const DEFAULT_SECRET_FILE_MAX_BYTES: number;","entrypoint":"core","exportName":"DEFAULT_SECRET_FILE_MAX_BYTES","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export","sourceLine":5,"sourcePath":"src/infra/secret-file.ts"} {"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"core","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"} +{"declaration":"export type BoundTaskFlowsRuntime = BoundTaskFlowsRuntime;","entrypoint":"core","exportName":"BoundTaskFlowsRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":74,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} +{"declaration":"export type BoundTaskRunsRuntime = BoundTaskRunsRuntime;","entrypoint":"core","exportName":"BoundTaskRunsRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} {"declaration":"export type ChannelConfigUiHint = ChannelConfigUiHint;","entrypoint":"core","exportName":"ChannelConfigUiHint","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":40,"sourcePath":"src/channels/plugins/types.plugin.ts"} {"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"core","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":520,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessagingAdapter = ChannelMessagingAdapter;","entrypoint":"core","exportName":"ChannelMessagingAdapter","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":398,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelOutboundSessionRoute = ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"ChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":312,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; resolvedTarget?: { to: string; kind: import(\"../channels/plugins/types.core.js\").ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":166,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; resolvedTarget?: { to: string; kind: import(\"../channels/plugins/types.core.js\").ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":181,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"core","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":81,"sourcePath":"src/channels/plugins/types.plugin.ts"} {"declaration":"export type GatewayBindUrlResult = GatewayBindUrlResult;","entrypoint":"core","exportName":"GatewayBindUrlResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/gateway-bind-url.ts"} {"declaration":"export type GatewayRequestHandlerOptions = GatewayRequestHandlerOptions;","entrypoint":"core","exportName":"GatewayRequestHandlerOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":115,"sourcePath":"src/gateway/server-methods/types.ts"} @@ -435,6 +448,9 @@ {"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"core","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1628,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"core","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"core","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"} +{"declaration":"export type PluginRuntimeTaskFlows = PluginRuntimeTaskFlows;","entrypoint":"core","exportName":"PluginRuntimeTaskFlows","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":84,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} +{"declaration":"export type PluginRuntimeTaskRuns = PluginRuntimeTaskRuns;","entrypoint":"core","exportName":"PluginRuntimeTaskRuns","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":64,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} +{"declaration":"export type PluginRuntimeTasks = PluginRuntimeTasks;","entrypoint":"core","exportName":"PluginRuntimeTasks","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":94,"sourcePath":"src/plugins/runtime/runtime-tasks.ts"} {"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"core","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":801,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"core","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":180,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"core","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":519,"sourcePath":"src/plugins/types.ts"} @@ -478,6 +494,12 @@ {"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"core","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1451,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type TailscaleStatusCommandResult = TailscaleStatusCommandResult;","entrypoint":"core","exportName":"TailscaleStatusCommandResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/shared/tailscale-status.ts"} {"declaration":"export type TailscaleStatusCommandRunner = TailscaleStatusCommandRunner;","entrypoint":"core","exportName":"TailscaleStatusCommandRunner","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/shared/tailscale-status.ts"} +{"declaration":"export type TaskFlowDetail = TaskFlowDetail;","entrypoint":"core","exportName":"TaskFlowDetail","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":74,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskFlowView = TaskFlowView;","entrypoint":"core","exportName":"TaskFlowView","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunAggregateSummary = TaskRunAggregateSummary;","entrypoint":"core","exportName":"TaskRunAggregateSummary","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":14,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunCancelResult = TaskRunCancelResult;","entrypoint":"core","exportName":"TaskRunCancelResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunDetail = TaskRunView;","entrypoint":"core","exportName":"TaskRunDetail","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":51,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} +{"declaration":"export type TaskRunView = TaskRunView;","entrypoint":"core","exportName":"TaskRunView","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":23,"sourcePath":"src/plugins/runtime/task-domain-types.ts"} {"declaration":"export type UsageProviderId = UsageProviderId;","entrypoint":"core","exportName":"UsageProviderId","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":20,"sourcePath":"src/infra/provider-usage.types.ts"} {"declaration":"export type UsageWindow = UsageWindow;","entrypoint":"core","exportName":"UsageWindow","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/infra/provider-usage.types.ts"} {"declaration":"export class KeyedAsyncQueue","entrypoint":"core","exportName":"KeyedAsyncQueue","importSpecifier":"openclaw/plugin-sdk/core","kind":"class","recordType":"export","sourceLine":34,"sourcePath":"src/plugin-sdk/keyed-async-queue.ts"} diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index af9c0c19a34..e407b4b72c1 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -108,6 +108,21 @@ export type { export type { ChannelMessageActionContext } from "../channels/plugins/types.js"; export type { ChannelConfigUiHint, ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { + BoundTaskFlowsRuntime, + BoundTaskRunsRuntime, + PluginRuntimeTaskFlows, + PluginRuntimeTaskRuns, + PluginRuntimeTasks, +} from "../plugins/runtime/runtime-tasks.js"; +export type { + TaskFlowDetail, + TaskFlowView, + TaskRunAggregateSummary, + TaskRunCancelResult, + TaskRunDetail, + TaskRunView, +} from "../plugins/runtime/task-domain-types.js"; export { definePluginEntry } from "./plugin-entry.js"; export { buildPluginConfigSchema, emptyPluginConfigSchema } from "../plugins/config-schema.js"; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 592b30a38d1..32e4e9a7ffe 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -59,6 +59,21 @@ export type { SubagentRunParams, SubagentRunResult, } from "../plugins/runtime/types.js"; +export type { + BoundTaskFlowsRuntime, + BoundTaskRunsRuntime, + PluginRuntimeTaskFlows, + PluginRuntimeTaskRuns, + PluginRuntimeTasks, +} from "../plugins/runtime/runtime-tasks.js"; +export type { + TaskFlowDetail, + TaskFlowView, + TaskRunAggregateSummary, + TaskRunCancelResult, + TaskRunDetail, + TaskRunView, +} from "../plugins/runtime/task-domain-types.js"; export type { OpenClawConfig } from "../config/config.js"; /** @deprecated Use OpenClawConfig instead */ export type { OpenClawConfig as ClawdbotConfig } from "../config/config.js"; diff --git a/src/plugins/runtime/index.test.ts b/src/plugins/runtime/index.test.ts index cd5494037e5..eaf3a8de00d 100644 --- a/src/plugins/runtime/index.test.ts +++ b/src/plugins/runtime/index.test.ts @@ -190,8 +190,16 @@ describe("plugin runtime command execution", () => { }, }, { - name: "exposes runtime.tasks.flow as the canonical TaskFlow runtime and keeps runtime.taskFlow as an alias", + name: "exposes canonical runtime.tasks.runs and runtime.tasks.flows while keeping legacy TaskFlow aliases", assert: (runtime: ReturnType) => { + expectFunctionKeys(runtime.tasks.runs as Record, [ + "bindSession", + "fromToolContext", + ]); + expectFunctionKeys(runtime.tasks.flows as Record, [ + "bindSession", + "fromToolContext", + ]); expectFunctionKeys(runtime.tasks.flow as Record, [ "bindSession", "fromToolContext", diff --git a/src/plugins/runtime/index.ts b/src/plugins/runtime/index.ts index 52585ece13a..4694f409be4 100644 --- a/src/plugins/runtime/index.ts +++ b/src/plugins/runtime/index.ts @@ -17,6 +17,7 @@ import { createRuntimeLogging } from "./runtime-logging.js"; import { createRuntimeMedia } from "./runtime-media.js"; import { createRuntimeSystem } from "./runtime-system.js"; import { createRuntimeTaskFlow } from "./runtime-taskflow.js"; +import { createRuntimeTasks } from "./runtime-tasks.js"; import type { PluginRuntime } from "./types.js"; const loadTtsRuntime = createLazyRuntimeModule(() => import("./runtime-tts.runtime.js")); @@ -185,6 +186,9 @@ export type CreatePluginRuntimeOptions = { export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): PluginRuntime { const mediaUnderstanding = createRuntimeMediaUnderstandingFacade(); const taskFlow = createRuntimeTaskFlow(); + const tasks = createRuntimeTasks({ + legacyTaskFlow: taskFlow, + }); const runtime = { // Sourced from the shared OpenClaw version resolver (#52899) so plugins // always see the same version the CLI reports, avoiding API-version drift. @@ -205,9 +209,7 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): events: createRuntimeEvents(), logging: createRuntimeLogging(), state: { resolveStateDir }, - tasks: { - flow: taskFlow, - }, + tasks, taskFlow, } satisfies Omit< PluginRuntime, diff --git a/src/plugins/runtime/runtime-tasks.test.ts b/src/plugins/runtime/runtime-tasks.test.ts new file mode 100644 index 00000000000..4f32988bc7c --- /dev/null +++ b/src/plugins/runtime/runtime-tasks.test.ts @@ -0,0 +1,236 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { resetTaskFlowRegistryForTests } from "../../tasks/task-flow-registry.js"; +import { resetTaskRegistryForTests } from "../../tasks/task-registry.js"; +import { createRuntimeTaskFlow } from "./runtime-taskflow.js"; +import { createRuntimeTaskFlows, createRuntimeTaskRuns } from "./runtime-tasks.js"; + +const hoisted = vi.hoisted(() => { + const sendMessageMock = vi.fn(); + const cancelSessionMock = vi.fn(); + const killSubagentRunAdminMock = vi.fn(); + return { + sendMessageMock, + cancelSessionMock, + killSubagentRunAdminMock, + }; +}); + +vi.mock("../../tasks/task-registry-delivery-runtime.js", () => ({ + sendMessage: hoisted.sendMessageMock, +})); + +vi.mock("../../acp/control-plane/manager.js", () => ({ + getAcpSessionManager: () => ({ + cancelSession: hoisted.cancelSessionMock, + }), +})); + +vi.mock("../../agents/subagent-control.js", () => ({ + killSubagentRunAdmin: (params: unknown) => hoisted.killSubagentRunAdminMock(params), +})); + +afterEach(() => { + resetTaskRegistryForTests(); + resetTaskFlowRegistryForTests({ persist: false }); + vi.clearAllMocks(); +}); + +describe("runtime tasks", () => { + it("exposes canonical task and TaskFlow DTOs without leaking raw registry fields", () => { + const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ + sessionKey: "agent:main:main", + requesterOrigin: { + channel: "telegram", + to: "telegram:123", + }, + }); + const taskFlows = createRuntimeTaskFlows().bindSession({ + sessionKey: "agent:main:main", + }); + const taskRuns = createRuntimeTaskRuns().bindSession({ + sessionKey: "agent:main:main", + }); + const otherTaskFlows = createRuntimeTaskFlows().bindSession({ + sessionKey: "agent:main:other", + }); + const otherTaskRuns = createRuntimeTaskRuns().bindSession({ + sessionKey: "agent:main:other", + }); + + const created = legacyTaskFlow.createManaged({ + controllerId: "tests/runtime-tasks", + goal: "Review inbox", + currentStep: "triage", + stateJson: { lane: "priority" }, + }); + const child = legacyTaskFlow.runTask({ + flowId: created.flowId, + runtime: "acp", + childSessionKey: "agent:main:subagent:child", + runId: "runtime-task-run", + label: "Inbox triage", + task: "Review PR 1", + status: "running", + startedAt: 10, + lastEventAt: 11, + progressSummary: "Inspecting", + }); + if (!child.created) { + throw new Error("expected child task creation to succeed"); + } + + expect(taskFlows.list()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: created.flowId, + ownerKey: "agent:main:main", + goal: "Review inbox", + currentStep: "triage", + }), + ]), + ); + expect(taskFlows.get(created.flowId)).toMatchObject({ + id: created.flowId, + ownerKey: "agent:main:main", + goal: "Review inbox", + currentStep: "triage", + state: { lane: "priority" }, + taskSummary: { + total: 1, + active: 1, + }, + tasks: [ + expect.objectContaining({ + id: child.task.taskId, + flowId: created.flowId, + title: "Review PR 1", + label: "Inbox triage", + runId: "runtime-task-run", + }), + ], + }); + expect(taskRuns.list()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: child.task.taskId, + flowId: created.flowId, + sessionKey: "agent:main:main", + title: "Review PR 1", + status: "running", + }), + ]), + ); + expect(taskRuns.get(child.task.taskId)).toMatchObject({ + id: child.task.taskId, + flowId: created.flowId, + title: "Review PR 1", + progressSummary: "Inspecting", + }); + expect(taskRuns.findLatest()?.id).toBe(child.task.taskId); + expect(taskRuns.resolve("runtime-task-run")?.id).toBe(child.task.taskId); + expect(taskFlows.getTaskSummary(created.flowId)).toMatchObject({ + total: 1, + active: 1, + }); + + expect(otherTaskFlows.get(created.flowId)).toBeUndefined(); + expect(otherTaskRuns.get(child.task.taskId)).toBeUndefined(); + + const flowDetail = taskFlows.get(created.flowId); + expect(flowDetail).not.toHaveProperty("revision"); + expect(flowDetail).not.toHaveProperty("controllerId"); + expect(flowDetail).not.toHaveProperty("syncMode"); + + const taskDetail = taskRuns.get(child.task.taskId); + expect(taskDetail).not.toHaveProperty("taskId"); + expect(taskDetail).not.toHaveProperty("requesterSessionKey"); + expect(taskDetail).not.toHaveProperty("scopeKind"); + }); + + it("maps task cancellation results onto canonical task DTOs", async () => { + const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ + sessionKey: "agent:main:main", + }); + const taskRuns = createRuntimeTaskRuns().bindSession({ + sessionKey: "agent:main:main", + }); + + const created = legacyTaskFlow.createManaged({ + controllerId: "tests/runtime-tasks", + goal: "Cancel active task", + }); + const child = legacyTaskFlow.runTask({ + flowId: created.flowId, + runtime: "acp", + childSessionKey: "agent:main:subagent:child", + runId: "runtime-task-cancel", + task: "Cancel me", + status: "running", + startedAt: 20, + lastEventAt: 21, + }); + if (!child.created) { + throw new Error("expected child task creation to succeed"); + } + + const result = await taskRuns.cancel({ + taskId: child.task.taskId, + cfg: {} as never, + }); + + expect(hoisted.cancelSessionMock).toHaveBeenCalledWith({ + cfg: {}, + sessionKey: "agent:main:subagent:child", + reason: "task-cancel", + }); + expect(result).toMatchObject({ + found: true, + cancelled: true, + task: { + id: child.task.taskId, + title: "Cancel me", + status: "cancelled", + }, + }); + }); + + it("does not allow cross-owner task cancellation or leak task details", async () => { + const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ + sessionKey: "agent:main:main", + }); + const otherTaskRuns = createRuntimeTaskRuns().bindSession({ + sessionKey: "agent:main:other", + }); + + const created = legacyTaskFlow.createManaged({ + controllerId: "tests/runtime-tasks", + goal: "Keep owner isolation", + }); + const child = legacyTaskFlow.runTask({ + flowId: created.flowId, + runtime: "acp", + childSessionKey: "agent:main:subagent:child", + runId: "runtime-task-isolation", + task: "Do not cancel me", + status: "running", + startedAt: 30, + lastEventAt: 31, + }); + if (!child.created) { + throw new Error("expected child task creation to succeed"); + } + + const result = await otherTaskRuns.cancel({ + taskId: child.task.taskId, + cfg: {} as never, + }); + + expect(hoisted.cancelSessionMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + found: false, + cancelled: false, + reason: "Task not found.", + }); + expect(otherTaskRuns.get(child.task.taskId)).toBeUndefined(); + }); +}); diff --git a/src/plugins/runtime/runtime-tasks.ts b/src/plugins/runtime/runtime-tasks.ts new file mode 100644 index 00000000000..819f6f88e7d --- /dev/null +++ b/src/plugins/runtime/runtime-tasks.ts @@ -0,0 +1,263 @@ +import type { OpenClawConfig } from "../../config/config.js"; +import { cancelTaskById, listTasksForFlowId } from "../../tasks/runtime-internal.js"; +import { + mapTaskFlowDetail, + mapTaskFlowView, + mapTaskRunAggregateSummary, + mapTaskRunDetail, + mapTaskRunView, +} from "../../tasks/task-domain-views.js"; +import { getFlowTaskSummary } from "../../tasks/task-executor.js"; +import { + getTaskFlowByIdForOwner, + listTaskFlowsForOwner, + findLatestTaskFlowForOwner, + resolveTaskFlowForLookupTokenForOwner, +} from "../../tasks/task-flow-owner-access.js"; +import { + findLatestTaskForRelatedSessionKeyForOwner, + getTaskByIdForOwner, + listTasksForRelatedSessionKeyForOwner, + resolveTaskForLookupTokenForOwner, +} from "../../tasks/task-owner-access.js"; +import { normalizeDeliveryContext } from "../../utils/delivery-context.js"; +import type { OpenClawPluginToolContext } from "../types.js"; +import type { PluginRuntimeTaskFlow } from "./runtime-taskflow.js"; +import type { + TaskFlowDetail, + TaskFlowView, + TaskRunAggregateSummary, + TaskRunCancelResult, + TaskRunDetail, + TaskRunView, +} from "./task-domain-types.js"; + +function assertSessionKey(sessionKey: string | undefined, errorMessage: string): string { + const normalized = sessionKey?.trim(); + if (!normalized) { + throw new Error(errorMessage); + } + return normalized; +} + +function mapCancelledTaskResult( + result: Awaited>, +): TaskRunCancelResult { + return { + found: result.found, + cancelled: result.cancelled, + ...(result.reason ? { reason: result.reason } : {}), + ...(result.task ? { task: mapTaskRunDetail(result.task) } : {}), + }; +} + +export type BoundTaskRunsRuntime = { + readonly sessionKey: string; + readonly requesterOrigin?: ReturnType; + get: (taskId: string) => TaskRunDetail | undefined; + list: () => TaskRunView[]; + findLatest: () => TaskRunDetail | undefined; + resolve: (token: string) => TaskRunDetail | undefined; + cancel: (params: { taskId: string; cfg: OpenClawConfig }) => Promise; +}; + +export type PluginRuntimeTaskRuns = { + bindSession: (params: { + sessionKey: string; + requesterOrigin?: import("../../tasks/task-registry.types.js").TaskDeliveryState["requesterOrigin"]; + }) => BoundTaskRunsRuntime; + fromToolContext: ( + ctx: Pick, + ) => BoundTaskRunsRuntime; +}; + +export type BoundTaskFlowsRuntime = { + readonly sessionKey: string; + readonly requesterOrigin?: ReturnType; + get: (flowId: string) => TaskFlowDetail | undefined; + list: () => TaskFlowView[]; + findLatest: () => TaskFlowDetail | undefined; + resolve: (token: string) => TaskFlowDetail | undefined; + getTaskSummary: (flowId: string) => TaskRunAggregateSummary | undefined; +}; + +export type PluginRuntimeTaskFlows = { + bindSession: (params: { + sessionKey: string; + requesterOrigin?: import("../../tasks/task-registry.types.js").TaskDeliveryState["requesterOrigin"]; + }) => BoundTaskFlowsRuntime; + fromToolContext: ( + ctx: Pick, + ) => BoundTaskFlowsRuntime; +}; + +export type PluginRuntimeTasks = { + runs: PluginRuntimeTaskRuns; + flows: PluginRuntimeTaskFlows; + /** @deprecated Use runtime.tasks.flows for DTO-based TaskFlow access. */ + flow: PluginRuntimeTaskFlow; +}; + +function createBoundTaskRunsRuntime(params: { + sessionKey: string; + requesterOrigin?: import("../../tasks/task-registry.types.js").TaskDeliveryState["requesterOrigin"]; +}): BoundTaskRunsRuntime { + const ownerKey = assertSessionKey( + params.sessionKey, + "Tasks runtime requires a bound sessionKey.", + ); + const requesterOrigin = params.requesterOrigin + ? normalizeDeliveryContext(params.requesterOrigin) + : undefined; + return { + sessionKey: ownerKey, + ...(requesterOrigin ? { requesterOrigin } : {}), + get: (taskId) => { + const task = getTaskByIdForOwner({ taskId, callerOwnerKey: ownerKey }); + return task ? mapTaskRunDetail(task) : undefined; + }, + list: () => + listTasksForRelatedSessionKeyForOwner({ + relatedSessionKey: ownerKey, + callerOwnerKey: ownerKey, + }).map((task) => mapTaskRunView(task)), + findLatest: () => { + const task = findLatestTaskForRelatedSessionKeyForOwner({ + relatedSessionKey: ownerKey, + callerOwnerKey: ownerKey, + }); + return task ? mapTaskRunDetail(task) : undefined; + }, + resolve: (token) => { + const task = resolveTaskForLookupTokenForOwner({ + token, + callerOwnerKey: ownerKey, + }); + return task ? mapTaskRunDetail(task) : undefined; + }, + cancel: async ({ taskId, cfg }) => { + const task = getTaskByIdForOwner({ + taskId, + callerOwnerKey: ownerKey, + }); + if (!task) { + return { + found: false, + cancelled: false, + reason: "Task not found.", + }; + } + return mapCancelledTaskResult( + await cancelTaskById({ + cfg, + taskId: task.taskId, + }), + ); + }, + }; +} + +function createBoundTaskFlowsRuntime(params: { + sessionKey: string; + requesterOrigin?: import("../../tasks/task-registry.types.js").TaskDeliveryState["requesterOrigin"]; +}): BoundTaskFlowsRuntime { + const ownerKey = assertSessionKey( + params.sessionKey, + "TaskFlow runtime requires a bound sessionKey.", + ); + const requesterOrigin = params.requesterOrigin + ? normalizeDeliveryContext(params.requesterOrigin) + : undefined; + + const getDetail = (flowId: string): TaskFlowDetail | undefined => { + const flow = getTaskFlowByIdForOwner({ + flowId, + callerOwnerKey: ownerKey, + }); + if (!flow) { + return undefined; + } + const tasks = listTasksForFlowId(flow.flowId); + return mapTaskFlowDetail({ + flow, + tasks, + summary: getFlowTaskSummary(flow.flowId), + }); + }; + + return { + sessionKey: ownerKey, + ...(requesterOrigin ? { requesterOrigin } : {}), + get: (flowId) => getDetail(flowId), + list: () => + listTaskFlowsForOwner({ + callerOwnerKey: ownerKey, + }).map((flow) => mapTaskFlowView(flow)), + findLatest: () => { + const flow = findLatestTaskFlowForOwner({ + callerOwnerKey: ownerKey, + }); + return flow ? getDetail(flow.flowId) : undefined; + }, + resolve: (token) => { + const flow = resolveTaskFlowForLookupTokenForOwner({ + token, + callerOwnerKey: ownerKey, + }); + return flow ? getDetail(flow.flowId) : undefined; + }, + getTaskSummary: (flowId) => { + const flow = getTaskFlowByIdForOwner({ + flowId, + callerOwnerKey: ownerKey, + }); + return flow ? mapTaskRunAggregateSummary(getFlowTaskSummary(flow.flowId)) : undefined; + }, + }; +} + +export function createRuntimeTaskRuns(): PluginRuntimeTaskRuns { + return { + bindSession: (params) => + createBoundTaskRunsRuntime({ + sessionKey: params.sessionKey, + requesterOrigin: params.requesterOrigin, + }), + fromToolContext: (ctx) => + createBoundTaskRunsRuntime({ + sessionKey: assertSessionKey( + ctx.sessionKey, + "Tasks runtime requires tool context with a sessionKey.", + ), + requesterOrigin: ctx.deliveryContext, + }), + }; +} + +export function createRuntimeTaskFlows(): PluginRuntimeTaskFlows { + return { + bindSession: (params) => + createBoundTaskFlowsRuntime({ + sessionKey: params.sessionKey, + requesterOrigin: params.requesterOrigin, + }), + fromToolContext: (ctx) => + createBoundTaskFlowsRuntime({ + sessionKey: assertSessionKey( + ctx.sessionKey, + "TaskFlow runtime requires tool context with a sessionKey.", + ), + requesterOrigin: ctx.deliveryContext, + }), + }; +} + +export function createRuntimeTasks(params: { + legacyTaskFlow: PluginRuntimeTaskFlow; +}): PluginRuntimeTasks { + return { + runs: createRuntimeTaskRuns(), + flows: createRuntimeTaskFlows(), + flow: params.legacyTaskFlow, + }; +} diff --git a/src/plugins/runtime/task-domain-types.ts b/src/plugins/runtime/task-domain-types.ts new file mode 100644 index 00000000000..6208e1d4b12 --- /dev/null +++ b/src/plugins/runtime/task-domain-types.ts @@ -0,0 +1,83 @@ +import type { JsonValue } from "../../tasks/task-flow-registry.types.js"; +import type { + TaskDeliveryStatus, + TaskNotifyPolicy, + TaskRuntime, + TaskScopeKind, + TaskRuntimeCounts, + TaskStatus, + TaskStatusCounts, + TaskTerminalOutcome, +} from "../../tasks/task-registry.types.js"; +import type { DeliveryContext } from "../../utils/delivery-context.js"; + +export type TaskRunAggregateSummary = { + total: number; + active: number; + terminal: number; + failures: number; + byStatus: TaskStatusCounts; + byRuntime: TaskRuntimeCounts; +}; + +export type TaskRunView = { + id: string; + runtime: TaskRuntime; + sourceId?: string; + sessionKey: string; + ownerKey: string; + scope: TaskScopeKind; + childSessionKey?: string; + flowId?: string; + parentTaskId?: string; + agentId?: string; + runId?: string; + label?: string; + title: string; + status: TaskStatus; + deliveryStatus: TaskDeliveryStatus; + notifyPolicy: TaskNotifyPolicy; + createdAt: number; + startedAt?: number; + endedAt?: number; + lastEventAt?: number; + cleanupAfter?: number; + error?: string; + progressSummary?: string; + terminalSummary?: string; + terminalOutcome?: TaskTerminalOutcome; +}; + +export type TaskRunDetail = TaskRunView; + +export type TaskRunCancelResult = { + found: boolean; + cancelled: boolean; + reason?: string; + task?: TaskRunDetail; +}; + +export type TaskFlowView = { + id: string; + ownerKey: string; + requesterOrigin?: DeliveryContext; + status: import("../../tasks/task-flow-registry.types.js").TaskFlowStatus; + notifyPolicy: TaskNotifyPolicy; + goal: string; + currentStep?: string; + cancelRequestedAt?: number; + createdAt: number; + updatedAt: number; + endedAt?: number; +}; + +export type TaskFlowDetail = TaskFlowView & { + state?: JsonValue; + wait?: JsonValue; + blocked?: { + taskId?: string; + summary?: string; + }; + tasks: TaskRunView[]; + taskSummary: TaskRunAggregateSummary; +}; diff --git a/src/plugins/runtime/types-core.ts b/src/plugins/runtime/types-core.ts index 25a654693a9..195fa7ba412 100644 --- a/src/plugins/runtime/types-core.ts +++ b/src/plugins/runtime/types-core.ts @@ -104,8 +104,12 @@ export type PluginRuntimeCore = { resolveStateDir: typeof import("../../config/paths.js").resolveStateDir; }; tasks: { + runs: import("./runtime-tasks.js").PluginRuntimeTaskRuns; + flows: import("./runtime-tasks.js").PluginRuntimeTaskFlows; + /** @deprecated Use runtime.tasks.flows for DTO-based TaskFlow access. */ flow: import("./runtime-taskflow.js").PluginRuntimeTaskFlow; }; + /** @deprecated Use runtime.tasks.flows for DTO-based TaskFlow access. */ taskFlow: import("./runtime-taskflow.js").PluginRuntimeTaskFlow; modelAuth: { /** Resolve auth for a model. Only provider/model and optional cfg are used. */ diff --git a/src/tasks/task-domain-views.ts b/src/tasks/task-domain-views.ts new file mode 100644 index 00000000000..1ea7fdb5cfb --- /dev/null +++ b/src/tasks/task-domain-views.ts @@ -0,0 +1,95 @@ +import type { + TaskFlowDetail, + TaskFlowView, + TaskRunAggregateSummary, + TaskRunDetail, + TaskRunView, +} from "../plugins/runtime/task-domain-types.js"; +import type { TaskFlowRecord } from "./task-flow-registry.types.js"; +import { summarizeTaskRecords } from "./task-registry.summary.js"; +import type { TaskRecord, TaskRegistrySummary } from "./task-registry.types.js"; + +export function mapTaskRunAggregateSummary(summary: TaskRegistrySummary): TaskRunAggregateSummary { + return { + total: summary.total, + active: summary.active, + terminal: summary.terminal, + failures: summary.failures, + byStatus: { ...summary.byStatus }, + byRuntime: { ...summary.byRuntime }, + }; +} + +export function mapTaskRunView(task: TaskRecord): TaskRunView { + return { + id: task.taskId, + runtime: task.runtime, + ...(task.sourceId ? { sourceId: task.sourceId } : {}), + sessionKey: task.requesterSessionKey, + ownerKey: task.ownerKey, + scope: task.scopeKind, + ...(task.childSessionKey ? { childSessionKey: task.childSessionKey } : {}), + ...(task.parentFlowId ? { flowId: task.parentFlowId } : {}), + ...(task.parentTaskId ? { parentTaskId: task.parentTaskId } : {}), + ...(task.agentId ? { agentId: task.agentId } : {}), + ...(task.runId ? { runId: task.runId } : {}), + ...(task.label ? { label: task.label } : {}), + title: task.task, + status: task.status, + deliveryStatus: task.deliveryStatus, + notifyPolicy: task.notifyPolicy, + createdAt: task.createdAt, + ...(task.startedAt !== undefined ? { startedAt: task.startedAt } : {}), + ...(task.endedAt !== undefined ? { endedAt: task.endedAt } : {}), + ...(task.lastEventAt !== undefined ? { lastEventAt: task.lastEventAt } : {}), + ...(task.cleanupAfter !== undefined ? { cleanupAfter: task.cleanupAfter } : {}), + ...(task.error ? { error: task.error } : {}), + ...(task.progressSummary ? { progressSummary: task.progressSummary } : {}), + ...(task.terminalSummary ? { terminalSummary: task.terminalSummary } : {}), + ...(task.terminalOutcome ? { terminalOutcome: task.terminalOutcome } : {}), + }; +} + +export function mapTaskRunDetail(task: TaskRecord): TaskRunDetail { + return mapTaskRunView(task); +} + +export function mapTaskFlowView(flow: TaskFlowRecord): TaskFlowView { + return { + id: flow.flowId, + ownerKey: flow.ownerKey, + ...(flow.requesterOrigin ? { requesterOrigin: { ...flow.requesterOrigin } } : {}), + status: flow.status, + notifyPolicy: flow.notifyPolicy, + goal: flow.goal, + ...(flow.currentStep ? { currentStep: flow.currentStep } : {}), + ...(flow.cancelRequestedAt !== undefined ? { cancelRequestedAt: flow.cancelRequestedAt } : {}), + createdAt: flow.createdAt, + updatedAt: flow.updatedAt, + ...(flow.endedAt !== undefined ? { endedAt: flow.endedAt } : {}), + }; +} + +export function mapTaskFlowDetail(params: { + flow: TaskFlowRecord; + tasks: TaskRecord[]; + summary?: TaskRegistrySummary; +}): TaskFlowDetail { + const summary = params.summary ?? summarizeTaskRecords(params.tasks); + const base = mapTaskFlowView(params.flow); + return { + ...base, + ...(params.flow.stateJson !== undefined ? { state: params.flow.stateJson } : {}), + ...(params.flow.waitJson !== undefined ? { wait: params.flow.waitJson } : {}), + ...(params.flow.blockedTaskId || params.flow.blockedSummary + ? { + blocked: { + ...(params.flow.blockedTaskId ? { taskId: params.flow.blockedTaskId } : {}), + ...(params.flow.blockedSummary ? { summary: params.flow.blockedSummary } : {}), + }, + } + : {}), + tasks: params.tasks.map((task) => mapTaskRunView(task)), + taskSummary: mapTaskRunAggregateSummary(summary), + }; +} diff --git a/test/helpers/plugins/plugin-runtime-mock.ts b/test/helpers/plugins/plugin-runtime-mock.ts index c0e2c0ce30e..ba5a96d92d8 100644 --- a/test/helpers/plugins/plugin-runtime-mock.ts +++ b/test/helpers/plugins/plugin-runtime-mock.ts @@ -359,6 +359,14 @@ export function createPluginRuntimeMock(overrides: DeepPartial = resolveStateDir: vi.fn(() => "/tmp/openclaw"), }, tasks: { + runs: { + bindSession: vi.fn(), + fromToolContext: vi.fn(), + } as PluginRuntime["tasks"]["runs"], + flows: { + bindSession: vi.fn(), + fromToolContext: vi.fn(), + } as PluginRuntime["tasks"]["flows"], flow: taskFlow, }, taskFlow,