@smithers-orchestrator/components 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +47 -0
  3. package/src/SmithersWorkflow.ts +1 -0
  4. package/src/aspects/AspectAccumulator.ts +9 -0
  5. package/src/aspects/AspectContext.js +29 -0
  6. package/src/aspects/AspectContextValue.ts +16 -0
  7. package/src/aspects/CostBudgetConfig.ts +9 -0
  8. package/src/aspects/LatencySloConfig.ts +11 -0
  9. package/src/aspects/TokenBudgetConfig.ts +11 -0
  10. package/src/aspects/TrackingConfig.ts +11 -0
  11. package/src/aspects/index.js +10 -0
  12. package/src/components/Approval.js +211 -0
  13. package/src/components/ApprovalAutoApprove.ts +8 -0
  14. package/src/components/ApprovalDecision.ts +4 -0
  15. package/src/components/ApprovalGate.js +45 -0
  16. package/src/components/ApprovalGateProps.ts +22 -0
  17. package/src/components/ApprovalMode.ts +1 -0
  18. package/src/components/ApprovalOption.ts +6 -0
  19. package/src/components/ApprovalProps.ts +42 -0
  20. package/src/components/ApprovalRanking.ts +4 -0
  21. package/src/components/ApprovalRequest.ts +5 -0
  22. package/src/components/ApprovalSelection.ts +4 -0
  23. package/src/components/Aspects.js +39 -0
  24. package/src/components/AspectsProps.ts +18 -0
  25. package/src/components/Branch.js +12 -0
  26. package/src/components/BranchProps.ts +8 -0
  27. package/src/components/CategoryConfig.ts +10 -0
  28. package/src/components/CheckConfig.ts +8 -0
  29. package/src/components/CheckSuite.js +71 -0
  30. package/src/components/CheckSuiteProps.ts +12 -0
  31. package/src/components/ClassifyAndRoute.js +75 -0
  32. package/src/components/ClassifyAndRouteProps.ts +30 -0
  33. package/src/components/ColumnDef.ts +19 -0
  34. package/src/components/ContentPipeline.js +38 -0
  35. package/src/components/ContentPipelineProps.ts +12 -0
  36. package/src/components/ContentPipelineStage.ts +13 -0
  37. package/src/components/ContinueAsNew.js +27 -0
  38. package/src/components/ContinueAsNewProps.ts +6 -0
  39. package/src/components/Debate.js +63 -0
  40. package/src/components/DebateProps.ts +15 -0
  41. package/src/components/DecisionRule.ts +10 -0
  42. package/src/components/DecisionTable.js +42 -0
  43. package/src/components/DecisionTableProps.ts +14 -0
  44. package/src/components/DepsSpec.ts +3 -0
  45. package/src/components/DriftDetector.js +54 -0
  46. package/src/components/DriftDetectorProps.ts +29 -0
  47. package/src/components/EscalationChain.js +99 -0
  48. package/src/components/EscalationChainProps.ts +20 -0
  49. package/src/components/EscalationLevel.ts +13 -0
  50. package/src/components/GatherAndSynthesize.js +69 -0
  51. package/src/components/GatherAndSynthesizeProps.ts +24 -0
  52. package/src/components/HumanTask.js +94 -0
  53. package/src/components/HumanTaskProps.ts +27 -0
  54. package/src/components/InferDeps.ts +8 -0
  55. package/src/components/Kanban.js +68 -0
  56. package/src/components/KanbanProps.ts +27 -0
  57. package/src/components/Loop.js +6 -0
  58. package/src/components/LoopProps.ts +11 -0
  59. package/src/components/MergeQueue.js +16 -0
  60. package/src/components/MergeQueueProps.ts +12 -0
  61. package/src/components/Optimizer.js +52 -0
  62. package/src/components/OptimizerProps.ts +25 -0
  63. package/src/components/OutputTarget.ts +6 -0
  64. package/src/components/Panel.js +69 -0
  65. package/src/components/PanelProps.ts +17 -0
  66. package/src/components/PanelistConfig.ts +7 -0
  67. package/src/components/Parallel.js +16 -0
  68. package/src/components/ParallelProps.ts +8 -0
  69. package/src/components/Poller.js +69 -0
  70. package/src/components/PollerProps.ts +24 -0
  71. package/src/components/Ralph.js +17 -0
  72. package/src/components/RalphProps.ts +4 -0
  73. package/src/components/ReviewLoop.js +51 -0
  74. package/src/components/ReviewLoopProps.ts +23 -0
  75. package/src/components/Runbook.js +91 -0
  76. package/src/components/RunbookProps.ts +19 -0
  77. package/src/components/RunbookStep.ts +17 -0
  78. package/src/components/Saga.js +77 -0
  79. package/src/components/SagaProps.ts +10 -0
  80. package/src/components/SagaStepDef.ts +8 -0
  81. package/src/components/SagaStepProps.ts +7 -0
  82. package/src/components/Sandbox.js +48 -0
  83. package/src/components/SandboxProps.ts +46 -0
  84. package/src/components/SandboxRuntime.ts +1 -0
  85. package/src/components/SandboxVolumeMount.ts +5 -0
  86. package/src/components/SandboxWorkspaceSpec.ts +6 -0
  87. package/src/components/ScanFixVerify.js +60 -0
  88. package/src/components/ScanFixVerifyProps.ts +30 -0
  89. package/src/components/Sequence.js +11 -0
  90. package/src/components/SequenceProps.ts +6 -0
  91. package/src/components/Signal.js +48 -0
  92. package/src/components/SignalProps.ts +21 -0
  93. package/src/components/SourceDef.ts +12 -0
  94. package/src/components/Subflow.js +32 -0
  95. package/src/components/SubflowProps.ts +33 -0
  96. package/src/components/SuperSmithers.js +102 -0
  97. package/src/components/SuperSmithersProps.ts +20 -0
  98. package/src/components/Supervisor.js +86 -0
  99. package/src/components/SupervisorProps.ts +28 -0
  100. package/src/components/Task.js +319 -0
  101. package/src/components/TaskProps.ts +57 -0
  102. package/src/components/Timer.js +42 -0
  103. package/src/components/TimerProps.ts +21 -0
  104. package/src/components/TryCatchFinally.js +35 -0
  105. package/src/components/TryCatchFinallyProps.ts +12 -0
  106. package/src/components/WaitForEvent.js +37 -0
  107. package/src/components/WaitForEventProps.ts +28 -0
  108. package/src/components/Workflow.js +10 -0
  109. package/src/components/WorkflowProps.ts +7 -0
  110. package/src/components/Worktree.js +17 -0
  111. package/src/components/WorktreeProps.ts +11 -0
  112. package/src/components/control-flow-utils.js +37 -0
  113. package/src/components/index.js +121 -0
  114. package/src/index.d.ts +1579 -0
  115. package/src/index.js +62 -0
  116. package/src/markdownComponents.js +44 -0
  117. package/src/renderMdx.js +26 -0
  118. package/src/types/react-dom-server.d.ts +1 -0
  119. package/src/types.ts +22 -0
  120. package/src/zod-to-example.js +87 -0
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { SmithersContext } from "@smithers-orchestrator/react-reconciler/context";
3
+ import { Task } from "./Task.js";
4
+ import { Loop } from "./Ralph.js";
5
+ /** @typedef {import("./PollerProps.ts").PollerProps} PollerProps */
6
+
7
+ /**
8
+ * Compute the timeout for a given attempt based on the backoff strategy.
9
+ * This effectively controls the interval between polls by setting
10
+ * the task's timeoutMs, giving the agent/compute time proportional
11
+ * to the backoff delay.
12
+ */
13
+ function computeTimeoutMs(attempt, baseMs, strategy) {
14
+ switch (strategy) {
15
+ case "linear":
16
+ return baseMs * (attempt + 1);
17
+ case "exponential":
18
+ return baseMs * Math.pow(2, attempt);
19
+ case "fixed":
20
+ default:
21
+ return baseMs;
22
+ }
23
+ }
24
+ /**
25
+ * @param {PollerProps} props
26
+ */
27
+ export function Poller(props) {
28
+ if (props.skipIf)
29
+ return null;
30
+ const ctx = React.useContext(SmithersContext);
31
+ const prefix = props.id ?? "poll";
32
+ const maxAttempts = props.maxAttempts ?? 30;
33
+ const backoff = props.backoff ?? "fixed";
34
+ const baseInterval = props.intervalMs ?? 5000;
35
+ const onTimeout = props.onTimeout ?? "fail";
36
+ const iteration = ctx?.iterations?.[`${prefix}-loop`] ?? ctx?.iteration ?? 0;
37
+ const checkRow = ctx?.outputMaybe(props.checkOutput, {
38
+ nodeId: `${prefix}-check`,
39
+ iteration,
40
+ });
41
+ const until = checkRow?.satisfied === true;
42
+ // Determine if check is an agent or a compute function
43
+ const isAgent = typeof props.check === "object" &&
44
+ props.check !== null &&
45
+ "generate" in props.check;
46
+ // Build the check task
47
+ const prompt = props.children ??
48
+ "Check whether the condition is satisfied. Return an object with a satisfied boolean.";
49
+ const checkTask = isAgent
50
+ ? React.createElement(Task, {
51
+ id: `${prefix}-check`,
52
+ output: props.checkOutput,
53
+ timeoutMs: computeTimeoutMs(iteration, baseInterval, backoff),
54
+ agent: props.check,
55
+ children: prompt,
56
+ })
57
+ : React.createElement(Task, {
58
+ id: `${prefix}-check`,
59
+ output: props.checkOutput,
60
+ timeoutMs: computeTimeoutMs(iteration, baseInterval, backoff),
61
+ children: props.check,
62
+ });
63
+ return React.createElement(Loop, {
64
+ id: `${prefix}-loop`,
65
+ until,
66
+ maxIterations: maxAttempts,
67
+ onMaxReached: onTimeout === "fail" ? "fail" : "return-last",
68
+ }, checkTask);
69
+ }
@@ -0,0 +1,24 @@
1
+ import type React from "react";
2
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
3
+ import type { OutputTarget } from "./OutputTarget.ts";
4
+
5
+ export type PollerProps = {
6
+ /** ID prefix for generated task/component ids. */
7
+ id?: string;
8
+ /** Agent or compute function that checks the condition. */
9
+ check: AgentLike | (() => unknown | Promise<unknown>);
10
+ /** Output schema for the check result. Must include `satisfied: boolean`. */
11
+ checkOutput: OutputTarget;
12
+ /** Maximum poll attempts. Default 30. */
13
+ maxAttempts?: number;
14
+ /** Backoff strategy between polls. Default "fixed". */
15
+ backoff?: "fixed" | "linear" | "exponential";
16
+ /** Base interval in milliseconds between polls. Default 5000. */
17
+ intervalMs?: number;
18
+ /** Behavior when maxAttempts is reached. Default "fail". */
19
+ onTimeout?: "fail" | "return-last";
20
+ /** Skip the entire component. */
21
+ skipIf?: boolean;
22
+ /** Prompt/condition description for the check agent. */
23
+ children?: React.ReactNode;
24
+ };
@@ -0,0 +1,17 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./RalphProps.ts").RalphProps} RalphProps */
3
+ // @smithers-type-exports-end
4
+
5
+ import React from "react";
6
+ /** @typedef {import("./LoopProps.ts").LoopProps} LoopProps */
7
+
8
+ /**
9
+ * @param {LoopProps} props
10
+ */
11
+ export function Loop(props) {
12
+ if (props.skipIf)
13
+ return null;
14
+ return React.createElement("smithers:ralph", props, props.children);
15
+ }
16
+ /** @deprecated Use `Loop` instead. */
17
+ export const Ralph = Loop;
@@ -0,0 +1,4 @@
1
+ import type { LoopProps } from "./LoopProps.ts";
2
+
3
+ /** @deprecated Use `LoopProps` instead. */
4
+ export type RalphProps = LoopProps;
@@ -0,0 +1,51 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./ReviewLoopProps.ts").ReviewLoopProps} ReviewLoopProps */
3
+ // @smithers-type-exports-end
4
+
5
+ import React from "react";
6
+ import { Loop } from "./Ralph.js";
7
+ import { Sequence } from "./Sequence.js";
8
+ import { Task } from "./Task.js";
9
+ /**
10
+ * Produce -> review -> fix -> repeat until approved.
11
+ *
12
+ * Composes Loop, Sequence, and Task to create a standard
13
+ * review-loop pattern. The producer receives the reviewer's
14
+ * feedback on subsequent iterations.
15
+ * @param {ReviewLoopProps} props
16
+ */
17
+ export function ReviewLoop(props) {
18
+ if (props.skipIf)
19
+ return null;
20
+ const { id, producer, reviewer, produceOutput, reviewOutput, maxIterations = 5, onMaxReached = "return-last", children, } = props;
21
+ const prefix = id ?? "review-loop";
22
+ const produceId = `${prefix}-produce`;
23
+ const reviewId = `${prefix}-review`;
24
+ // The Loop's `until` condition is always false here — at render time
25
+ // the runtime re-renders and re-evaluates. The composite defers the
26
+ // `until` condition to the host element which reads `reviewOutput`
27
+ // for the `approved` field.
28
+ //
29
+ // We pass `until={false}` because the condition is evaluated by the
30
+ // runtime reading the review output's `approved` field each frame.
31
+ // The Loop primitive re-renders the tree, and the runtime checks
32
+ // the review output to decide whether to continue.
33
+ const reviewerAgents = Array.isArray(reviewer) ? reviewer : [reviewer];
34
+ return React.createElement(Loop, {
35
+ id: prefix,
36
+ until: false,
37
+ maxIterations,
38
+ onMaxReached,
39
+ }, React.createElement(Sequence, null, React.createElement(Task, {
40
+ id: produceId,
41
+ output: produceOutput,
42
+ agent: producer,
43
+ children,
44
+ }), React.createElement(Task, {
45
+ id: reviewId,
46
+ output: reviewOutput,
47
+ agent: reviewerAgents.length === 1 ? reviewerAgents[0] : reviewerAgents,
48
+ needs: { produced: produceId },
49
+ children: `Review the produced work and decide whether to approve.`,
50
+ })));
51
+ }
@@ -0,0 +1,23 @@
1
+ import type React from "react";
2
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
3
+ import type { OutputTarget } from "./OutputTarget.ts";
4
+
5
+ export type ReviewLoopProps = {
6
+ id?: string;
7
+ /** Agent that produces or fixes the work each iteration. */
8
+ producer: AgentLike;
9
+ /** Agent (or agents) that reviews the produced work. */
10
+ reviewer: AgentLike | AgentLike[];
11
+ /** Output schema for the produced work. */
12
+ produceOutput: OutputTarget;
13
+ /** Output schema for the review result. Must include an `approved: boolean` field. */
14
+ reviewOutput: OutputTarget;
15
+ /** Maximum number of review cycles before stopping. @default 5 */
16
+ maxIterations?: number;
17
+ /** Behavior when maxIterations is reached. @default "return-last" */
18
+ onMaxReached?: "return-last" | "fail";
19
+ /** Skip the entire review loop. */
20
+ skipIf?: boolean;
21
+ /** Initial prompt for the producer (string or ReactNode). */
22
+ children: string | React.ReactNode;
23
+ };
@@ -0,0 +1,91 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./RunbookProps.ts").RunbookProps} RunbookProps */
3
+ /** @typedef {import("./RunbookStep.ts").RunbookStep} RunbookStep */
4
+ // @smithers-type-exports-end
5
+
6
+ import React from "react";
7
+ import { Sequence } from "./Sequence.js";
8
+ import { Task } from "./Task.js";
9
+ import { Approval } from "./Approval.js";
10
+ /**
11
+ * <Runbook> — Sequential steps with risk classification.
12
+ *
13
+ * Safe steps auto-execute. Risky and critical steps require human approval first.
14
+ * Composes: Sequence of [Approval? → Task] per step, chained via `needs`.
15
+ * @param {RunbookProps} props
16
+ */
17
+ export function Runbook(props) {
18
+ if (props.skipIf)
19
+ return null;
20
+ const prefix = props.id ?? "runbook";
21
+ const onDeny = props.onDeny ?? "fail";
22
+ const children = [];
23
+ let previousStepId;
24
+ for (let i = 0; i < props.steps.length; i++) {
25
+ const step = props.steps[i];
26
+ const stepId = `${prefix}-${step.id}`;
27
+ const agent = step.agent ?? props.defaultAgent;
28
+ const output = step.output ?? props.stepOutput;
29
+ const label = step.label ?? step.id;
30
+ // Build needs: each step depends on the previous step's completion
31
+ const needs = previousStepId
32
+ ? { previousStep: previousStepId }
33
+ : undefined;
34
+ if (step.risk === "safe") {
35
+ // Safe: plain Task, auto-executes
36
+ children.push(React.createElement(Task, {
37
+ key: stepId,
38
+ id: stepId,
39
+ output,
40
+ agent,
41
+ needs,
42
+ label: `[safe] ${label}`,
43
+ children: step.command ?? `Execute step: ${label}`,
44
+ }));
45
+ previousStepId = stepId;
46
+ }
47
+ else {
48
+ // Risky or critical: Approval gate then Task
49
+ const approvalId = `${stepId}-approval`;
50
+ const isCritical = step.risk === "critical";
51
+ const approvalTitle = props.approvalRequest?.title ??
52
+ `Approve ${isCritical ? "CRITICAL" : "risky"} step: ${label}`;
53
+ const approvalSummary = props.approvalRequest?.summary ??
54
+ (isCritical
55
+ ? `CRITICAL step requires elevated approval. Command: ${step.command ?? label}`
56
+ : `Risky step requires approval before execution. Command: ${step.command ?? label}`);
57
+ const approvalMeta = {
58
+ stepId: step.id,
59
+ risk: step.risk,
60
+ ...props.approvalRequest?.metadata,
61
+ };
62
+ if (isCritical) {
63
+ approvalMeta.elevated = true;
64
+ }
65
+ children.push(React.createElement(Approval, {
66
+ key: approvalId,
67
+ id: approvalId,
68
+ output: `${approvalId}-decision`,
69
+ request: {
70
+ title: approvalTitle,
71
+ summary: approvalSummary,
72
+ metadata: approvalMeta,
73
+ },
74
+ onDeny: onDeny === "skip" ? "skip" : "fail",
75
+ needs,
76
+ label: `Approve: ${label}`,
77
+ }));
78
+ children.push(React.createElement(Task, {
79
+ key: stepId,
80
+ id: stepId,
81
+ output,
82
+ agent,
83
+ needs: { approval: approvalId },
84
+ label: `[${step.risk}] ${label}`,
85
+ children: step.command ?? `Execute step: ${label}`,
86
+ }));
87
+ previousStepId = stepId;
88
+ }
89
+ }
90
+ return React.createElement(Sequence, null, ...children);
91
+ }
@@ -0,0 +1,19 @@
1
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
2
+ import type { ApprovalRequest } from "./ApprovalRequest.ts";
3
+ import type { RunbookStep } from "./RunbookStep.ts";
4
+ import type { OutputTarget } from "./OutputTarget.ts";
5
+
6
+ export type RunbookProps = {
7
+ id?: string;
8
+ /** Ordered steps to execute. */
9
+ steps: RunbookStep[];
10
+ /** Default agent for steps that don't specify one. */
11
+ defaultAgent?: AgentLike;
12
+ /** Default output schema for step results. */
13
+ stepOutput: OutputTarget;
14
+ /** Template for approval requests on risky/critical steps. */
15
+ approvalRequest?: Partial<ApprovalRequest>;
16
+ /** Behavior when a risky/critical step is denied: "fail" (default) or "skip". */
17
+ onDeny?: "fail" | "skip";
18
+ skipIf?: boolean;
19
+ };
@@ -0,0 +1,17 @@
1
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
2
+ import type { OutputTarget } from "./OutputTarget.ts";
3
+
4
+ export type RunbookStep = {
5
+ /** Unique step identifier. */
6
+ id: string;
7
+ /** Agent for this step (falls back to `defaultAgent`). */
8
+ agent?: AgentLike;
9
+ /** Shell command or instruction for the step. */
10
+ command?: string;
11
+ /** Risk classification: safe auto-executes, risky/critical require approval. */
12
+ risk: "safe" | "risky" | "critical";
13
+ /** Human-readable label for the step. */
14
+ label?: string;
15
+ /** Per-step output schema override. */
16
+ output?: OutputTarget;
17
+ };
@@ -0,0 +1,77 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./SagaProps.ts").SagaProps} SagaProps */
3
+ /** @typedef {import("./SagaStepDef.ts").SagaStepDef} SagaStepDef */
4
+ // @smithers-type-exports-end
5
+
6
+ import React from "react";
7
+ import { forceContinueOnFail } from "./control-flow-utils.js";
8
+ /** @typedef {import("./SagaStepProps.ts").SagaStepProps} SagaStepProps */
9
+
10
+ /**
11
+ * @param {SagaStepProps} _props
12
+ * @returns {React.ReactElement | null}
13
+ */
14
+ function SagaStep(_props) {
15
+ // SagaStep is a declarative marker — the Saga component reads its props
16
+ // directly from the children array. It does not render on its own.
17
+ return null;
18
+ }
19
+ /**
20
+ * Forward steps with registered compensations executed in reverse on failure/cancel.
21
+ *
22
+ * Use the `steps` prop for an array-driven API, or nest `<Saga.Step>` children
23
+ * for a declarative JSX style.
24
+ *
25
+ * Renders to `<smithers:saga>`.
26
+ * @param {SagaProps} props
27
+ */
28
+ export function Saga(props) {
29
+ if (props.skipIf)
30
+ return null;
31
+ const { steps, children, onFailure = "compensate", id, ...rest } = props;
32
+ // Collect steps from either the `steps` prop or Saga.Step children.
33
+ let resolvedSteps = [];
34
+ if (steps && steps.length > 0) {
35
+ resolvedSteps = steps;
36
+ }
37
+ else if (children) {
38
+ // Walk children to extract Saga.Step elements.
39
+ const childArr = React.Children.toArray(children);
40
+ for (const child of childArr) {
41
+ if (React.isValidElement(child) &&
42
+ (child.type === SagaStep || child.type.__isSagaStep)) {
43
+ const stepProps = child.props;
44
+ resolvedSteps.push({
45
+ id: stepProps.id,
46
+ action: stepProps.children,
47
+ compensation: stepProps.compensation,
48
+ });
49
+ }
50
+ }
51
+ }
52
+ // Build the host element props with step metadata.
53
+ const sagaProps = {
54
+ ...rest,
55
+ id,
56
+ onFailure,
57
+ __sagaSteps: resolvedSteps.map((s) => ({
58
+ id: s.id,
59
+ label: s.label,
60
+ })),
61
+ };
62
+ const actionChildren = resolvedSteps.map((step) => React.cloneElement(forceContinueOnFail(step.action), {
63
+ key: `saga-action-${step.id}`,
64
+ }));
65
+ const compensationChildren = resolvedSteps.map((step) => React.cloneElement(step.compensation, {
66
+ key: `saga-compensation-${step.id}`,
67
+ }));
68
+ // Store compensation elements on the host props for the engine.
69
+ sagaProps.__sagaCompensations = resolvedSteps.reduce((acc, step) => {
70
+ acc[step.id] = step.compensation;
71
+ return acc;
72
+ }, {});
73
+ return React.createElement("smithers:saga", sagaProps, React.createElement("smithers:saga-actions", null, ...actionChildren), React.createElement("smithers:saga-compensations", null, ...compensationChildren));
74
+ }
75
+ // Mark SagaStep for identification.
76
+ SagaStep.__isSagaStep = true;
77
+ Saga.Step = SagaStep;
@@ -0,0 +1,10 @@
1
+ import type React from "react";
2
+ import type { SagaStepDef } from "./SagaStepDef.ts";
3
+
4
+ export type SagaProps = {
5
+ id?: string;
6
+ steps?: SagaStepDef[];
7
+ onFailure?: "compensate" | "compensate-and-fail" | "fail";
8
+ skipIf?: boolean;
9
+ children?: React.ReactNode;
10
+ };
@@ -0,0 +1,8 @@
1
+ import type React from "react";
2
+
3
+ export type SagaStepDef = {
4
+ id: string;
5
+ action: React.ReactElement;
6
+ compensation: React.ReactElement;
7
+ label?: string;
8
+ };
@@ -0,0 +1,7 @@
1
+ import type React from "react";
2
+
3
+ export type SagaStepProps = {
4
+ id: string;
5
+ compensation: React.ReactElement;
6
+ children: React.ReactElement;
7
+ };
@@ -0,0 +1,48 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./SandboxRuntime.ts").SandboxRuntime} SandboxRuntime */
3
+ /** @typedef {import("./SandboxVolumeMount.ts").SandboxVolumeMount} SandboxVolumeMount */
4
+ /** @typedef {import("./SandboxWorkspaceSpec.ts").SandboxWorkspaceSpec} SandboxWorkspaceSpec */
5
+ // @smithers-type-exports-end
6
+
7
+ import React from "react";
8
+ /** @typedef {import("./SandboxProps.ts").SandboxProps} SandboxProps */
9
+
10
+ /**
11
+ * @param {SandboxProps} props
12
+ */
13
+ export function Sandbox(props) {
14
+ if (props.skipIf)
15
+ return null;
16
+ return React.createElement("smithers:sandbox", {
17
+ id: props.id,
18
+ key: props.key,
19
+ output: props.output,
20
+ runtime: props.runtime ?? "bubblewrap",
21
+ allowNetwork: props.allowNetwork,
22
+ reviewDiffs: props.reviewDiffs,
23
+ autoAcceptDiffs: props.autoAcceptDiffs,
24
+ image: props.image,
25
+ env: props.env,
26
+ ports: props.ports,
27
+ volumes: props.volumes,
28
+ memoryLimit: props.memoryLimit,
29
+ cpuLimit: props.cpuLimit,
30
+ command: props.command,
31
+ workspace: props.workspace,
32
+ timeoutMs: props.timeoutMs,
33
+ heartbeatTimeoutMs: props.heartbeatTimeoutMs,
34
+ heartbeatTimeout: props.heartbeatTimeout,
35
+ retries: props.retries,
36
+ retryPolicy: props.retryPolicy,
37
+ continueOnFail: props.continueOnFail,
38
+ cache: props.cache,
39
+ dependsOn: props.dependsOn,
40
+ needs: props.needs,
41
+ label: props.label ?? props.id,
42
+ meta: props.meta,
43
+ __smithersSandboxWorkflow: props.workflow,
44
+ __smithersSandboxInput: props.input,
45
+ __smithersSandboxRuntime: props.runtime ?? "bubblewrap",
46
+ __smithersSandboxChildren: props.children,
47
+ });
48
+ }
@@ -0,0 +1,46 @@
1
+ import type React from "react";
2
+ import type { CachePolicy } from "@smithers-orchestrator/scheduler/CachePolicy";
3
+ import type { RetryPolicy } from "@smithers-orchestrator/scheduler/RetryPolicy";
4
+ import type { SmithersWorkflow } from "../SmithersWorkflow.ts";
5
+ import type { OutputTarget } from "./OutputTarget.ts";
6
+ import type { SandboxRuntime } from "./SandboxRuntime.ts";
7
+ import type { SandboxVolumeMount } from "./SandboxVolumeMount.ts";
8
+ import type { SandboxWorkspaceSpec } from "./SandboxWorkspaceSpec.ts";
9
+
10
+ export type SandboxProps = {
11
+ id: string;
12
+ /** Child workflow definition. If omitted, createSmithers-bound Sandbox wrappers may provide one. */
13
+ workflow?: SmithersWorkflow<unknown>;
14
+ /** Input passed to the child workflow. */
15
+ input?: unknown;
16
+ output: OutputTarget;
17
+ runtime?: SandboxRuntime;
18
+ allowNetwork?: boolean;
19
+ reviewDiffs?: boolean;
20
+ autoAcceptDiffs?: boolean;
21
+ image?: string;
22
+ env?: Record<string, string>;
23
+ ports?: Array<{
24
+ host: number;
25
+ container: number;
26
+ }>;
27
+ volumes?: SandboxVolumeMount[];
28
+ memoryLimit?: string;
29
+ cpuLimit?: string;
30
+ command?: string;
31
+ workspace?: SandboxWorkspaceSpec;
32
+ skipIf?: boolean;
33
+ timeoutMs?: number;
34
+ heartbeatTimeoutMs?: number;
35
+ heartbeatTimeout?: number;
36
+ retries?: number;
37
+ retryPolicy?: RetryPolicy;
38
+ continueOnFail?: boolean;
39
+ cache?: CachePolicy;
40
+ dependsOn?: string[];
41
+ needs?: Record<string, string>;
42
+ label?: string;
43
+ meta?: Record<string, unknown>;
44
+ key?: string;
45
+ children?: React.ReactNode;
46
+ };
@@ -0,0 +1 @@
1
+ export type SandboxRuntime = "bubblewrap" | "docker" | "codeplane";
@@ -0,0 +1,5 @@
1
+ export type SandboxVolumeMount = {
2
+ host: string;
3
+ container: string;
4
+ readonly?: boolean;
5
+ };
@@ -0,0 +1,6 @@
1
+ export type SandboxWorkspaceSpec = {
2
+ name: string;
3
+ snapshotId?: string;
4
+ idleTimeoutSecs?: number;
5
+ persistence?: "ephemeral" | "sticky";
6
+ };
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import { Task } from "./Task.js";
3
+ import { Sequence } from "./Sequence.js";
4
+ import { Parallel } from "./Parallel.js";
5
+ import { Loop } from "./Ralph.js";
6
+ /** @typedef {import("./ScanFixVerifyProps.ts").ScanFixVerifyProps} ScanFixVerifyProps */
7
+
8
+ /**
9
+ * @param {ScanFixVerifyProps} props
10
+ */
11
+ export function ScanFixVerify(props) {
12
+ if (props.skipIf)
13
+ return null;
14
+ const prefix = props.id ?? "sfv";
15
+ const maxRetries = props.maxRetries ?? 3;
16
+ const fixers = Array.isArray(props.fixer) ? props.fixer : [props.fixer];
17
+ // The scan task finds problems
18
+ const scanTask = React.createElement(Task, {
19
+ id: `${prefix}-scan`,
20
+ output: props.scanOutput,
21
+ agent: props.scanner,
22
+ children: props.children ?? "Scan for problems and return an issues array.",
23
+ });
24
+ // Parallel fix tasks — one per issue slot. At render time we don't know
25
+ // how many issues there are, so we create a single fix task that the agent
26
+ // will apply to all discovered issues. The Parallel wrapper allows the
27
+ // runtime to fan out when the scan output becomes available.
28
+ const fixTask = React.createElement(Task, {
29
+ id: `${prefix}-fix`,
30
+ output: props.fixOutput,
31
+ agent: fixers[0],
32
+ dependsOn: [`${prefix}-scan`],
33
+ children: "Fix all issues identified by the scan. Address each problem found.",
34
+ });
35
+ const fixParallel = React.createElement(Parallel, { id: `${prefix}-fixes`, maxConcurrency: props.maxConcurrency }, fixTask);
36
+ // Verify that all fixes were applied correctly
37
+ const verifyTask = React.createElement(Task, {
38
+ id: `${prefix}-verify`,
39
+ output: props.verifyOutput,
40
+ agent: props.verifier,
41
+ dependsOn: [`${prefix}-fix`],
42
+ children: "Verify that all fixes were applied correctly. Return whether all issues are resolved.",
43
+ });
44
+ // The inner loop: scan → fix → verify, repeating until verification passes
45
+ const innerSequence = React.createElement(Sequence, null, scanTask, fixParallel, verifyTask);
46
+ const loop = React.createElement(Loop, {
47
+ id: `${prefix}-loop`,
48
+ until: false, // Re-evaluated at render time via reactive context
49
+ maxIterations: maxRetries,
50
+ onMaxReached: "return-last",
51
+ }, innerSequence);
52
+ // Final report task after the loop completes
53
+ const reportTask = React.createElement(Task, {
54
+ id: `${prefix}-report`,
55
+ output: props.reportOutput,
56
+ dependsOn: [`${prefix}-verify`],
57
+ children: "Produce a final summary report of all scan-fix-verify cycles, including what was found, what was fixed, and the final verification status.",
58
+ });
59
+ return React.createElement(Sequence, null, loop, reportTask);
60
+ }
@@ -0,0 +1,30 @@
1
+ import type React from "react";
2
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
3
+ import type { OutputTarget } from "./OutputTarget.ts";
4
+
5
+ export type ScanFixVerifyProps = {
6
+ /** ID prefix for generated task/component ids. */
7
+ id?: string;
8
+ /** Agent that scans for problems. */
9
+ scanner: AgentLike;
10
+ /** Agent (or agents) that fixes problems. When an array is provided, agents are cycled across issues. */
11
+ fixer: AgentLike | AgentLike[];
12
+ /** Agent that verifies the fixes were applied correctly. */
13
+ verifier: AgentLike;
14
+ /** Output schema for scan results. Should include `issues: Array`. */
15
+ scanOutput: OutputTarget;
16
+ /** Output schema for each individual fix. */
17
+ fixOutput: OutputTarget;
18
+ /** Output schema for verification results. */
19
+ verifyOutput: OutputTarget;
20
+ /** Output schema for the final summary report. */
21
+ reportOutput: OutputTarget;
22
+ /** Maximum number of parallel fix tasks. */
23
+ maxConcurrency?: number;
24
+ /** Maximum scan-fix-verify cycles before stopping. Default 3. */
25
+ maxRetries?: number;
26
+ /** Skip the entire component. */
27
+ skipIf?: boolean;
28
+ /** Prompt/context describing what to scan for. */
29
+ children?: React.ReactNode;
30
+ };
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ /** @typedef {import("./SequenceProps.ts").SequenceProps} SequenceProps */
3
+
4
+ /**
5
+ * @param {SequenceProps} props
6
+ */
7
+ export function Sequence(props) {
8
+ if (props.skipIf)
9
+ return null;
10
+ return React.createElement("smithers:sequence", props, props.children);
11
+ }
@@ -0,0 +1,6 @@
1
+ import type React from "react";
2
+
3
+ export type SequenceProps = {
4
+ skipIf?: boolean;
5
+ children?: React.ReactNode;
6
+ };