diff --git a/plugins/automations/src/cli.ts b/plugins/automations/src/cli.ts index 4c00b774d..b87e98fb9 100644 --- a/plugins/automations/src/cli.ts +++ b/plugins/automations/src/cli.ts @@ -24,6 +24,8 @@ import { import { AUTOMATION_SCRIPT_TIMEOUT_DEFAULT_MS, automationScriptInterpreterSchema, + agentExecutionUpdateSchema, + automationAgentExecutionRequestSchema, } from "./rpc-types.js"; const DURATION_PATTERN = @@ -442,7 +444,9 @@ async function buildExecution( validateAgentTargetOptions(args); const environment = await buildAgentEnvironment(bb, args); return { - execution: { + // argv is a system boundary: apply the rpc request policy (prompt cap) + // before the service persists anything (#2166). + execution: automationAgentExecutionRequestSchema.parse({ mode: "agent", prompt, providerId: provider, @@ -457,7 +461,7 @@ async function buildExecution( ...(flag(args, "target-thread") ? { targetThreadId: flag(args, "target-thread") } : {}), - }, + }), }; } if ( @@ -542,7 +546,10 @@ async function buildAgentExecutionUpdate( environment: await buildAgentEnvironment(bb, args), }; } - return update; + // argv is a system boundary: apply the same request policy as the rpc + // route so an over-cap prompt is rejected before anything is persisted + // (#2166). + return agentExecutionUpdateSchema.parse(update); } async function buildUpdateRequest( diff --git a/plugins/automations/src/rpc-types.ts b/plugins/automations/src/rpc-types.ts index 41be2cb59..8b44d7962 100644 --- a/plugins/automations/src/rpc-types.ts +++ b/plugins/automations/src/rpc-types.ts @@ -119,10 +119,12 @@ export const automationTriggerSchema = z.discriminatedUnion("triggerType", [ ]); export type AutomationTrigger = z.infer; +// Stored/response shape. Length caps are request policy only: a cap applied +// here would make an already-persisted row unreadable and unrepairable (#2166). const automationAgentExecutionSchema = z .object({ mode: z.literal("agent"), - prompt: z.string().min(1).max(AUTOMATION_PROMPT_MAX_LENGTH), + prompt: z.string().min(1), providerId: z.string().min(1), model: z.string().min(1), permissionMode: permissionModeSchema, @@ -134,7 +136,7 @@ const automationAgentExecutionSchema = z const automationScriptExecutionSchema = z .object({ mode: z.literal("script"), - script: z.string().min(1).max(AUTOMATION_SCRIPT_MAX_LENGTH).optional(), + script: z.string().min(1).optional(), scriptFile: z .string() .min(1) @@ -173,9 +175,19 @@ function requireExactlyOneScriptSource( } } -const automationExecutionRequestSchema = automationExecutionSchema.superRefine( - requireExactlyOneScriptSource, -); +export const automationAgentExecutionRequestSchema = + automationAgentExecutionSchema.extend({ + prompt: z.string().min(1).max(AUTOMATION_PROMPT_MAX_LENGTH), + }); + +const automationExecutionRequestSchema = z + .discriminatedUnion("mode", [ + automationAgentExecutionRequestSchema, + automationScriptExecutionSchema.extend({ + script: z.string().min(1).max(AUTOMATION_SCRIPT_MAX_LENGTH).optional(), + }), + ]) + .superRefine(requireExactlyOneScriptSource); /** * Execution as returned to clients. Script automations add `storedScriptPath`: @@ -205,7 +217,7 @@ const agentExecutionTargetSchema = z.discriminatedUnion("type", [ .strict(), ]); -const agentExecutionUpdateSchema = z +export const agentExecutionUpdateSchema = z .object({ prompt: z.string().min(1).max(AUTOMATION_PROMPT_MAX_LENGTH).optional(), model: z.string().min(1).optional(),