From 23a4981e4d601e03ef5e1849def9d5cc1e8e9dcc Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 01:36:48 -0700 Subject: [PATCH] feat(web,myra): a mailed package can carry a cron schedule; Deploy creates the scheduled workflow (CL-8513) --- agents/myra/src/system-prompt.ts | 3 ++- apps/web/src/agent-deploy.ts | 8 +++++++- apps/web/src/chat/deployable-package.ts | 8 ++++++++ apps/web/src/chat/message-attachments.tsx | 21 ++++++++++++++++++--- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/agents/myra/src/system-prompt.ts b/agents/myra/src/system-prompt.ts index 2b7111070..aac2d7ffb 100644 --- a/agents/myra/src/system-prompt.ts +++ b/agents/myra/src/system-prompt.ts @@ -23,7 +23,8 @@ export const ASSISTANT_SYSTEM_PROMPT = "workbench itself. To stand up a new agent or workflow, write the " + "package in your working tree, then reply with its files attached — " + 'a "package.json" plus a "definition.json" holding {"name", ' + - '"description", "systemPrompt"} — a one-line summary of what it ' + + '"description", "systemPrompt", and an optional five-field cron ' + + '"schedule" for a routine} — a one-line summary of what it ' + "does, and a note to press Deploy. Workbench renders and deploys the " + "package itself from those two files, so send exactly them and " + "never try to deploy anything yourself. New capabilities, " + diff --git a/apps/web/src/agent-deploy.ts b/apps/web/src/agent-deploy.ts index 7efec4ccf..3d38dc915 100644 --- a/apps/web/src/agent-deploy.ts +++ b/apps/web/src/agent-deploy.ts @@ -88,11 +88,15 @@ export function buildAgentDefinitionJson(args: { systemPrompt: string; triggerAddress: string; declaredSources: readonly { readonly provider: string; readonly model: string }[]; + schedule?: string; }): unknown { const stepId = "run"; return { id: args.slug, - triggers: [{ type: "mail", to: args.triggerAddress }], + triggers: [ + { type: "mail", to: args.triggerAddress }, + ...(args.schedule !== undefined ? [{ type: "schedule", cron: args.schedule }] : []), + ], steps: { [stepId]: { kind: "step", @@ -191,6 +195,7 @@ export function isAgentDeploySourceAssetName(name: string): boolean { export type NewAgentInput = { readonly name: string; readonly systemPrompt: string; + readonly schedule?: string; }; export type DeployedAgent = typeof WorkflowDeploymentResponse.infer; @@ -240,6 +245,7 @@ export async function deployAgentSource( systemPrompt, triggerAddress: `${slug}@${tenant.domain}`, declaredSources: offering.declaredSources, + ...(args.input.schedule !== undefined ? { schedule: args.input.schedule } : {}), }); const commitSha = await pushAgentSource( args.tenantId, diff --git a/apps/web/src/chat/deployable-package.ts b/apps/web/src/chat/deployable-package.ts index 12d1ff900..331229235 100644 --- a/apps/web/src/chat/deployable-package.ts +++ b/apps/web/src/chat/deployable-package.ts @@ -13,8 +13,16 @@ const AgentDefinition = type({ name: "string", systemPrompt: "string", "description?": "string", + "schedule?": "string", }); +/** A cron string this pipeline accepts: exactly five whitespace-separated + * fields. No third-party parser — the fields are validated for shape only, + * `cronSentence` (from `@corbits/workflows/client`) is the semantic check. */ +export function isFiveFieldCron(schedule: string): boolean { + return schedule.trim().split(/\s+/).length === 5; +} + export type DeployablePackage = typeof AgentDefinition.infer; export const PACKAGE_MANIFEST_NAME = "package.json"; diff --git a/apps/web/src/chat/message-attachments.tsx b/apps/web/src/chat/message-attachments.tsx index 266911462..d940420a2 100644 --- a/apps/web/src/chat/message-attachments.tsx +++ b/apps/web/src/chat/message-attachments.tsx @@ -3,11 +3,12 @@ // agent never calls the hub; anything else is a plain file list. import { Button } from "@corbits/react-ui"; +import { cronSentence } from "@corbits/workflows/client"; import { useDeployAgentMutation } from "../agents-api"; import { chatPath } from "../chat-path"; import { Link } from "../navigation"; -import { deployablePackage, type DeployablePackage } from "./deployable-package"; +import { deployablePackage, isFiveFieldCron, type DeployablePackage } from "./deployable-package"; import type { MailAttachment } from "./threads-api"; function errorText(cause: unknown): string { @@ -47,6 +48,8 @@ function DeployPackageCard({ }) { const deploy = useDeployAgentMutation(tenantId); const deployed = deploy.data; + const scheduleValid = pkg.schedule === undefined || isFiveFieldCron(pkg.schedule); + const sentence = pkg.schedule !== undefined && scheduleValid ? cronSentence(pkg.schedule) : null; return (
@@ -54,13 +57,25 @@ function DeployPackageCard({ {pkg.description === undefined ? null : ( {pkg.description} )} + {sentence !== null ? {sentence} : null} + {pkg.schedule !== undefined && !scheduleValid ? ( + + {`This package's schedule ("${pkg.schedule}") isn't a valid five-field cron string.`} + + ) : null}
{deployed === undefined ? (