Skip to main content
import { Approval, approvalDecisionSchema } from "smithers-orchestrator";

type ApprovalProps = {
  id: string;
  mode?: "approve" | "select" | "rank"; // default "approve"
  options?: ApprovalOption[]; // required for select/rank
  output: z.ZodObject | Table | string;
  outputSchema?: z.ZodObject; // default approvalDecisionSchema
  request: { title: string; summary?: string; metadata?: Record<string, unknown> };
  onDeny?: "fail" | "continue" | "skip"; // default "fail"
  allowedScopes?: string[];
  allowedUsers?: string[];
  autoApprove?: {
    after?: number; // auto-approve after N consecutive manual approvals
    condition?: (ctx: WorkflowContext) => boolean;
    audit?: boolean;
    revertOn?: (ctx: WorkflowContext) => boolean;
  };
  async?: boolean; // unrelated downstream may continue while pending
  dependsOn?: string[];
  needs?: Record<string, string>;
  skipIf?: boolean;
  timeoutMs?: number;
  retries?: number;
  retryPolicy?: { backoff?: "fixed" | "linear" | "exponential"; initialDelayMs?: number };
  continueOnFail?: boolean;
  cache?: { by?: (ctx) => unknown; version?: string };
  label?: string;
  meta?: Record<string, unknown>;
};
import { Approval, Sequence, Task, Workflow, approvalDecisionSchema, createSmithers } from "smithers-orchestrator";
import { z } from "zod";

const { smithers, outputs } = createSmithers({
  publishApproval: approvalDecisionSchema,
  publishResult: z.object({ status: z.enum(["published", "rejected"]) }),
});

export default smithers((ctx) => {
  const decision = ctx.outputMaybe(outputs.publishApproval, { nodeId: "approve-publish" });
  return (
    <Workflow name="publish-flow">
      <Sequence>
        <Approval
          id="approve-publish"
          output={outputs.publishApproval}
          request={{ title: "Publish the draft?", summary: "Human review required." }}
          onDeny="continue"
        />
        {decision ? (
          <Task id="record" output={outputs.publishResult}>
            {{ status: decision.approved ? "published" : "rejected" }}
          </Task>
        ) : null}
      </Sequence>
    </Workflow>
  );
});

Notes

  • mode="select" returns { selected, notes }; mode="rank" returns { ranked, notes }.
  • Durable deferred keyed on (run, node, iteration) survives restarts; smithers approve/deny resolves it.
  • For a pre-task pause without persisted decision, use <Task needsApproval>.