Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions agents/myra/src/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export const ASSISTANT_SYSTEM_PROMPT =
"package in your working tree, then reply with its two files as " +
"fenced code blocks, each labelled with its filename on the line " +
'above the fence — a "package.json" plus a "definition.json" holding {"name", ' +
'"description", "systemPrompt", and an optional five-field cron ' +
'"schedule" for a routine} — a one-line summary of what it ' +
'"description", "systemPrompt"} — 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, " +
Expand Down
8 changes: 1 addition & 7 deletions apps/web/src/agent-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,11 @@ 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 },
...(args.schedule !== undefined ? [{ type: "schedule", cron: args.schedule }] : []),
],
triggers: [{ type: "mail", to: args.triggerAddress }],
steps: {
[stepId]: {
kind: "step",
Expand Down Expand Up @@ -206,7 +202,6 @@ export function agentSlugFromSourceAssetName(assetName: string): string | null {
export type NewAgentInput = {
readonly name: string;
readonly systemPrompt: string;
readonly schedule?: string;
/** The agent's address slug, when a caller already knows it (e.g.
* redeploying or re-joining an existing agent) — used verbatim instead
* of being re-derived from `name`, so the asset name stays stable. */
Expand Down Expand Up @@ -260,7 +255,6 @@ 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,
Expand Down
10 changes: 1 addition & 9 deletions apps/web/src/chat/deployable-package.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The one contract between an agent that writes a package and the client
// that deploys it: a reply carrying `package.json` plus a
// `definition.json` of {name, systemPrompt, description?, schedule?} —
// `definition.json` of {name, systemPrompt, description?} —
// either as mail attachments, or (since `@intx/tools-mail`'s `mail_send`
// has no attachments parameter) as two labelled fenced code blocks in the
// message body. The client renders the source tree itself
Expand All @@ -16,16 +16,8 @@ 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";
Expand Down
14 changes: 2 additions & 12 deletions apps/web/src/chat/message-attachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
// 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 { isFiveFieldCron, type DeployablePackage } from "./deployable-package";
import type { DeployablePackage } from "./deployable-package";
import type { MailAttachment } from "./threads-api";

function errorText(cause: unknown): string {
Expand Down Expand Up @@ -51,32 +50,23 @@ 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 (
<div className="chat-deploy-card">
<div className="chat-deploy-card-text">
<span className="chat-deploy-card-name">{pkg.name}</span>
{pkg.description === undefined ? null : (
<span className="chat-deploy-card-note">{pkg.description}</span>
)}
{sentence !== null ? <span className="chat-deploy-card-note">{sentence}</span> : null}
{pkg.schedule !== undefined && !scheduleValid ? (
<span className="chat-deploy-card-error">
{`This package's schedule ("${pkg.schedule}") isn't a valid five-field cron string.`}
</span>
) : null}
</div>
{deployed === undefined ? (
<Button
variant="primary"
size="sm"
disabled={deploy.isPending || !scheduleValid}
disabled={deploy.isPending}
onClick={() =>
deploy.mutate({
name: pkg.name,
systemPrompt: pkg.systemPrompt,
...(pkg.schedule !== undefined ? { schedule: pkg.schedule } : {}),
})
}
>
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/insights-stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ function scheduled(
assetId: "ast_def",
name: "Daily dig",
tenantId: "t1",
cron: "0 9 * * *",
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
...partial,
Expand Down
10 changes: 3 additions & 7 deletions apps/web/src/pages/routine-detail-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// `/workflows/<definitionId>` — a scheduled definition's own page: name,
// cron sentence, pause, run now, and its runs (with expandable event logs,
// from stock's `GET /workflows/runs?definitionId=` and `GET
// `/workflows/<definitionId>` — a deployed definition's own page: name,
// pause, run now, and its runs (with expandable event logs, from stock's
// `GET /workflows/runs?definitionId=` and `GET
// /workflows/runs/:runId/events` — `vendor/intx/hub-api/src/routes/runs.ts`).
// The id is the definition id.
import {
Expand Down Expand Up @@ -29,7 +29,6 @@ import type { GlobalRoutineRow } from "../global-routines";
import { Link } from "../navigation";
import { WORKFLOWS_PATH_PREFIX } from "../path-ids";
import { StageTopBar } from "../shell/stage-top-bar";
import { scheduleSentence } from "./routines-page";

const RunsPageSchema = paginatedSchema(WorkflowRunResponse);
type RunRow = typeof WorkflowRunResponse.infer;
Expand Down Expand Up @@ -198,15 +197,13 @@ export function RoutineDetailPage({
readonly onRunNow: () => Promise<void>;
}) {
const enabled = row.definition.status === "deployed";
const sentence = scheduleSentence(row.definition.cron);
return (
<div className="flex h-full min-h-0 flex-col">
<StageTopBar
crumbs={[
{ label: "Workflows", href: WORKFLOWS_PATH_PREFIX },
{ label: row.definition.name },
]}
subtitle={sentence}
actions={
<div className="flex items-center gap-2">
<Button
Expand All @@ -226,7 +223,6 @@ export function RoutineDetailPage({
<div>
<h1 className="m-0 text-xl font-semibold">{row.definition.name}</h1>
<p className="mt-2 text-sm text-[var(--ui-fg-muted)]">{row.tenantName}</p>
<p className="mt-4 text-lg">{sentence}</p>
</div>
<RoutineRunsSection tenantId={row.tenantId} definitionId={row.definition.definitionId} />
</div>
Expand Down
20 changes: 5 additions & 15 deletions apps/web/src/pages/routines-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Workflows: an ops table of authored workflow definitions that carry a
// ScheduleTrigger, including paused (`stopped`) ones. Pause/resume and
// run-now are the only writes; schedules are authored on the definition.
// Workflows: an ops table of deployed workflow definitions, including
// paused (`stopped`) ones. Pause/resume and run-now are the only writes.
import {
EmptyState,
RichEmptyState,
Expand All @@ -14,7 +13,6 @@ import {
TableRow,
} from "@corbits/react-ui";
import { Clock } from "@/lib/icons";
import { cronSentence } from "@corbits/workflows/client";

import { useGlobalRoutines, useRoutineActions } from "../global-routines";
import type { GlobalRoutineRow } from "../global-routines";
Expand All @@ -24,10 +22,6 @@ import { StageTopBar } from "../shell/stage-top-bar";

export type { GlobalRoutineRow } from "../global-routines";

export function scheduleSentence(cron: string): string {
return cronSentence(cron) ?? cron;
}

export function GlobalRoutinesList({
rows,
onToggleEnabled,
Expand All @@ -41,8 +35,8 @@ export function GlobalRoutinesList({
return (
<RichEmptyState
icon={<Clock />}
title="No scheduled workflows yet"
description="A workflow with a schedule shows up here. Pause, resume, or run it now."
title="No workflows yet"
description="A deployed workflow shows up here. Pause, resume, or run it now."
/>
);
}
Expand All @@ -51,7 +45,6 @@ export function GlobalRoutinesList({
<TableHeader>
<TableRow>
<TableHead>Routine</TableHead>
<TableHead>Schedule</TableHead>
<TableHead>On</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
Expand All @@ -76,9 +69,6 @@ export function GlobalRoutinesList({
<span className="text-xs text-[var(--ui-fg-muted)]">{row.tenantName}</span>
</span>
</TableCell>
<TableCell>
<span className="text-sm">{scheduleSentence(row.definition.cron)}</span>
</TableCell>
<TableCell>
<Switch
checked={enabled}
Expand Down Expand Up @@ -106,7 +96,7 @@ export function RoutinesRoute() {
<div className="flex h-full min-h-0 flex-col">
<StageTopBar
crumbs={[{ label: "Workflows" }]}
subtitle="Scheduled workflows. Pause, resume, or run now."
subtitle="Deployed workflows. Pause, resume, or run now."
/>
<div className="stage-content flex min-h-0 flex-1 flex-col overflow-y-auto">
{routinesQuery.kind === "loading" ? (
Expand Down
9 changes: 3 additions & 6 deletions apps/web/src/routines-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
// assets (`GET /assets?kind=workflow`) for a display name, exactly the two
// stock reads `vendor/intx/hub-api/src/routes/workflows.ts` exposes.
//
// Stock's deployment row carries no schedule: a `ScheduleTrigger`'s cron
// lives in the deployed source's `workflow.json`, unreachable from either
// listing read, so `cron` always reports "manual" here — honest rather than
// guessed — until an upstream read exposes it. Run-now and pause/resume have
// The `schedule` trigger is reserved on Interchange but unimplemented — no
// scheduler fires it — so this reads deployments as plain workflows, with
// no schedule concept. Run-now and pause/resume have
// no backing stock route either (`/deployments` is list/create only; no
// per-deployment PATCH or trigger route exists), so both stay rejected
// promises with a message naming the missing route, same pattern as before.
Expand All @@ -27,7 +26,6 @@ export const ScheduledWorkflowDefinition = type({
name: "string",
tenantId: "string",
status: "'deployed' | 'stopped'",
cron: "string",
createdAt: "string",
updatedAt: "string",
});
Expand Down Expand Up @@ -90,7 +88,6 @@ export async function listScheduledWorkflows(
name: nameByAssetId.get(deployment.definitionAssetId) ?? "Untitled workflow",
tenantId: deployment.tenantId,
status: deployment.status === "deployed" ? "deployed" : "stopped",
cron: "manual",
createdAt: deployment.createdAt,
updatedAt: deployment.createdAt,
}));
Expand Down
Loading