@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 William Cory
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@smithers-orchestrator/components",
3
+ "version": "0.16.0",
4
+ "description": "React components for Smithers workflows",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./src/index.d.ts",
10
+ "import": "./src/index.js",
11
+ "default": "./src/index.js"
12
+ },
13
+ "./*": {
14
+ "types": "./src/index.d.ts",
15
+ "import": "./src/*.js",
16
+ "default": "./src/*.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "src/"
21
+ ],
22
+ "dependencies": {
23
+ "bippy": "^0.5.32",
24
+ "react": "^18.3.0 || ^19.0.0",
25
+ "react-dom": "^18.3.0 || ^19.0.0",
26
+ "zod": "^4.3.6",
27
+ "@smithers-orchestrator/agents": "0.16.0",
28
+ "@smithers-orchestrator/db": "0.16.0",
29
+ "@smithers-orchestrator/driver": "0.16.0",
30
+ "@smithers-orchestrator/errors": "0.16.0",
31
+ "@smithers-orchestrator/memory": "0.16.0",
32
+ "@smithers-orchestrator/graph": "0.16.0",
33
+ "@smithers-orchestrator/react-reconciler": "0.16.0",
34
+ "@smithers-orchestrator/observability": "0.16.0",
35
+ "@smithers-orchestrator/scheduler": "0.16.0",
36
+ "@smithers-orchestrator/scorers": "0.16.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/bun": "latest",
40
+ "typescript": "~5.9.3"
41
+ },
42
+ "scripts": {
43
+ "test": "bun test tests",
44
+ "typecheck": "tsc -p tsconfig.json --noEmit",
45
+ "build": "tsup --dts-only"
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export type { WorkflowDefinition as SmithersWorkflow } from "@smithers-orchestrator/driver/WorkflowDefinition";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Runtime accumulator for tracked metrics within an Aspects scope.
3
+ */
4
+ export type AspectAccumulator = {
5
+ totalTokens: number;
6
+ totalLatencyMs: number;
7
+ totalCostUsd: number;
8
+ taskCount: number;
9
+ };
@@ -0,0 +1,29 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./AspectAccumulator.ts").AspectAccumulator} AspectAccumulator */
3
+ /** @typedef {import("./AspectContextValue.ts").AspectContextValue} AspectContextValue */
4
+ /** @typedef {import("./CostBudgetConfig.ts").CostBudgetConfig} CostBudgetConfig */
5
+ /** @typedef {import("./LatencySloConfig.ts").LatencySloConfig} LatencySloConfig */
6
+ /** @typedef {import("./TokenBudgetConfig.ts").TokenBudgetConfig} TokenBudgetConfig */
7
+ /** @typedef {import("./TrackingConfig.ts").TrackingConfig} TrackingConfig */
8
+ // @smithers-type-exports-end
9
+
10
+ import React from "react";
11
+ /**
12
+ * React context that propagates Aspects configuration down the component tree.
13
+ * Tasks read from this context to enforce budgets and track metrics.
14
+ * @type {React.Context<AspectContextValue | null>}
15
+ */
16
+ export const AspectContext = React.createContext(/** @type {AspectContextValue | null} */ (null));
17
+ AspectContext.displayName = "AspectContext";
18
+ /**
19
+ * Create a fresh accumulator with zeroed counters.
20
+ * @returns {AspectAccumulator}
21
+ */
22
+ export function createAccumulator() {
23
+ return {
24
+ totalTokens: 0,
25
+ totalLatencyMs: 0,
26
+ totalCostUsd: 0,
27
+ taskCount: 0,
28
+ };
29
+ }
@@ -0,0 +1,16 @@
1
+ import type { TokenBudgetConfig } from "./TokenBudgetConfig.ts";
2
+ import type { LatencySloConfig } from "./LatencySloConfig.ts";
3
+ import type { CostBudgetConfig } from "./CostBudgetConfig.ts";
4
+ import type { TrackingConfig } from "./TrackingConfig.ts";
5
+ import type { AspectAccumulator } from "./AspectAccumulator.ts";
6
+
7
+ /**
8
+ * The value provided by AspectContext to descendant components.
9
+ */
10
+ export type AspectContextValue = {
11
+ tokenBudget?: TokenBudgetConfig;
12
+ latencySlo?: LatencySloConfig;
13
+ costBudget?: CostBudgetConfig;
14
+ tracking: TrackingConfig;
15
+ accumulator: AspectAccumulator;
16
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Cost budget configuration for Aspects.
3
+ */
4
+ export type CostBudgetConfig = {
5
+ /** Maximum total cost in USD across all tasks within the Aspects scope. */
6
+ maxUsd: number;
7
+ /** Behavior when the budget is exceeded. Default: "fail". */
8
+ onExceeded?: "fail" | "warn" | "skip-remaining";
9
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Latency SLO configuration for Aspects.
3
+ */
4
+ export type LatencySloConfig = {
5
+ /** Maximum total latency in milliseconds across all tasks. */
6
+ maxMs: number;
7
+ /** Optional per-task latency limit in milliseconds. */
8
+ perTask?: number;
9
+ /** Behavior when the SLO is exceeded. Default: "fail". */
10
+ onExceeded?: "fail" | "warn";
11
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Token budget configuration for Aspects.
3
+ */
4
+ export type TokenBudgetConfig = {
5
+ /** Maximum total tokens across all tasks within the Aspects scope. */
6
+ max: number;
7
+ /** Optional per-task token limit. */
8
+ perTask?: number;
9
+ /** Behavior when the budget is exceeded. Default: "fail". */
10
+ onExceeded?: "fail" | "warn" | "skip-remaining";
11
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tracking configuration — which metrics to track.
3
+ */
4
+ export type TrackingConfig = {
5
+ /** Track token usage. Default: true. */
6
+ tokens?: boolean;
7
+ /** Track latency. Default: true. */
8
+ latency?: boolean;
9
+ /** Track cost. Default: true. */
10
+ cost?: boolean;
11
+ };
@@ -0,0 +1,10 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./AspectAccumulator.ts").AspectAccumulator} AspectAccumulator */
3
+ /** @typedef {import("./AspectContextValue.ts").AspectContextValue} AspectContextValue */
4
+ /** @typedef {import("./CostBudgetConfig.ts").CostBudgetConfig} CostBudgetConfig */
5
+ /** @typedef {import("./LatencySloConfig.ts").LatencySloConfig} LatencySloConfig */
6
+ /** @typedef {import("./TokenBudgetConfig.ts").TokenBudgetConfig} TokenBudgetConfig */
7
+ /** @typedef {import("./TrackingConfig.ts").TrackingConfig} TrackingConfig */
8
+ // @smithers-type-exports-end
9
+
10
+ export { AspectContext, createAccumulator, } from "./AspectContext.js";
@@ -0,0 +1,211 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./ApprovalDecision.ts").ApprovalDecision} ApprovalDecision */
3
+ /** @typedef {import("./ApprovalRanking.ts").ApprovalRanking} ApprovalRanking */
4
+ /** @typedef {import("./ApprovalRequest.ts").ApprovalRequest} ApprovalRequest */
5
+ /** @typedef {import("./ApprovalSelection.ts").ApprovalSelection} ApprovalSelection */
6
+ // @smithers-type-exports-end
7
+
8
+ import React from "react";
9
+ import { z } from "zod";
10
+ import { SmithersContext } from "@smithers-orchestrator/react-reconciler/context";
11
+ import { getTaskRuntime } from "@smithers-orchestrator/driver/task-runtime";
12
+ import { SmithersDb } from "@smithers-orchestrator/db/adapter";
13
+ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
14
+ /** @typedef {import("./ApprovalAutoApprove.ts").ApprovalAutoApprove} ApprovalAutoApprove */
15
+ /** @typedef {import("./ApprovalMode.ts").ApprovalMode} ApprovalMode */
16
+ /** @typedef {import("./ApprovalOption.ts").ApprovalOption} ApprovalOption */
17
+ /**
18
+ * @template Row, Output
19
+ * @typedef {import("./ApprovalProps.ts").ApprovalProps<Row, Output>} ApprovalProps
20
+ */
21
+
22
+ export const approvalDecisionSchema = z.object({
23
+ approved: z.boolean(),
24
+ note: z.string().nullable(),
25
+ decidedBy: z.string().nullable(),
26
+ decidedAt: z.string().datetime().nullable(),
27
+ });
28
+ export const approvalSelectionSchema = z.object({
29
+ selected: z.string(),
30
+ notes: z.string().nullable(),
31
+ });
32
+ export const approvalRankingSchema = z.object({
33
+ ranked: z.array(z.string()),
34
+ notes: z.string().nullable(),
35
+ });
36
+ /**
37
+ * @param {unknown} value
38
+ * @returns {value is import("zod").ZodObject<import("zod").ZodRawShape>}
39
+ */
40
+ function isZodObject(value) {
41
+ return Boolean(value && typeof value === "object" && "shape" in value);
42
+ }
43
+ /**
44
+ * @template T
45
+ * @param {unknown} value
46
+ * @returns {T | null}
47
+ */
48
+ function parseJson(value) {
49
+ if (typeof value !== "string" || value.length === 0) {
50
+ return null;
51
+ }
52
+ try {
53
+ return JSON.parse(value);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * @param {ApprovalMode} mode
61
+ * @returns {import("zod").ZodObject<import("zod").ZodRawShape>}
62
+ */
63
+ function defaultSchemaForMode(mode) {
64
+ switch (mode) {
65
+ case "select":
66
+ return approvalSelectionSchema;
67
+ case "rank":
68
+ return approvalRankingSchema;
69
+ default:
70
+ return approvalDecisionSchema;
71
+ }
72
+ }
73
+ /**
74
+ * @param {ApprovalMode | undefined} mode
75
+ * @returns {"select" | "rank" | "decision"}
76
+ */
77
+ function normalizeMode(mode) {
78
+ switch (mode) {
79
+ case "select":
80
+ return "select";
81
+ case "rank":
82
+ return "rank";
83
+ default:
84
+ return "decision";
85
+ }
86
+ }
87
+ /**
88
+ * @param {ApprovalOption[] | undefined} options
89
+ * @returns {ApprovalOption[] | undefined}
90
+ */
91
+ function normalizeOptions(options) {
92
+ return options?.map((option) => ({
93
+ key: option.key,
94
+ label: option.label,
95
+ ...(option.summary ? { summary: option.summary } : {}),
96
+ ...(option.metadata ? { metadata: option.metadata } : {}),
97
+ }));
98
+ }
99
+ /**
100
+ * @param {ApprovalAutoApprove[keyof ApprovalAutoApprove]} callback
101
+ * @param {import("@smithers-orchestrator/driver").SmithersCtx<unknown> | null} ctx
102
+ * @returns {boolean | undefined}
103
+ */
104
+ function evaluateBooleanCallback(callback, ctx) {
105
+ if (typeof callback !== "function") {
106
+ return undefined;
107
+ }
108
+ return Boolean(/** @type {(ctx: import("@smithers-orchestrator/driver").SmithersCtx<unknown> | null) => boolean} */ (callback)(ctx));
109
+ }
110
+ /**
111
+ * @template Row
112
+ * @param {ApprovalProps<Row>} props
113
+ * @returns {React.ReactElement | null}
114
+ */
115
+ export function Approval(props) {
116
+ if (props.skipIf)
117
+ return null;
118
+ const smithersContext = props.smithersContext ?? SmithersContext;
119
+ const ctx = React.useContext(smithersContext);
120
+ const mode = props.mode ?? "approve";
121
+ const approvalMode = normalizeMode(mode);
122
+ const options = normalizeOptions(props.options);
123
+ if ((mode === "select" || mode === "rank") && (!options || options.length === 0)) {
124
+ throw new SmithersError("APPROVAL_OPTIONS_REQUIRED", `Approval ${props.id} requires options when mode="${mode}".`);
125
+ }
126
+ const autoApprove = props.autoApprove
127
+ ? {
128
+ ...(typeof props.autoApprove.after === "number" ? { after: props.autoApprove.after } : {}),
129
+ audit: props.autoApprove.audit !== false,
130
+ ...(evaluateBooleanCallback(props.autoApprove.condition, ctx) !== undefined
131
+ ? { conditionMet: evaluateBooleanCallback(props.autoApprove.condition, ctx) }
132
+ : {}),
133
+ ...(evaluateBooleanCallback(props.autoApprove.revertOn, ctx) !== undefined
134
+ ? { revertOnMet: evaluateBooleanCallback(props.autoApprove.revertOn, ctx) }
135
+ : {}),
136
+ }
137
+ : undefined;
138
+ const requestMeta = {
139
+ ...(props.request.summary ? { requestSummary: props.request.summary } : {}),
140
+ ...(options ? { approvalOptions: options } : {}),
141
+ ...(props.allowedScopes?.length ? { approvalAllowedScopes: props.allowedScopes } : {}),
142
+ ...(props.allowedUsers?.length ? { approvalAllowedUsers: props.allowedUsers } : {}),
143
+ ...(autoApprove ? { approvalAutoApprove: autoApprove } : {}),
144
+ ...props.request.metadata,
145
+ ...props.meta,
146
+ };
147
+ /**
148
+ * @returns {Promise<Row>}
149
+ */
150
+ const computeDecision = async () => {
151
+ const runtime = getTaskRuntime();
152
+ if (!runtime) {
153
+ throw new SmithersError("APPROVAL_OUTSIDE_TASK", "Approval decisions can only be resolved while a Smithers task is executing.");
154
+ }
155
+ const adapter = new SmithersDb(runtime.db);
156
+ const approval = await adapter.getApproval(runtime.runId, props.id, runtime.iteration);
157
+ const decision = parseJson(approval?.decisionJson);
158
+ if (approvalMode === "select") {
159
+ return {
160
+ selected: typeof decision?.selected === "string" ? decision.selected : "",
161
+ notes: typeof decision?.notes === "string"
162
+ ? decision.notes
163
+ : approval?.note ?? null,
164
+ };
165
+ }
166
+ if (approvalMode === "rank") {
167
+ return {
168
+ ranked: Array.isArray(decision?.ranked)
169
+ ? decision.ranked.filter((value) => typeof value === "string")
170
+ : [],
171
+ notes: typeof decision?.notes === "string"
172
+ ? decision.notes
173
+ : approval?.note ?? null,
174
+ };
175
+ }
176
+ return {
177
+ approved: approval?.status === "approved",
178
+ note: approval?.note ?? null,
179
+ decidedBy: approval?.decidedBy ?? null,
180
+ decidedAt: null,
181
+ };
182
+ };
183
+ return React.createElement("smithers:task", {
184
+ id: props.id,
185
+ key: props.key,
186
+ output: props.output,
187
+ outputSchema: props.outputSchema ??
188
+ (isZodObject(props.output) ? props.output : defaultSchemaForMode(mode)),
189
+ dependsOn: props.dependsOn,
190
+ needs: props.needs,
191
+ needsApproval: true,
192
+ waitAsync: props.async === true,
193
+ approvalMode,
194
+ approvalOnDeny: props.onDeny,
195
+ approvalOptions: options,
196
+ approvalAllowedScopes: props.allowedScopes,
197
+ approvalAllowedUsers: props.allowedUsers,
198
+ approvalAutoApprove: autoApprove,
199
+ timeoutMs: props.timeoutMs,
200
+ heartbeatTimeoutMs: props.heartbeatTimeoutMs,
201
+ heartbeatTimeout: props.heartbeatTimeout,
202
+ retries: props.retries,
203
+ retryPolicy: props.retryPolicy,
204
+ continueOnFail: props.continueOnFail,
205
+ cache: props.cache,
206
+ label: props.label ?? props.request.title,
207
+ meta: Object.keys(requestMeta).length > 0 ? requestMeta : undefined,
208
+ __smithersKind: "compute",
209
+ __smithersComputeFn: computeDecision,
210
+ });
211
+ }
@@ -0,0 +1,8 @@
1
+ import type { SmithersCtx } from "@smithers-orchestrator/driver";
2
+
3
+ export type ApprovalAutoApprove = {
4
+ after?: number;
5
+ condition?: ((ctx: SmithersCtx<unknown> | null) => boolean) | (() => boolean);
6
+ audit?: boolean;
7
+ revertOn?: ((ctx: SmithersCtx<unknown> | null) => boolean) | (() => boolean);
8
+ };
@@ -0,0 +1,4 @@
1
+ import type { z } from "zod";
2
+ import type { approvalDecisionSchema } from "./Approval.js";
3
+
4
+ export type ApprovalDecision = z.infer<typeof approvalDecisionSchema>;
@@ -0,0 +1,45 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./ApprovalGateProps.ts").ApprovalGateProps} ApprovalGateProps */
3
+ // @smithers-type-exports-end
4
+
5
+ import React from "react";
6
+ import { Branch } from "./Branch.js";
7
+ import { Approval } from "./Approval.js";
8
+ import { Task } from "./Task.js";
9
+ /**
10
+ * Conditional approval gate. Requires human approval only when `when` is true;
11
+ * otherwise auto-approves with a static `{ approved: true }` decision.
12
+ *
13
+ * Composes Branch + Approval + Task internally.
14
+ * @param {ApprovalGateProps} props
15
+ */
16
+ export function ApprovalGate(props) {
17
+ if (props.skipIf)
18
+ return null;
19
+ return React.createElement(Branch, {
20
+ if: props.when,
21
+ then: React.createElement(Approval, {
22
+ id: props.id,
23
+ output: props.output,
24
+ request: props.request,
25
+ onDeny: props.onDeny,
26
+ timeoutMs: props.timeoutMs,
27
+ heartbeatTimeoutMs: props.heartbeatTimeoutMs,
28
+ heartbeatTimeout: props.heartbeatTimeout,
29
+ retries: props.retries,
30
+ retryPolicy: props.retryPolicy,
31
+ continueOnFail: props.continueOnFail,
32
+ }),
33
+ else: React.createElement(Task, {
34
+ id: props.id,
35
+ output: props.output,
36
+ label: `${props.request.title} (auto-approved)`,
37
+ children: {
38
+ approved: true,
39
+ note: "auto-approved",
40
+ decidedBy: null,
41
+ decidedAt: null,
42
+ },
43
+ }),
44
+ });
45
+ }
@@ -0,0 +1,22 @@
1
+ import type { ApprovalRequest } from "./ApprovalRequest.ts";
2
+ import type { RetryPolicy } from "@smithers-orchestrator/scheduler/RetryPolicy";
3
+ import type { OutputTarget } from "./OutputTarget.ts";
4
+
5
+ export type ApprovalGateProps = {
6
+ id: string;
7
+ /** Where to persist the approval decision. */
8
+ output: OutputTarget;
9
+ /** Human-facing approval request. */
10
+ request: ApprovalRequest;
11
+ /** When `true`, approval is required. When `false`, auto-approves. */
12
+ when: boolean;
13
+ /** Behavior after denial. */
14
+ onDeny?: "fail" | "continue" | "skip";
15
+ skipIf?: boolean;
16
+ timeoutMs?: number;
17
+ heartbeatTimeoutMs?: number;
18
+ heartbeatTimeout?: number;
19
+ retries?: number;
20
+ retryPolicy?: RetryPolicy;
21
+ continueOnFail?: boolean;
22
+ };
@@ -0,0 +1 @@
1
+ export type ApprovalMode = "approve" | "select" | "rank";
@@ -0,0 +1,6 @@
1
+ export type ApprovalOption = {
2
+ key: string;
3
+ label: string;
4
+ summary?: string;
5
+ metadata?: Record<string, unknown>;
6
+ };
@@ -0,0 +1,42 @@
1
+ import type React from "react";
2
+ import type { z } from "zod";
3
+ import type { SmithersCtx } from "@smithers-orchestrator/driver";
4
+ import type { ApprovalMode } from "./ApprovalMode.ts";
5
+ import type { ApprovalOption } from "./ApprovalOption.ts";
6
+ import type { ApprovalRequest } from "./ApprovalRequest.ts";
7
+ import type { ApprovalAutoApprove } from "./ApprovalAutoApprove.ts";
8
+ import type { ApprovalDecision } from "./ApprovalDecision.ts";
9
+ import type { OutputTarget } from "./OutputTarget.ts";
10
+
11
+ export type ApprovalProps<Row = ApprovalDecision, Output extends OutputTarget = OutputTarget> = {
12
+ id: string;
13
+ mode?: ApprovalMode;
14
+ options?: ApprovalOption[];
15
+ /** Where to persist the approval decision. Pass a Zod schema from `outputs` (recommended), a Drizzle table, or a string key. */
16
+ output: Output;
17
+ outputSchema?: z.ZodObject<z.ZodRawShape>;
18
+ request: ApprovalRequest;
19
+ onDeny?: "fail" | "continue" | "skip";
20
+ allowedScopes?: string[];
21
+ allowedUsers?: string[];
22
+ autoApprove?: ApprovalAutoApprove;
23
+ /** Do not block unrelated downstream flow while this approval is pending. */
24
+ async?: boolean;
25
+ /** Explicit dependency on other task node IDs. */
26
+ dependsOn?: string[];
27
+ /** Named dependencies on other tasks. Keys become context keys, values are task node IDs. */
28
+ needs?: Record<string, string>;
29
+ skipIf?: boolean;
30
+ timeoutMs?: number;
31
+ heartbeatTimeoutMs?: number;
32
+ heartbeatTimeout?: number;
33
+ retries?: number;
34
+ retryPolicy?: import("@smithers-orchestrator/scheduler/RetryPolicy").RetryPolicy;
35
+ continueOnFail?: boolean;
36
+ cache?: import("@smithers-orchestrator/scheduler/CachePolicy").CachePolicy;
37
+ label?: string;
38
+ meta?: Record<string, unknown>;
39
+ key?: string;
40
+ children?: React.ReactNode;
41
+ smithersContext?: React.Context<SmithersCtx<unknown> | null>;
42
+ };
@@ -0,0 +1,4 @@
1
+ import type { z } from "zod";
2
+ import type { approvalRankingSchema } from "./Approval.js";
3
+
4
+ export type ApprovalRanking = z.infer<typeof approvalRankingSchema>;
@@ -0,0 +1,5 @@
1
+ export type ApprovalRequest = {
2
+ title: string;
3
+ summary?: string;
4
+ metadata?: Record<string, unknown>;
5
+ };
@@ -0,0 +1,4 @@
1
+ import type { z } from "zod";
2
+ import type { approvalSelectionSchema } from "./Approval.js";
3
+
4
+ export type ApprovalSelection = z.infer<typeof approvalSelectionSchema>;
@@ -0,0 +1,39 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./AspectsProps.ts").AspectsProps} AspectsProps */
3
+ // @smithers-type-exports-end
4
+
5
+ import React from "react";
6
+ import { AspectContext, createAccumulator, } from "../aspects/AspectContext.js";
7
+ /**
8
+ * Aspects — declarative cross-cutting concerns for workflow scopes.
9
+ *
10
+ * Wraps a section of the workflow tree and propagates token budgets,
11
+ * latency SLOs, and cost budgets to all descendant Task components
12
+ * without modifying individual tasks.
13
+ *
14
+ * ```tsx
15
+ * <Aspects tokenBudget={{ max: 100_000, perTask: 20_000, onExceeded: "warn" }}>
16
+ * <Task id="step1" ...>...</Task>
17
+ * <Task id="step2" ...>...</Task>
18
+ * </Aspects>
19
+ * ```
20
+ * @param {AspectsProps} props
21
+ */
22
+ export function Aspects(props) {
23
+ const { tokenBudget, latencySlo, costBudget, tracking, children } = props;
24
+ // Merge with parent context if nested
25
+ const parentCtx = React.useContext(AspectContext);
26
+ const resolvedTracking = {
27
+ tokens: tracking?.tokens ?? parentCtx?.tracking?.tokens ?? true,
28
+ latency: tracking?.latency ?? parentCtx?.tracking?.latency ?? true,
29
+ cost: tracking?.cost ?? parentCtx?.tracking?.cost ?? true,
30
+ };
31
+ const value = {
32
+ tokenBudget: tokenBudget ?? parentCtx?.tokenBudget,
33
+ latencySlo: latencySlo ?? parentCtx?.latencySlo,
34
+ costBudget: costBudget ?? parentCtx?.costBudget,
35
+ tracking: resolvedTracking,
36
+ accumulator: parentCtx?.accumulator ?? createAccumulator(),
37
+ };
38
+ return React.createElement(AspectContext.Provider, { value }, children);
39
+ }
@@ -0,0 +1,18 @@
1
+ import type React from "react";
2
+ import type { TokenBudgetConfig } from "../aspects/TokenBudgetConfig.ts";
3
+ import type { LatencySloConfig } from "../aspects/LatencySloConfig.ts";
4
+ import type { CostBudgetConfig } from "../aspects/CostBudgetConfig.ts";
5
+ import type { TrackingConfig } from "../aspects/TrackingConfig.ts";
6
+
7
+ export type AspectsProps = {
8
+ /** Token budget — max total tokens, optional per-task limit, and exceeded behavior. */
9
+ tokenBudget?: TokenBudgetConfig;
10
+ /** Latency SLO — max total latency, optional per-task limit, and exceeded behavior. */
11
+ latencySlo?: LatencySloConfig;
12
+ /** Cost budget — max total USD, and exceeded behavior. */
13
+ costBudget?: CostBudgetConfig;
14
+ /** Which metrics to track. Defaults to all enabled. */
15
+ tracking?: TrackingConfig;
16
+ /** Workflow content these aspects apply to. */
17
+ children?: React.ReactNode;
18
+ };
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ /** @typedef {import("./BranchProps.ts").BranchProps} BranchProps */
3
+
4
+ /**
5
+ * @param {BranchProps} props
6
+ */
7
+ export function Branch(props) {
8
+ if (props.skipIf)
9
+ return null;
10
+ const chosen = props.if ? props.then : (props.else ?? null);
11
+ return React.createElement("smithers:branch", props, chosen);
12
+ }
@@ -0,0 +1,8 @@
1
+ import type React from "react";
2
+
3
+ export type BranchProps = {
4
+ if: boolean;
5
+ then: React.ReactElement;
6
+ else?: React.ReactElement | null;
7
+ skipIf?: boolean;
8
+ };
@@ -0,0 +1,10 @@
1
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
2
+ import type { OutputTarget } from "./OutputTarget.ts";
3
+
4
+ export type CategoryConfig = {
5
+ agent: AgentLike;
6
+ /** Output schema for this category's route handler. Overrides `routeOutput`. */
7
+ output?: OutputTarget;
8
+ /** Optional prompt for the route handler. Receives the classified item. */
9
+ prompt?: (item: unknown) => string;
10
+ };
@@ -0,0 +1,8 @@
1
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
2
+
3
+ export type CheckConfig = {
4
+ id: string;
5
+ agent?: AgentLike;
6
+ command?: string;
7
+ label?: string;
8
+ };