Skip to content
Open
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
4 changes: 2 additions & 2 deletions examples/workflow-sdk/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions integrations/workflow-world/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,15 @@ package is left to Node at runtime. Next.js rejects a package asked to do both.

This package only implements orchestration. Sandbox selection, including
agentOS, stays in Vercel Eve's sandbox configuration.

## Workflow SDK compatibility

This package implements the Workflow SDK 5 World contract and requires
`workflow` 5.2 or newer. Event ids are positions in the run's log
(`evnt_00000000000000000000000001` is a run's first event), allocated inside
the run actor's transaction that appends the event.

Runs created by an earlier release of this package numbered their events with
ULIDs, and the Workflow SDK 5 runtime refuses to replay them. Every run is
served by the deployment at `WORKFLOW_RUNTIME_URL`, so let in-flight runs
finish on the previous release before upgrading.
7 changes: 4 additions & 3 deletions integrations/workflow-world/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
"test:conformance": "pnpm run build && tsx scripts/conformance/run.ts"
},
"dependencies": {
"@workflow/errors": "5.0.0-beta.11",
"@workflow/world": "5.0.0-beta.21",
"@workflow/errors": "5.0.4",
"@workflow/utils": "5.0.2",
"@workflow/world": "5.0.4",
"cbor-x": "^1.6.0",
"rivetkit": "workspace:*",
"ulid": "~3.0.1"
Expand All @@ -39,7 +40,7 @@
"@rivetkit/engine-cli-linux-x64-musl": "2.3.5",
"@rivetkit/rivetkit-napi-linux-x64-gnu": "2.3.5",
"@types/node": "^24.0.0",
"@workflow/world-testing": "5.0.0-beta.35",
"@workflow/world-testing": "5.0.5",
"tsx": "^4.20.0",
"typescript": "^5.5.2",
"vite": "^7.0.0",
Expand Down
8 changes: 4 additions & 4 deletions integrations/workflow-world/scripts/integration/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,14 +641,14 @@ async function runChecks(
input: { source: "integration" },
},
});
const globalStep = await world.steps.get(undefined, stepId, {
const scopedStep = await world.steps.get(stepLookupRunId, stepId, {
resolveData: "none",
});
assert(
globalStep.stepId === stepId && globalStep.runId === stepLookupRunId,
"Expected steps.get without runId to resolve via correlation index",
scopedStep.stepId === stepId && scopedStep.runId === stepLookupRunId,
"Expected steps.get to resolve the step within its run",
);
log("global step lookup check passed");
log("run-scoped step lookup check passed");

const waitCreated = await world.events.create(null, {
eventType: "run_created",
Expand Down
15 changes: 12 additions & 3 deletions integrations/workflow-world/src/actors/coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,18 @@ export const coordinator = actor({
where.push("workflow_name = ?");
args.push(params.workflowName);
}
if (params?.status) {
where.push("status = ?");
args.push(params.status);
if (params?.status !== undefined) {
// An array matches any listed status; an empty array matches nothing,
// like SQL `IN ()`.
const statuses = Array.isArray(params.status)
? params.status
: [params.status];
if (statuses.length === 0) {
where.push("0");
} else {
where.push(`status IN (${statuses.map(() => "?").join(", ")})`);
args.push(...statuses);
}
}
const rows = await c.db.execute(
`
Expand Down
14 changes: 9 additions & 5 deletions integrations/workflow-world/src/actors/dispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MessageId, QueuePayloadSchema, parseQueueName } from "@workflow/world";
import { MessageId, QueuePayloadSchema } from "@workflow/world";
import type { Ctx } from "./shared.js";

type DispatchRow = {
message_id: string;
queue_name: string;
route: "flow" | "step";
route: "flow";
runtime_url: string;
body: string;
headers: Record<string, string>;
Expand Down Expand Up @@ -153,8 +153,10 @@ function normalizeRuntimeUrl(value: string) {
return value.replace(/\/+$/, "");
}

function queueRoute(queueName: string): "flow" | "step" {
return parseQueueName(queueName).kind === "workflow" ? "flow" : "step";
// Workflow continuations and queued steps both travel on the workflow topic and
// are served by the combined flow handler. There is no step route.
function queueRoute(_queueName: string): "flow" {
return "flow";
}

export async function claimNextDispatch(c: Ctx): Promise<DispatchRow | null> {
Expand Down Expand Up @@ -192,7 +194,9 @@ export async function claimNextDispatch(c: Ctx): Promise<DispatchRow | null> {
return {
message_id: String(row.message_id),
queue_name: String(row.queue_name),
route: row.route === "step" ? "step" : "flow",
// Rows enqueued by a release that still had a step route are delivered to
// the flow handler too.
route: "flow",
runtime_url: String(row.runtime_url),
body: String(row.body),
headers:
Expand Down
Loading