@smithers-orchestrator/components 0.24.0 → 0.25.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithers-orchestrator/components",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "React components for Smithers workflows",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,15 +24,15 @@
24
24
  "react": "^19.2.5",
25
25
  "react-dom": "^19.2.5",
26
26
  "zod": "^4.3.6",
27
- "@smithers-orchestrator/agents": "0.24.0",
28
- "@smithers-orchestrator/db": "0.24.0",
29
- "@smithers-orchestrator/driver": "0.24.0",
30
- "@smithers-orchestrator/errors": "0.24.0",
31
- "@smithers-orchestrator/graph": "0.24.0",
32
- "@smithers-orchestrator/memory": "0.24.0",
33
- "@smithers-orchestrator/scheduler": "0.24.0",
34
- "@smithers-orchestrator/react-reconciler": "0.24.0",
35
- "@smithers-orchestrator/observability": "0.24.0"
27
+ "@smithers-orchestrator/agents": "0.25.0",
28
+ "@smithers-orchestrator/driver": "0.25.0",
29
+ "@smithers-orchestrator/db": "0.25.0",
30
+ "@smithers-orchestrator/errors": "0.25.0",
31
+ "@smithers-orchestrator/memory": "0.25.0",
32
+ "@smithers-orchestrator/scheduler": "0.25.0",
33
+ "@smithers-orchestrator/observability": "0.25.0",
34
+ "@smithers-orchestrator/graph": "0.25.0",
35
+ "@smithers-orchestrator/react-reconciler": "0.25.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@tanstack/react-query": "^5.99.1",
@@ -4,6 +4,5 @@
4
4
  export type AspectAccumulator = {
5
5
  totalTokens: number;
6
6
  totalLatencyMs: number;
7
- totalCostUsd: number;
8
7
  taskCount: number;
9
8
  };
@@ -1,7 +1,6 @@
1
1
  // @smithers-type-exports-begin
2
2
  /** @typedef {import("./AspectAccumulator.ts").AspectAccumulator} AspectAccumulator */
3
3
  /** @typedef {import("./AspectContextValue.ts").AspectContextValue} AspectContextValue */
4
- /** @typedef {import("./CostBudgetConfig.ts").CostBudgetConfig} CostBudgetConfig */
5
4
  /** @typedef {import("./LatencySloConfig.ts").LatencySloConfig} LatencySloConfig */
6
5
  /** @typedef {import("./TokenBudgetConfig.ts").TokenBudgetConfig} TokenBudgetConfig */
7
6
  /** @typedef {import("./TrackingConfig.ts").TrackingConfig} TrackingConfig */
@@ -10,7 +9,8 @@
10
9
  import React from "react";
11
10
  /**
12
11
  * React context that propagates Aspects configuration down the component tree.
13
- * Budget configuration is declarative metadata and is not enforced yet.
12
+ * Tasks read from this context to attach budgets the engine enforces and to
13
+ * track metrics.
14
14
  * @type {React.Context<AspectContextValue | null>}
15
15
  */
16
16
  export const AspectContext = React.createContext(/** @type {AspectContextValue | null} */ (null));
@@ -23,7 +23,6 @@ export function createAccumulator() {
23
23
  return {
24
24
  totalTokens: 0,
25
25
  totalLatencyMs: 0,
26
- totalCostUsd: 0,
27
26
  taskCount: 0,
28
27
  };
29
28
  }
@@ -1,6 +1,5 @@
1
1
  import type { TokenBudgetConfig } from "./TokenBudgetConfig.ts";
2
2
  import type { LatencySloConfig } from "./LatencySloConfig.ts";
3
- import type { CostBudgetConfig } from "./CostBudgetConfig.ts";
4
3
  import type { TrackingConfig } from "./TrackingConfig.ts";
5
4
  import type { AspectAccumulator } from "./AspectAccumulator.ts";
6
5
 
@@ -10,7 +9,6 @@ import type { AspectAccumulator } from "./AspectAccumulator.ts";
10
9
  export type AspectContextValue = {
11
10
  tokenBudget?: TokenBudgetConfig;
12
11
  latencySlo?: LatencySloConfig;
13
- costBudget?: CostBudgetConfig;
14
12
  tracking: TrackingConfig;
15
13
  accumulator: AspectAccumulator;
16
14
  };
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Latency SLO configuration for Aspects.
3
3
  *
4
- * Runtime enforcement is not implemented yet; this is declarative metadata.
4
+ * The engine enforces the scope-wide `maxMs` wall-clock SLO at task-dispatch
5
+ * time, measured from the run's start.
5
6
  */
6
7
  export type LatencySloConfig = {
7
- /** Maximum total latency in milliseconds across all tasks. */
8
+ /** Maximum total wall-clock latency in milliseconds across all tasks. */
8
9
  maxMs: number;
9
- /** Optional per-task latency limit in milliseconds. */
10
+ /** Optional per-task latency limit in milliseconds. Not enforced yet. */
10
11
  perTask?: number;
11
- /** Requested future behavior when the SLO is exceeded. Default: "fail". */
12
+ /** Behavior when the SLO is exceeded. Default: "fail". */
12
13
  onExceeded?: "fail" | "warn";
13
14
  };
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Token budget configuration for Aspects.
3
3
  *
4
- * Runtime enforcement is not implemented yet; this is declarative metadata.
4
+ * The engine accumulates per-run token usage and enforces `max` at
5
+ * task-dispatch time.
5
6
  */
6
7
  export type TokenBudgetConfig = {
7
8
  /** Maximum total tokens across all tasks within the Aspects scope. */
8
9
  max: number;
9
- /** Optional per-task token limit. */
10
+ /** Optional per-task token limit. Not enforced yet. */
10
11
  perTask?: number;
11
- /** Requested future behavior when the budget is exceeded. Default: "fail". */
12
+ /** Behavior when the budget is exceeded. Default: "fail". */
12
13
  onExceeded?: "fail" | "warn" | "skip-remaining";
13
14
  };
@@ -6,6 +6,4 @@ export type TrackingConfig = {
6
6
  tokens?: boolean;
7
7
  /** Track latency. Default: true. */
8
8
  latency?: boolean;
9
- /** Track cost. Default: true. */
10
- cost?: boolean;
11
9
  };
@@ -21,7 +21,10 @@ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
21
21
 
22
22
  export const approvalDecisionSchema = z.object({
23
23
  approved: z.boolean(),
24
- note: z.string().nullable(),
24
+ // `note` is omitted entirely when no note was provided, so the default
25
+ // decision schema must accept an absent key (optional) as well as the
26
+ // legacy null/string shapes.
27
+ note: z.string().nullable().optional(),
25
28
  decidedBy: z.string().nullable(),
26
29
  decidedAt: z.string().datetime().nullable(),
27
30
  });
@@ -70,6 +73,25 @@ function defaultSchemaForMode(mode) {
70
73
  return approvalDecisionSchema;
71
74
  }
72
75
  }
76
+ /**
77
+ * @param {{ status?: string | null; note?: string | null; decidedBy?: string | null; decidedAtMs?: number | null } | undefined | null} approval
78
+ * @param {import("zod").ZodObject<import("zod").ZodRawShape>} outputSchema
79
+ * @returns {Record<string, unknown>}
80
+ */
81
+ function buildDecisionPayload(approval, outputSchema) {
82
+ const base = {
83
+ approved: approval?.status === "approved",
84
+ decidedBy: approval?.decidedBy ?? null,
85
+ decidedAt: approval?.decidedAtMs != null ? new Date(approval.decidedAtMs).toISOString() : null,
86
+ };
87
+ if (typeof approval?.note === "string") {
88
+ return { ...base, note: approval.note };
89
+ }
90
+ if (outputSchema.safeParse(base).success) {
91
+ return base;
92
+ }
93
+ return { ...base, note: null };
94
+ }
73
95
  /**
74
96
  * @param {ApprovalMode | undefined} mode
75
97
  * @returns {"select" | "rank" | "decision"}
@@ -120,6 +142,8 @@ export function Approval(props) {
120
142
  const mode = props.mode ?? "approve";
121
143
  const approvalMode = normalizeMode(mode);
122
144
  const options = normalizeOptions(props.options);
145
+ const outputSchema = props.outputSchema ??
146
+ (isZodObject(props.output) ? props.output : defaultSchemaForMode(mode));
123
147
  if ((mode === "select" || mode === "rank") && (!options || options.length === 0)) {
124
148
  throw new SmithersError("APPROVAL_OPTIONS_REQUIRED", `Approval ${props.id} requires options when mode="${mode}".`);
125
149
  }
@@ -175,19 +199,13 @@ export function Approval(props) {
175
199
  : approval?.note ?? null,
176
200
  };
177
201
  }
178
- return {
179
- approved: approval?.status === "approved",
180
- note: approval?.note ?? null,
181
- decidedBy: approval?.decidedBy ?? null,
182
- decidedAt: approval?.decidedAtMs != null ? new Date(approval.decidedAtMs).toISOString() : null,
183
- };
202
+ return buildDecisionPayload(approval, outputSchema);
184
203
  };
185
204
  return React.createElement("smithers:task", {
186
205
  id: props.id,
187
206
  key: props.key,
188
207
  output: props.output,
189
- outputSchema: props.outputSchema ??
190
- (isZodObject(props.output) ? props.output : defaultSchemaForMode(mode)),
208
+ outputSchema,
191
209
  dependsOn: props.dependsOn,
192
210
  needs: props.needs,
193
211
  needsApproval: true,
@@ -9,8 +9,8 @@ import { AspectContext, createAccumulator, } from "../aspects/AspectContext.js";
9
9
  *
10
10
  * Wraps a section of the workflow tree and propagates token budgets,
11
11
  * latency SLOs, and cost budgets to all descendant Task components
12
- * without modifying individual tasks. Runtime budget enforcement is not
13
- * implemented yet.
12
+ * without modifying individual tasks. The engine enforces the scope-wide
13
+ * budgets at task-dispatch time.
14
14
  *
15
15
  * ```tsx
16
16
  * <Aspects tokenBudget={{ max: 100_000, perTask: 20_000, onExceeded: "warn" }}>
@@ -21,18 +21,16 @@ import { AspectContext, createAccumulator, } from "../aspects/AspectContext.js";
21
21
  * @param {AspectsProps} props
22
22
  */
23
23
  export function Aspects(props) {
24
- const { tokenBudget, latencySlo, costBudget, tracking, children } = props;
24
+ const { tokenBudget, latencySlo, tracking, children } = props;
25
25
  // Merge with parent context if nested
26
26
  const parentCtx = React.useContext(AspectContext);
27
27
  const resolvedTracking = {
28
28
  tokens: tracking?.tokens ?? parentCtx?.tracking?.tokens ?? true,
29
29
  latency: tracking?.latency ?? parentCtx?.tracking?.latency ?? true,
30
- cost: tracking?.cost ?? parentCtx?.tracking?.cost ?? true,
31
30
  };
32
31
  const value = {
33
32
  tokenBudget: tokenBudget ?? parentCtx?.tokenBudget,
34
33
  latencySlo: latencySlo ?? parentCtx?.latencySlo,
35
- costBudget: costBudget ?? parentCtx?.costBudget,
36
34
  tracking: resolvedTracking,
37
35
  accumulator: parentCtx?.accumulator ?? createAccumulator(),
38
36
  };
@@ -1,16 +1,13 @@
1
1
  import type React from "react";
2
2
  import type { TokenBudgetConfig } from "../aspects/TokenBudgetConfig.ts";
3
3
  import type { LatencySloConfig } from "../aspects/LatencySloConfig.ts";
4
- import type { CostBudgetConfig } from "../aspects/CostBudgetConfig.ts";
5
4
  import type { TrackingConfig } from "../aspects/TrackingConfig.ts";
6
5
 
7
6
  export type AspectsProps = {
8
- /** Token budget metadata. Runtime enforcement is not implemented yet. */
7
+ /** Token budget max total tokens, optional per-task limit, and exceeded behavior. */
9
8
  tokenBudget?: TokenBudgetConfig;
10
- /** Latency SLO metadata. Runtime enforcement is not implemented yet. */
9
+ /** Latency SLO max total wall-clock latency and exceeded behavior. */
11
10
  latencySlo?: LatencySloConfig;
12
- /** Cost budget metadata. Runtime enforcement is not implemented yet. */
13
- costBudget?: CostBudgetConfig;
14
11
  /** Which metrics to track. Defaults to all enabled. */
15
12
  tracking?: TrackingConfig;
16
13
  /** Workflow content these aspects apply to. */
@@ -1,12 +1,24 @@
1
1
  import React from "react";
2
+ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
2
3
  /** @typedef {import("./BranchProps.ts").BranchProps} BranchProps */
3
4
 
4
5
  /**
5
6
  * @param {BranchProps} props
6
7
  */
7
8
  export function Branch(props) {
9
+ // <Branch> resolves its subtree from the `then`/`else` props; any JSX children
10
+ // would be silently dropped, removing those tasks from the graph with no
11
+ // feedback. Fail fast instead. (Checked before skipIf so a stray-children
12
+ // mistake still surfaces even on a skipped branch.)
13
+ if (props.children !== undefined && props.children !== null) {
14
+ throw new SmithersError("INVALID_INPUT", `<Branch> does not take children. Use the "then" and "else" props instead, e.g. ` +
15
+ `<Branch if={cond} then={<Task .../>} else={<Task .../>} />. ` +
16
+ `Children passed to <Branch> are silently ignored and would drop those tasks from the graph.`);
17
+ }
8
18
  if (props.skipIf)
9
19
  return null;
10
20
  const chosen = props.if ? props.then : (props.else ?? null);
11
- return React.createElement("smithers:branch", props, chosen);
21
+ // The branch is resolved to `chosen` at render time, so the host element
22
+ // carries no props of its own (align with the sanitizing structural components).
23
+ return React.createElement("smithers:branch", {}, chosen);
12
24
  }
@@ -5,4 +5,10 @@ export type BranchProps = {
5
5
  then: React.ReactElement;
6
6
  else?: React.ReactElement | null;
7
7
  skipIf?: boolean;
8
+ /**
9
+ * `<Branch>` resolves its subtree from `then`/`else`; it takes no children.
10
+ * Typed as `never` so passing JSX children is a compile-time error (the
11
+ * runtime also throws — children would otherwise be silently dropped).
12
+ */
13
+ children?: never;
8
14
  };
@@ -56,7 +56,7 @@ export function Panel(props) {
56
56
  ? `\n\nStrategy: VOTE. Count how many panelists agree. ${minAgree ? `Minimum agreement required: ${minAgree}.` : ""}`
57
57
  : strategy === "consensus"
58
58
  ? `\n\nStrategy: CONSENSUS. All panelists must converge. ${minAgree ? `Minimum agreement required: ${minAgree}.` : ""}`
59
- : `\n\nStrategy: SYNTHESIZE. Combine all panelist outputs into a single coherent result.`;
59
+ : `\n\nStrategy: SYNTHESIZE. Combine all panelist outputs into a single coherent result. Preserve each panelist's concrete, grounded findings verbatim (specific file paths, line numbers, identifiers, prior-PR references, and what already exists); reconcile disagreements with evidence. Do not over-generalize, drop specifics, or change the scope the panelists analyzed.`;
60
60
  const moderatorChildren = `Synthesize the following panelist outputs.${strategyPrompt}`;
61
61
  const moderatorTask = React.createElement(Task, {
62
62
  id: `${prefix}-moderator`,
@@ -11,7 +11,16 @@ import React from "react";
11
11
  export function Loop(props) {
12
12
  if (props.skipIf)
13
13
  return null;
14
- return React.createElement("smithers:ralph", props, props.children);
14
+ // Sanitize to the loop's host props (align with other structural components);
15
+ // key/skipIf are React/control props and children are passed separately.
16
+ const next = {
17
+ id: props.id,
18
+ until: props.until,
19
+ maxIterations: props.maxIterations,
20
+ onMaxReached: props.onMaxReached,
21
+ continueAsNewEvery: props.continueAsNewEvery,
22
+ };
23
+ return React.createElement("smithers:ralph", next, props.children);
15
24
  }
16
25
  /** @deprecated Use `Loop` instead. */
17
26
  export const Ralph = Loop;
@@ -4,14 +4,18 @@
4
4
  // @smithers-type-exports-end
5
5
 
6
6
  import React from "react";
7
+ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
7
8
  import { forceContinueOnFail } from "./control-flow-utils.js";
8
9
  /** @typedef {import("./SagaStepProps.ts").SagaStepProps} SagaStepProps */
9
10
 
10
11
  /**
12
+ * Declarative marker for a Saga step. Exported as a value so `import { SagaStep }`
13
+ * works; also available as `<Saga.Step>`.
14
+ *
11
15
  * @param {SagaStepProps} _props
12
16
  * @returns {React.ReactElement | null}
13
17
  */
14
- function SagaStep(_props) {
18
+ export function SagaStep(_props) {
15
19
  // SagaStep is a declarative marker — the Saga component reads its props
16
20
  // directly from the children array. It does not render on its own.
17
21
  return null;
@@ -39,7 +43,8 @@ export function Saga(props) {
39
43
  const childArr = React.Children.toArray(children);
40
44
  for (const child of childArr) {
41
45
  if (React.isValidElement(child) &&
42
- (child.type === SagaStep || child.type.__isSagaStep)) {
46
+ (child.type === SagaStep ||
47
+ (typeof child.type === "function" && child.type.__isSagaStep))) {
43
48
  const stepProps = child.props;
44
49
  resolvedSteps.push({
45
50
  id: stepProps.id,
@@ -62,9 +67,15 @@ export function Saga(props) {
62
67
  const actionChildren = resolvedSteps.map((step) => React.cloneElement(forceContinueOnFail(step.action), {
63
68
  key: `saga-action-${step.id}`,
64
69
  }));
65
- const compensationChildren = resolvedSteps.map((step) => React.cloneElement(step.compensation, {
66
- key: `saga-compensation-${step.id}`,
67
- }));
70
+ const compensationChildren = resolvedSteps.map((step) => {
71
+ if (!React.isValidElement(step.compensation)) {
72
+ throw new SmithersError("INVALID_INPUT", `<Saga id="${id}"> step "${step.id}" has an invalid compensation. Each step needs a single ` +
73
+ `element (e.g. a <Task/>) that undoes its action; a missing or inert compensation leaves dirty state on rollback.`);
74
+ }
75
+ return React.cloneElement(step.compensation, {
76
+ key: `saga-compensation-${step.id}`,
77
+ });
78
+ });
68
79
  // Store compensation elements on the host props for the engine.
69
80
  sagaProps.__sagaCompensations = resolvedSteps.reduce((acc, step) => {
70
81
  acc[step.id] = step.compensation;
@@ -7,5 +7,7 @@ import React from "react";
7
7
  export function Sequence(props) {
8
8
  if (props.skipIf)
9
9
  return null;
10
- return React.createElement("smithers:sequence", props, props.children);
10
+ // Sequence carries no host props of its own; pass an empty bag (align with
11
+ // the sanitizing structural components) so control props don't leak through.
12
+ return React.createElement("smithers:sequence", {}, props.children);
11
13
  }
@@ -0,0 +1,68 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./SidecarProps.ts").SidecarProps} SidecarProps */
3
+ // @smithers-type-exports-end
4
+
5
+ import React from "react";
6
+ import { Parallel } from "./Parallel.js";
7
+ import { Task } from "./Task.js";
8
+
9
+ /**
10
+ * Runs a primary task and a cheap shadow task over the same prompt.
11
+ *
12
+ * The primary task keeps the component id so downstream `needs` can consume it.
13
+ * The sidecar task is continue-on-fail and writes its own scorer rows.
14
+ *
15
+ * @param {SidecarProps} props
16
+ */
17
+ export function Sidecar(props) {
18
+ if (props.skipIf) return null;
19
+ const {
20
+ id = "sidecar",
21
+ agent,
22
+ sidecar,
23
+ output,
24
+ sidecarOutput,
25
+ scorers,
26
+ prompt,
27
+ input,
28
+ maxConcurrency,
29
+ groundTruth,
30
+ context,
31
+ primaryLabel,
32
+ sidecarLabel,
33
+ children,
34
+ } = props;
35
+ const promptNode = prompt ?? input ?? children;
36
+ const shadowId = `${id}-sidecar`;
37
+ return React.createElement(
38
+ Parallel,
39
+ { id: `${id}-parallel`, maxConcurrency },
40
+ React.createElement(
41
+ Task,
42
+ {
43
+ id,
44
+ output,
45
+ agent,
46
+ scorers,
47
+ groundTruth,
48
+ context,
49
+ label: primaryLabel,
50
+ },
51
+ promptNode,
52
+ ),
53
+ React.createElement(
54
+ Task,
55
+ {
56
+ id: shadowId,
57
+ output: sidecarOutput ?? output,
58
+ agent: sidecar,
59
+ continueOnFail: true,
60
+ scorers,
61
+ groundTruth,
62
+ context,
63
+ label: sidecarLabel,
64
+ },
65
+ promptNode,
66
+ ),
67
+ );
68
+ }
@@ -0,0 +1,6 @@
1
+ export type SidecarDelta = {
2
+ primaryScore: number | null;
3
+ sidecarScore: number | null;
4
+ delta: number | null;
5
+ cheaperWins: boolean;
6
+ };
@@ -0,0 +1,22 @@
1
+ import type React from "react";
2
+ import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
3
+ import type { ScorersMap } from "@smithers-orchestrator/graph/types";
4
+ import type { OutputTarget } from "./OutputTarget.ts";
5
+
6
+ export type SidecarProps = {
7
+ id?: string;
8
+ agent: AgentLike;
9
+ sidecar: AgentLike;
10
+ output: OutputTarget;
11
+ sidecarOutput?: OutputTarget;
12
+ scorers?: ScorersMap;
13
+ prompt?: string | React.ReactNode;
14
+ input?: string | React.ReactNode;
15
+ maxConcurrency?: number;
16
+ groundTruth?: unknown;
17
+ context?: unknown;
18
+ primaryLabel?: string;
19
+ sidecarLabel?: string;
20
+ skipIf?: boolean;
21
+ children?: string | React.ReactNode;
22
+ };
@@ -14,8 +14,8 @@ import React from "react";
14
14
  *
15
15
  * Internally expands to a sequence of tasks:
16
16
  * 1. Agent reads the strategy doc and target files
17
- * 2. Agent proposes modifications
18
- * 3. (If not dryRun) Compute task writes modifications to disk
17
+ * 2. (If not dryRun) Agent applies the modifications directly to disk
18
+ * 3. A compute marker records the apply and triggers the hot-reload system
19
19
  * 4. Agent generates a report of what changed
20
20
  *
21
21
  * ```tsx
@@ -36,7 +36,7 @@ export function SuperSmithers(props) {
36
36
  const prefix = idPrefix ?? "super-smithers";
37
37
  // Task 1: Read strategy and target files
38
38
  const readTaskId = `${prefix}-read`;
39
- const readOutput = reportOutput ?? "super-smithers-read";
39
+ const readOutput = "super-smithers-read";
40
40
  const strategyText = typeof strategy === "string" ? strategy : undefined;
41
41
  const strategyElement = typeof strategy !== "string" ? strategy : undefined;
42
42
  const readPrompt = strategyText
@@ -51,22 +51,26 @@ export function SuperSmithers(props) {
51
51
  agent,
52
52
  __smithersKind: "agent",
53
53
  }, readChildren);
54
- // Task 2: Propose modifications
54
+ // Task 2: Apply the modifications (or, in a dry run, propose them only)
55
55
  const proposeTaskId = `${prefix}-propose`;
56
- const proposeOutput = reportOutput ?? "super-smithers-propose";
56
+ const proposeOutput = "super-smithers-propose";
57
57
  const proposeTask = React.createElement("smithers:task", {
58
58
  id: proposeTaskId,
59
59
  output: proposeOutput,
60
60
  agent,
61
61
  dependsOn: [readTaskId],
62
62
  __smithersKind: "agent",
63
- }, "Based on your analysis, propose specific code modifications. " +
64
- "For each file, provide the exact changes needed as a list of edits. " +
65
- "Include the file path, the original code, and the replacement code for each change. " +
66
- (dryRun ? "This is a DRY RUN do not apply changes, only report them." : ""));
67
- // Task 3: Apply modifications (only if not dryRun)
63
+ }, dryRun
64
+ ? "Based on your analysis, propose specific code modifications. This is a DRY RUN — do NOT modify any files. " +
65
+ "For each file, provide the exact changes needed as a list of edits: the file path, the original code, and the replacement code."
66
+ : "Based on your analysis, apply the necessary code modifications directly to the target files using your file-editing tools. " +
67
+ "Make each edit on disk now. After applying them, list each file you changed with a short description of the change.");
68
+ // Task 3: Sync marker after the apply agent has written its edits (skipped on
69
+ // dry runs). The edits themselves are made by the agent in Task 2; this compute
70
+ // step is a dependency barrier so the report below only runs once the apply
71
+ // task has settled (and its settled write triggers the hot-reload system).
68
72
  const applyTaskId = `${prefix}-apply`;
69
- const applyOutput = reportOutput ?? "super-smithers-apply";
73
+ const applyOutput = "super-smithers-apply";
70
74
  const applyTask = !dryRun
71
75
  ? React.createElement("smithers:task", {
72
76
  id: applyTaskId,
@@ -74,9 +78,6 @@ export function SuperSmithers(props) {
74
78
  dependsOn: [proposeTaskId],
75
79
  __smithersKind: "compute",
76
80
  __smithersComputeFn: async () => {
77
- // The compute function has access to the proposed modifications
78
- // from the previous task via the engine context. The actual file
79
- // writes trigger the hot reload system.
80
81
  return { applied: true };
81
82
  },
82
83
  }, null)
@@ -223,8 +223,8 @@ export function Task(props) {
223
223
  ctx?.recordDeferredDep?.(props.id, depNodeIds ?? []);
224
224
  return null;
225
225
  }
226
- // Build aspect metadata to attach to the task element. Budget metadata is
227
- // declarative only until runtime enforcement is implemented.
226
+ // Build aspect metadata to attach to the task element so the engine can
227
+ // enforce budgets and track metrics at execution time.
228
228
  const aspectMeta = aspectCtx ? buildAspectMeta(aspectCtx) : undefined;
229
229
  const agentChain = Array.isArray(agent)
230
230
  ? fallbackAgent
@@ -287,12 +287,11 @@ export function Task(props) {
287
287
  }
288
288
  /**
289
289
  * Build the __aspects metadata object from the current AspectContext.
290
- * This is attached to the smithers:task element props. Budget metadata is not
291
- * enforced by the runtime yet.
290
+ * This is attached to the smithers:task element props so the engine can read
291
+ * budgets and tracking config at execution time.
292
292
  * @param {{
293
293
  * tokenBudget?: unknown;
294
294
  * latencySlo?: unknown;
295
- * costBudget?: unknown;
296
295
  * tracking?: unknown;
297
296
  * accumulator?: unknown;
298
297
  * }} aspectCtx
@@ -303,7 +302,6 @@ function buildAspectMeta(aspectCtx) {
303
302
  __aspects: {
304
303
  tokenBudget: aspectCtx.tokenBudget,
305
304
  latencySlo: aspectCtx.latencySlo,
306
- costBudget: aspectCtx.costBudget,
307
305
  tracking: aspectCtx.tracking,
308
306
  accumulator: aspectCtx.accumulator,
309
307
  },
@@ -54,6 +54,10 @@ export type TaskProps<Row, Output extends OutputTarget = OutputTarget, D extends
54
54
  cache?: CachePolicy;
55
55
  /** Optional scorers to evaluate this task's output after completion. */
56
56
  scorers?: ScorersMap;
57
+ /** Expected output supplied to scorers that compare against a reference answer. */
58
+ groundTruth?: unknown;
59
+ /** Additional source context supplied to scorers such as faithfulnessScorer. */
60
+ context?: unknown;
57
61
  /** Optional cross-run memory configuration. */
58
62
  memory?: TaskMemoryConfig;
59
63
  /** Request an immediate hijack handoff as soon as the task starts running. */
@@ -6,5 +6,8 @@ import React from "react";
6
6
  * @returns {React.DOMElement<WorkflowProps, Element>}
7
7
  */
8
8
  export function Workflow(props) {
9
- return React.createElement("smithers:workflow", props, props.children);
9
+ // Sanitize host props (align with other structural components): pass only the
10
+ // fields the host element carries, not the React children/control props.
11
+ const next = { name: props.name, cache: props.cache };
12
+ return React.createElement("smithers:workflow", next, props.children);
10
13
  }