@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.
- package/LICENSE +21 -0
- package/package.json +47 -0
- package/src/SmithersWorkflow.ts +1 -0
- package/src/aspects/AspectAccumulator.ts +9 -0
- package/src/aspects/AspectContext.js +29 -0
- package/src/aspects/AspectContextValue.ts +16 -0
- package/src/aspects/CostBudgetConfig.ts +9 -0
- package/src/aspects/LatencySloConfig.ts +11 -0
- package/src/aspects/TokenBudgetConfig.ts +11 -0
- package/src/aspects/TrackingConfig.ts +11 -0
- package/src/aspects/index.js +10 -0
- package/src/components/Approval.js +211 -0
- package/src/components/ApprovalAutoApprove.ts +8 -0
- package/src/components/ApprovalDecision.ts +4 -0
- package/src/components/ApprovalGate.js +45 -0
- package/src/components/ApprovalGateProps.ts +22 -0
- package/src/components/ApprovalMode.ts +1 -0
- package/src/components/ApprovalOption.ts +6 -0
- package/src/components/ApprovalProps.ts +42 -0
- package/src/components/ApprovalRanking.ts +4 -0
- package/src/components/ApprovalRequest.ts +5 -0
- package/src/components/ApprovalSelection.ts +4 -0
- package/src/components/Aspects.js +39 -0
- package/src/components/AspectsProps.ts +18 -0
- package/src/components/Branch.js +12 -0
- package/src/components/BranchProps.ts +8 -0
- package/src/components/CategoryConfig.ts +10 -0
- package/src/components/CheckConfig.ts +8 -0
- package/src/components/CheckSuite.js +71 -0
- package/src/components/CheckSuiteProps.ts +12 -0
- package/src/components/ClassifyAndRoute.js +75 -0
- package/src/components/ClassifyAndRouteProps.ts +30 -0
- package/src/components/ColumnDef.ts +19 -0
- package/src/components/ContentPipeline.js +38 -0
- package/src/components/ContentPipelineProps.ts +12 -0
- package/src/components/ContentPipelineStage.ts +13 -0
- package/src/components/ContinueAsNew.js +27 -0
- package/src/components/ContinueAsNewProps.ts +6 -0
- package/src/components/Debate.js +63 -0
- package/src/components/DebateProps.ts +15 -0
- package/src/components/DecisionRule.ts +10 -0
- package/src/components/DecisionTable.js +42 -0
- package/src/components/DecisionTableProps.ts +14 -0
- package/src/components/DepsSpec.ts +3 -0
- package/src/components/DriftDetector.js +54 -0
- package/src/components/DriftDetectorProps.ts +29 -0
- package/src/components/EscalationChain.js +99 -0
- package/src/components/EscalationChainProps.ts +20 -0
- package/src/components/EscalationLevel.ts +13 -0
- package/src/components/GatherAndSynthesize.js +69 -0
- package/src/components/GatherAndSynthesizeProps.ts +24 -0
- package/src/components/HumanTask.js +94 -0
- package/src/components/HumanTaskProps.ts +27 -0
- package/src/components/InferDeps.ts +8 -0
- package/src/components/Kanban.js +68 -0
- package/src/components/KanbanProps.ts +27 -0
- package/src/components/Loop.js +6 -0
- package/src/components/LoopProps.ts +11 -0
- package/src/components/MergeQueue.js +16 -0
- package/src/components/MergeQueueProps.ts +12 -0
- package/src/components/Optimizer.js +52 -0
- package/src/components/OptimizerProps.ts +25 -0
- package/src/components/OutputTarget.ts +6 -0
- package/src/components/Panel.js +69 -0
- package/src/components/PanelProps.ts +17 -0
- package/src/components/PanelistConfig.ts +7 -0
- package/src/components/Parallel.js +16 -0
- package/src/components/ParallelProps.ts +8 -0
- package/src/components/Poller.js +69 -0
- package/src/components/PollerProps.ts +24 -0
- package/src/components/Ralph.js +17 -0
- package/src/components/RalphProps.ts +4 -0
- package/src/components/ReviewLoop.js +51 -0
- package/src/components/ReviewLoopProps.ts +23 -0
- package/src/components/Runbook.js +91 -0
- package/src/components/RunbookProps.ts +19 -0
- package/src/components/RunbookStep.ts +17 -0
- package/src/components/Saga.js +77 -0
- package/src/components/SagaProps.ts +10 -0
- package/src/components/SagaStepDef.ts +8 -0
- package/src/components/SagaStepProps.ts +7 -0
- package/src/components/Sandbox.js +48 -0
- package/src/components/SandboxProps.ts +46 -0
- package/src/components/SandboxRuntime.ts +1 -0
- package/src/components/SandboxVolumeMount.ts +5 -0
- package/src/components/SandboxWorkspaceSpec.ts +6 -0
- package/src/components/ScanFixVerify.js +60 -0
- package/src/components/ScanFixVerifyProps.ts +30 -0
- package/src/components/Sequence.js +11 -0
- package/src/components/SequenceProps.ts +6 -0
- package/src/components/Signal.js +48 -0
- package/src/components/SignalProps.ts +21 -0
- package/src/components/SourceDef.ts +12 -0
- package/src/components/Subflow.js +32 -0
- package/src/components/SubflowProps.ts +33 -0
- package/src/components/SuperSmithers.js +102 -0
- package/src/components/SuperSmithersProps.ts +20 -0
- package/src/components/Supervisor.js +86 -0
- package/src/components/SupervisorProps.ts +28 -0
- package/src/components/Task.js +319 -0
- package/src/components/TaskProps.ts +57 -0
- package/src/components/Timer.js +42 -0
- package/src/components/TimerProps.ts +21 -0
- package/src/components/TryCatchFinally.js +35 -0
- package/src/components/TryCatchFinallyProps.ts +12 -0
- package/src/components/WaitForEvent.js +37 -0
- package/src/components/WaitForEventProps.ts +28 -0
- package/src/components/Workflow.js +10 -0
- package/src/components/WorkflowProps.ts +7 -0
- package/src/components/Worktree.js +17 -0
- package/src/components/WorktreeProps.ts +11 -0
- package/src/components/control-flow-utils.js +37 -0
- package/src/components/index.js +121 -0
- package/src/index.d.ts +1579 -0
- package/src/index.js +62 -0
- package/src/markdownComponents.js +44 -0
- package/src/renderMdx.js +26 -0
- package/src/types/react-dom-server.d.ts +1 -0
- package/src/types.ts +22 -0
- package/src/zod-to-example.js +87 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { ApprovalRequest } from "./ApprovalRequest.ts";
|
|
3
|
+
import type { EscalationLevel } from "./EscalationLevel.ts";
|
|
4
|
+
import type { OutputTarget } from "./OutputTarget.ts";
|
|
5
|
+
|
|
6
|
+
export type EscalationChainProps = {
|
|
7
|
+
/** ID prefix for generated nodes. */
|
|
8
|
+
id?: string;
|
|
9
|
+
/** Ordered escalation levels. Each level runs only if the previous escalated. */
|
|
10
|
+
levels: EscalationLevel[];
|
|
11
|
+
/** If `true`, the final escalation produces a human approval node. */
|
|
12
|
+
humanFallback?: boolean;
|
|
13
|
+
/** Approval request config used when `humanFallback` is `true`. */
|
|
14
|
+
humanRequest?: ApprovalRequest;
|
|
15
|
+
/** Output target for escalation tracking at each level. */
|
|
16
|
+
escalationOutput: OutputTarget;
|
|
17
|
+
skipIf?: boolean;
|
|
18
|
+
/** Prompt / input passed to each agent level. */
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
|
|
2
|
+
import type { OutputTarget } from "./OutputTarget.ts";
|
|
3
|
+
|
|
4
|
+
export type EscalationLevel = {
|
|
5
|
+
/** Agent to handle this escalation level. */
|
|
6
|
+
agent: AgentLike;
|
|
7
|
+
/** Output target for this level's result. */
|
|
8
|
+
output: OutputTarget;
|
|
9
|
+
/** Display label for this level. */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** Predicate evaluated on the level's result. Return `true` to escalate. */
|
|
12
|
+
escalateIf?: (result: unknown) => boolean;
|
|
13
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("./GatherAndSynthesizeProps.ts").GatherAndSynthesizeProps} GatherAndSynthesizeProps */
|
|
3
|
+
/** @typedef {import("./SourceDef.ts").SourceDef} SourceDef */
|
|
4
|
+
// @smithers-type-exports-end
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Sequence } from "./Sequence.js";
|
|
8
|
+
import { Parallel } from "./Parallel.js";
|
|
9
|
+
import { Task } from "./Task.js";
|
|
10
|
+
/**
|
|
11
|
+
* <GatherAndSynthesize> — Parallel data collection from different sources,
|
|
12
|
+
* then synthesis into a unified result.
|
|
13
|
+
*
|
|
14
|
+
* Composes Sequence, Parallel, and Task. First a Parallel block gathers data
|
|
15
|
+
* from each source agent, then a synthesis Task receives all gathered data
|
|
16
|
+
* and produces a combined output.
|
|
17
|
+
* @param {GatherAndSynthesizeProps} props
|
|
18
|
+
*/
|
|
19
|
+
export function GatherAndSynthesize(props) {
|
|
20
|
+
if (props.skipIf)
|
|
21
|
+
return null;
|
|
22
|
+
const { id, sources, synthesizer, gatherOutput, synthesisOutput, gatheredResults, maxConcurrency, synthesisPrompt, children, } = props;
|
|
23
|
+
const prefix = id ?? "gather-and-synthesize";
|
|
24
|
+
const sourceNames = Object.keys(sources);
|
|
25
|
+
// Step 1: Parallel gather from all sources
|
|
26
|
+
const gatherTasks = sourceNames.map((name) => {
|
|
27
|
+
const source = sources[name];
|
|
28
|
+
const output = source.output ?? gatherOutput;
|
|
29
|
+
const taskId = `${prefix}-gather-${name}`;
|
|
30
|
+
const content = source.children ??
|
|
31
|
+
source.prompt ??
|
|
32
|
+
`Gather data from source "${name}".`;
|
|
33
|
+
return React.createElement(Task, {
|
|
34
|
+
key: taskId,
|
|
35
|
+
id: taskId,
|
|
36
|
+
output,
|
|
37
|
+
agent: source.agent,
|
|
38
|
+
label: `Gather: ${name}`,
|
|
39
|
+
children: content,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
const gatherParallel = React.createElement(Parallel, {
|
|
43
|
+
key: `${prefix}-gather`,
|
|
44
|
+
id: `${prefix}-gather`,
|
|
45
|
+
maxConcurrency,
|
|
46
|
+
}, ...gatherTasks);
|
|
47
|
+
// Step 2: Build needs map — synthesis task depends on all gather tasks
|
|
48
|
+
const needs = {};
|
|
49
|
+
for (const name of sourceNames) {
|
|
50
|
+
needs[name] = `${prefix}-gather-${name}`;
|
|
51
|
+
}
|
|
52
|
+
// Build synthesis prompt from gathered results
|
|
53
|
+
const defaultSynthesisPrompt = gatheredResults
|
|
54
|
+
? `Synthesize the following gathered data into a unified result:\n\n${Object.entries(gatheredResults)
|
|
55
|
+
.map(([name, data]) => `## ${name}\n${JSON.stringify(data, null, 2)}`)
|
|
56
|
+
.join("\n\n")}`
|
|
57
|
+
: `Synthesize the gathered data from sources: ${sourceNames.join(", ")}.`;
|
|
58
|
+
const synthesisContent = children ?? synthesisPrompt ?? defaultSynthesisPrompt;
|
|
59
|
+
const synthesisTask = React.createElement(Task, {
|
|
60
|
+
key: `${prefix}-synthesize`,
|
|
61
|
+
id: `${prefix}-synthesize`,
|
|
62
|
+
output: synthesisOutput,
|
|
63
|
+
agent: synthesizer,
|
|
64
|
+
needs,
|
|
65
|
+
label: "Synthesize",
|
|
66
|
+
children: synthesisContent,
|
|
67
|
+
});
|
|
68
|
+
return React.createElement(Sequence, null, gatherParallel, synthesisTask);
|
|
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
|
+
import type { SourceDef } from "./SourceDef.ts";
|
|
5
|
+
|
|
6
|
+
export type GatherAndSynthesizeProps = {
|
|
7
|
+
id?: string;
|
|
8
|
+
/** Record mapping source names to source definitions. */
|
|
9
|
+
sources: Record<string, SourceDef>;
|
|
10
|
+
/** Agent that synthesizes gathered data. */
|
|
11
|
+
synthesizer: AgentLike;
|
|
12
|
+
/** Default output schema for each source gather task. */
|
|
13
|
+
gatherOutput: OutputTarget;
|
|
14
|
+
/** Output schema for the synthesis task. */
|
|
15
|
+
synthesisOutput: OutputTarget;
|
|
16
|
+
/** Gathered results keyed by source name. Typically from ctx.outputMaybe(). */
|
|
17
|
+
gatheredResults?: Record<string, unknown> | null;
|
|
18
|
+
/** Max parallel gatherers. */
|
|
19
|
+
maxConcurrency?: number;
|
|
20
|
+
/** Prompt for the synthesis task. If omitted, a default prompt is generated. */
|
|
21
|
+
synthesisPrompt?: string;
|
|
22
|
+
skipIf?: boolean;
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { renderPromptToText } from "./Task.js";
|
|
3
|
+
import { getTaskRuntime } from "@smithers-orchestrator/driver/task-runtime";
|
|
4
|
+
import { SmithersDb } from "@smithers-orchestrator/db/adapter";
|
|
5
|
+
import { buildHumanRequestId } from "@smithers-orchestrator/db/buildHumanRequestId";
|
|
6
|
+
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
7
|
+
/** @typedef {import("./HumanTaskProps.ts").HumanTaskProps} HumanTaskProps */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {unknown} value
|
|
11
|
+
* @returns {value is import("zod").ZodObject<import("zod").ZodRawShape>}
|
|
12
|
+
*/
|
|
13
|
+
function isZodObject(value) {
|
|
14
|
+
return Boolean(value && typeof value === "object" && "shape" in value);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* @param {HumanTaskProps} props
|
|
18
|
+
* @returns {React.ReactElement | null}
|
|
19
|
+
*/
|
|
20
|
+
export function HumanTask(props) {
|
|
21
|
+
if (props.skipIf)
|
|
22
|
+
return null;
|
|
23
|
+
const maxAttempts = props.maxAttempts ?? 10;
|
|
24
|
+
const outputSchema = props.outputSchema ?? (isZodObject(props.output) ? props.output : undefined);
|
|
25
|
+
const promptText = renderPromptToText(props.prompt);
|
|
26
|
+
const humanMeta = {
|
|
27
|
+
humanTask: true,
|
|
28
|
+
maxAttempts,
|
|
29
|
+
prompt: promptText,
|
|
30
|
+
...props.meta,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* @returns {Promise<unknown>}
|
|
34
|
+
*/
|
|
35
|
+
const computeHumanInput = async () => {
|
|
36
|
+
const runtime = getTaskRuntime();
|
|
37
|
+
if (!runtime) {
|
|
38
|
+
throw new SmithersError("HUMAN_TASK_OUTSIDE_RUNTIME", "HumanTask can only be resolved while a Smithers task is executing.");
|
|
39
|
+
}
|
|
40
|
+
const adapter = new SmithersDb(runtime.db);
|
|
41
|
+
const requestId = buildHumanRequestId(runtime.runId, props.id, runtime.iteration);
|
|
42
|
+
const humanRequest = await adapter.getHumanRequest(requestId);
|
|
43
|
+
const approval = await adapter.getApproval(runtime.runId, props.id, runtime.iteration);
|
|
44
|
+
let rawInput = humanRequest?.responseJson ?? null;
|
|
45
|
+
if (rawInput == null &&
|
|
46
|
+
humanRequest?.status !== "cancelled" &&
|
|
47
|
+
humanRequest?.status !== "expired" &&
|
|
48
|
+
typeof approval?.note === "string") {
|
|
49
|
+
rawInput = approval.note;
|
|
50
|
+
await adapter.answerHumanRequest(requestId, rawInput, approval.decidedAtMs ?? Date.now(), approval.decidedBy ?? null);
|
|
51
|
+
}
|
|
52
|
+
if (rawInput == null) {
|
|
53
|
+
if (humanRequest?.status === "cancelled") {
|
|
54
|
+
throw new SmithersError("HUMAN_TASK_CANCELLED", `Human input for task "${props.id}" was cancelled.`);
|
|
55
|
+
}
|
|
56
|
+
throw new SmithersError("HUMAN_TASK_NO_INPUT", `No human input received for task "${props.id}".`);
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = typeof rawInput === "string" ? JSON.parse(rawInput) : rawInput;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new SmithersError("HUMAN_TASK_INVALID_JSON", `Human input for task "${props.id}" is not valid JSON.`);
|
|
64
|
+
}
|
|
65
|
+
// Validate against output schema if provided
|
|
66
|
+
if (outputSchema) {
|
|
67
|
+
const result = outputSchema.safeParse(parsed);
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
throw new SmithersError("HUMAN_TASK_VALIDATION_FAILED", `Human input for task "${props.id}" does not match the output schema: ${result.error.message}`);
|
|
70
|
+
}
|
|
71
|
+
return result.data;
|
|
72
|
+
}
|
|
73
|
+
return parsed;
|
|
74
|
+
};
|
|
75
|
+
return React.createElement("smithers:task", {
|
|
76
|
+
id: props.id,
|
|
77
|
+
key: props.key,
|
|
78
|
+
output: props.output,
|
|
79
|
+
outputSchema,
|
|
80
|
+
dependsOn: props.dependsOn,
|
|
81
|
+
needs: props.needs,
|
|
82
|
+
needsApproval: true,
|
|
83
|
+
waitAsync: props.async === true,
|
|
84
|
+
approvalMode: "decision",
|
|
85
|
+
timeoutMs: props.timeoutMs,
|
|
86
|
+
retries: maxAttempts - 1,
|
|
87
|
+
retryPolicy: { backoff: "fixed", initialDelayMs: 0 },
|
|
88
|
+
continueOnFail: props.continueOnFail,
|
|
89
|
+
label: props.label ?? `human:${props.id}`,
|
|
90
|
+
meta: humanMeta,
|
|
91
|
+
__smithersKind: "human",
|
|
92
|
+
__smithersComputeFn: computeHumanInput,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { OutputTarget } from "./OutputTarget.ts";
|
|
4
|
+
|
|
5
|
+
export type HumanTaskProps = {
|
|
6
|
+
id: string;
|
|
7
|
+
/** Where to store the human's response. */
|
|
8
|
+
output: OutputTarget;
|
|
9
|
+
/** Zod schema the human must conform to. Used for validation. */
|
|
10
|
+
outputSchema?: z.ZodObject<z.ZodRawShape>;
|
|
11
|
+
/** Instructions for the human (string or ReactNode). */
|
|
12
|
+
prompt: string | React.ReactNode;
|
|
13
|
+
/** Max validation retries before failure. */
|
|
14
|
+
maxAttempts?: number;
|
|
15
|
+
/** Do not block unrelated downstream flow while waiting for human input. */
|
|
16
|
+
async?: boolean;
|
|
17
|
+
skipIf?: boolean;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
continueOnFail?: boolean;
|
|
20
|
+
/** Explicit dependency on other task node IDs. */
|
|
21
|
+
dependsOn?: string[];
|
|
22
|
+
/** Named dependencies on other tasks. Keys become context keys, values are task node IDs. */
|
|
23
|
+
needs?: Record<string, string>;
|
|
24
|
+
label?: string;
|
|
25
|
+
meta?: Record<string, unknown>;
|
|
26
|
+
key?: string;
|
|
27
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DepsSpec } from "./DepsSpec.ts";
|
|
2
|
+
import type { InferOutputEntry } from "@smithers-orchestrator/driver/OutputAccessor";
|
|
3
|
+
|
|
4
|
+
type InferDepValue<T> = T extends string ? unknown : InferOutputEntry<T>;
|
|
5
|
+
|
|
6
|
+
export type InferDeps<D extends DepsSpec> = {
|
|
7
|
+
[K in keyof D]: InferDepValue<D[K]>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("./ColumnDef.ts").ColumnDef} ColumnDef */
|
|
3
|
+
/** @typedef {import("./KanbanProps.ts").KanbanProps} KanbanProps */
|
|
4
|
+
// @smithers-type-exports-end
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Sequence } from "./Sequence.js";
|
|
8
|
+
import { Parallel } from "./Parallel.js";
|
|
9
|
+
import { Loop } from "./Ralph.js";
|
|
10
|
+
import { Task } from "./Task.js";
|
|
11
|
+
/**
|
|
12
|
+
* <Kanban> — Process items through columns with pluggable ticket source.
|
|
13
|
+
*
|
|
14
|
+
* Composes Loop, Sequence, Parallel, and Task to create a board where items
|
|
15
|
+
* flow through columns. Each column processes items via its assigned agent.
|
|
16
|
+
* Items in the same column can be processed in parallel.
|
|
17
|
+
* @param {KanbanProps} props
|
|
18
|
+
*/
|
|
19
|
+
export function Kanban(props) {
|
|
20
|
+
if (props.skipIf)
|
|
21
|
+
return null;
|
|
22
|
+
const { id, columns, useTickets, agents, maxConcurrency, onComplete, until = false, maxIterations = 5, children, } = props;
|
|
23
|
+
const prefix = id ?? "kanban";
|
|
24
|
+
const tickets = useTickets();
|
|
25
|
+
// Build a Sequence of columns. Each column processes all tickets in Parallel.
|
|
26
|
+
const columnElements = columns.map((col, colIdx) => {
|
|
27
|
+
const agent = agents?.[col.name] ?? col.agent;
|
|
28
|
+
const taskElements = tickets.map((item) => {
|
|
29
|
+
const taskId = `${prefix}-${col.name}-${item.id}`;
|
|
30
|
+
const taskProps = col.task ?? {};
|
|
31
|
+
const prompt = col.prompt
|
|
32
|
+
? col.prompt({ item, column: col.name })
|
|
33
|
+
: `Process item ${item.id} in column "${col.name}".`;
|
|
34
|
+
return React.createElement(Task, {
|
|
35
|
+
...taskProps,
|
|
36
|
+
key: `${col.name}-${item.id}`,
|
|
37
|
+
id: taskId,
|
|
38
|
+
output: col.output,
|
|
39
|
+
agent,
|
|
40
|
+
continueOnFail: taskProps.continueOnFail ?? true,
|
|
41
|
+
label: taskProps.label ?? `${col.name}: ${item.id}`,
|
|
42
|
+
children: prompt,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
return React.createElement(Parallel, {
|
|
46
|
+
key: `col-${colIdx}-${col.name}`,
|
|
47
|
+
id: `${prefix}-col-${col.name}`,
|
|
48
|
+
maxConcurrency,
|
|
49
|
+
}, ...taskElements);
|
|
50
|
+
});
|
|
51
|
+
const sequence = React.createElement(Sequence, null, ...columnElements);
|
|
52
|
+
const loop = React.createElement(Loop, {
|
|
53
|
+
id: `${prefix}-loop`,
|
|
54
|
+
until,
|
|
55
|
+
maxIterations,
|
|
56
|
+
onMaxReached: "return-last",
|
|
57
|
+
}, sequence);
|
|
58
|
+
if (!onComplete) {
|
|
59
|
+
return loop;
|
|
60
|
+
}
|
|
61
|
+
return React.createElement(Sequence, null, loop, React.createElement(Task, {
|
|
62
|
+
key: `${prefix}-complete`,
|
|
63
|
+
id: `${prefix}-complete`,
|
|
64
|
+
output: onComplete,
|
|
65
|
+
label: "Board complete",
|
|
66
|
+
children: children ?? null,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
|
|
3
|
+
import type { ColumnDef } from "./ColumnDef.ts";
|
|
4
|
+
import type { OutputTarget } from "./OutputTarget.ts";
|
|
5
|
+
|
|
6
|
+
export type KanbanProps = {
|
|
7
|
+
id?: string;
|
|
8
|
+
/** Column definitions in order. Items flow left to right. */
|
|
9
|
+
columns: ColumnDef[];
|
|
10
|
+
/** Function that returns ticket items to process. Each item must have an `id` field. */
|
|
11
|
+
useTickets: () => Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}>;
|
|
15
|
+
/** Record mapping column names to agents. Overrides column-level agents. */
|
|
16
|
+
agents?: Record<string, AgentLike>;
|
|
17
|
+
/** Max items processed in parallel per column. */
|
|
18
|
+
maxConcurrency?: number;
|
|
19
|
+
/** Callback output schema when an item reaches the final column. */
|
|
20
|
+
onComplete?: OutputTarget;
|
|
21
|
+
/** Whether the board loop is done. When true, the loop exits. */
|
|
22
|
+
until?: boolean;
|
|
23
|
+
/** Max iterations through the column pipeline. */
|
|
24
|
+
maxIterations?: number;
|
|
25
|
+
skipIf?: boolean;
|
|
26
|
+
children?: React.ReactNode | Record<string, unknown>;
|
|
27
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DEFAULT_MERGE_QUEUE_CONCURRENCY } from "@smithers-orchestrator/graph/constants";
|
|
3
|
+
/** @typedef {import("./MergeQueueProps.ts").MergeQueueProps} MergeQueueProps */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {MergeQueueProps} props
|
|
7
|
+
*/
|
|
8
|
+
export function MergeQueue(props) {
|
|
9
|
+
if (props.skipIf)
|
|
10
|
+
return null;
|
|
11
|
+
const next = {
|
|
12
|
+
maxConcurrency: props.maxConcurrency ?? DEFAULT_MERGE_QUEUE_CONCURRENCY,
|
|
13
|
+
id: props.id,
|
|
14
|
+
};
|
|
15
|
+
return React.createElement("smithers:merge-queue", next, props.children);
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Queue tasks so that at most `maxConcurrency` run concurrently across the group.
|
|
5
|
+
* Defaults to 1, providing an easy merge queue primitive.
|
|
6
|
+
*/
|
|
7
|
+
export type MergeQueueProps = {
|
|
8
|
+
id?: string;
|
|
9
|
+
maxConcurrency?: number;
|
|
10
|
+
skipIf?: boolean;
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("./OptimizerProps.ts").OptimizerProps} OptimizerProps */
|
|
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
|
+
* Generate -> evaluate -> improve loop with score convergence.
|
|
11
|
+
*
|
|
12
|
+
* Composes Loop, Sequence, and Task to create an iterative
|
|
13
|
+
* optimization pattern. Each iteration receives the previous
|
|
14
|
+
* score and feedback to guide improvement.
|
|
15
|
+
* @param {OptimizerProps} props
|
|
16
|
+
*/
|
|
17
|
+
export function Optimizer(props) {
|
|
18
|
+
if (props.skipIf)
|
|
19
|
+
return null;
|
|
20
|
+
const { id, generator, evaluator, generateOutput, evaluateOutput, targetScore, maxIterations = 10, onMaxReached = "return-last", children, } = props;
|
|
21
|
+
const prefix = id ?? "optimizer";
|
|
22
|
+
const generateId = `${prefix}-generate`;
|
|
23
|
+
const evaluateId = `${prefix}-evaluate`;
|
|
24
|
+
// `until` is false — the runtime re-renders and checks the evaluate
|
|
25
|
+
// output's `score` field against `targetScore` each frame.
|
|
26
|
+
// When no targetScore is set, the loop always runs all iterations.
|
|
27
|
+
const isAgentEvaluator = typeof evaluator !== "function";
|
|
28
|
+
return React.createElement(Loop, {
|
|
29
|
+
id: prefix,
|
|
30
|
+
until: false,
|
|
31
|
+
maxIterations,
|
|
32
|
+
onMaxReached,
|
|
33
|
+
}, React.createElement(Sequence, null, React.createElement(Task, {
|
|
34
|
+
id: generateId,
|
|
35
|
+
output: generateOutput,
|
|
36
|
+
agent: generator,
|
|
37
|
+
children,
|
|
38
|
+
}), isAgentEvaluator
|
|
39
|
+
? React.createElement(Task, {
|
|
40
|
+
id: evaluateId,
|
|
41
|
+
output: evaluateOutput,
|
|
42
|
+
agent: evaluator,
|
|
43
|
+
needs: { candidate: generateId },
|
|
44
|
+
children: `Evaluate the generated candidate and provide a score.`,
|
|
45
|
+
})
|
|
46
|
+
: React.createElement(Task, {
|
|
47
|
+
id: evaluateId,
|
|
48
|
+
output: evaluateOutput,
|
|
49
|
+
needs: { candidate: generateId },
|
|
50
|
+
children: evaluator,
|
|
51
|
+
})));
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 OptimizerProps = {
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Agent that generates or improves candidates each iteration. */
|
|
8
|
+
generator: AgentLike;
|
|
9
|
+
/** Agent (or compute function) that scores candidates. */
|
|
10
|
+
evaluator: AgentLike | ((candidate: unknown) => unknown | Promise<unknown>);
|
|
11
|
+
/** Output schema for generated candidates. */
|
|
12
|
+
generateOutput: OutputTarget;
|
|
13
|
+
/** Output schema for evaluation results. Must include a `score: number` field. */
|
|
14
|
+
evaluateOutput: OutputTarget;
|
|
15
|
+
/** Score threshold to stop early. When omitted, runs all iterations. */
|
|
16
|
+
targetScore?: number;
|
|
17
|
+
/** Maximum optimization rounds. @default 10 */
|
|
18
|
+
maxIterations?: number;
|
|
19
|
+
/** Behavior when maxIterations is reached. @default "return-last" */
|
|
20
|
+
onMaxReached?: "return-last" | "fail";
|
|
21
|
+
/** Skip the entire optimization loop. */
|
|
22
|
+
skipIf?: boolean;
|
|
23
|
+
/** Initial generation prompt (string or ReactNode). */
|
|
24
|
+
children: string | React.ReactNode;
|
|
25
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("./PanelProps.ts").PanelProps} PanelProps */
|
|
3
|
+
// @smithers-type-exports-end
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Sequence } from "./Sequence.js";
|
|
7
|
+
import { Parallel } from "./Parallel.js";
|
|
8
|
+
import { Task } from "./Task.js";
|
|
9
|
+
/** @typedef {import("@smithers-orchestrator/agents/AgentLike").AgentLike} AgentLike */
|
|
10
|
+
/** @typedef {import("./PanelistConfig.ts").PanelistConfig} PanelistConfig */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {PanelistConfig | AgentLike} entry
|
|
14
|
+
* @param {number} index
|
|
15
|
+
* @returns {PanelistConfig}
|
|
16
|
+
*/
|
|
17
|
+
function normalizePanelist(entry, index) {
|
|
18
|
+
if ("generate" in entry && !("agent" in entry)) {
|
|
19
|
+
return { agent: entry, label: `panelist-${index}` };
|
|
20
|
+
}
|
|
21
|
+
return entry;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* <Panel> — Parallel specialists review the same input, then a moderator synthesizes.
|
|
25
|
+
*
|
|
26
|
+
* Composes: Sequence > Parallel[Task per panelist] > Task(moderator)
|
|
27
|
+
* @param {PanelProps} props
|
|
28
|
+
*/
|
|
29
|
+
export function Panel(props) {
|
|
30
|
+
if (props.skipIf)
|
|
31
|
+
return null;
|
|
32
|
+
const { id, panelists, moderator, panelistOutput, moderatorOutput, strategy = "synthesize", minAgree, maxConcurrency, children, } = props;
|
|
33
|
+
const prefix = id ?? "panel";
|
|
34
|
+
const normalized = panelists.map(normalizePanelist);
|
|
35
|
+
// Build parallel panelist tasks
|
|
36
|
+
const panelistTasks = normalized.map((p, i) => {
|
|
37
|
+
const taskId = `${prefix}-${p.label ?? p.role ?? `panelist-${i}`}`;
|
|
38
|
+
return React.createElement(Task, {
|
|
39
|
+
key: taskId,
|
|
40
|
+
id: taskId,
|
|
41
|
+
output: panelistOutput,
|
|
42
|
+
agent: p.agent,
|
|
43
|
+
label: p.role ?? p.label,
|
|
44
|
+
children,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
const parallelEl = React.createElement(Parallel, { maxConcurrency }, ...panelistTasks);
|
|
48
|
+
// Build needs map: each panelist task id -> its task id
|
|
49
|
+
const needs = {};
|
|
50
|
+
normalized.forEach((p, i) => {
|
|
51
|
+
const taskId = `${prefix}-${p.label ?? p.role ?? `panelist-${i}`}`;
|
|
52
|
+
needs[taskId] = taskId;
|
|
53
|
+
});
|
|
54
|
+
// Moderator prompt includes strategy metadata
|
|
55
|
+
const strategyPrompt = strategy === "vote"
|
|
56
|
+
? `\n\nStrategy: VOTE. Count how many panelists agree. ${minAgree ? `Minimum agreement required: ${minAgree}.` : ""}`
|
|
57
|
+
: strategy === "consensus"
|
|
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.`;
|
|
60
|
+
const moderatorChildren = `Synthesize the following panelist outputs.${strategyPrompt}`;
|
|
61
|
+
const moderatorTask = React.createElement(Task, {
|
|
62
|
+
id: `${prefix}-moderator`,
|
|
63
|
+
output: moderatorOutput,
|
|
64
|
+
agent: moderator,
|
|
65
|
+
needs,
|
|
66
|
+
children: moderatorChildren,
|
|
67
|
+
});
|
|
68
|
+
return React.createElement(Sequence, null, parallelEl, moderatorTask);
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";
|
|
3
|
+
import type { PanelistConfig } from "./PanelistConfig.ts";
|
|
4
|
+
import type { OutputTarget } from "./OutputTarget.ts";
|
|
5
|
+
|
|
6
|
+
export type PanelProps = {
|
|
7
|
+
id?: string;
|
|
8
|
+
panelists: PanelistConfig[] | AgentLike[];
|
|
9
|
+
moderator: AgentLike;
|
|
10
|
+
panelistOutput: OutputTarget;
|
|
11
|
+
moderatorOutput: OutputTarget;
|
|
12
|
+
strategy?: "synthesize" | "vote" | "consensus";
|
|
13
|
+
minAgree?: number;
|
|
14
|
+
maxConcurrency?: number;
|
|
15
|
+
skipIf?: boolean;
|
|
16
|
+
children: string | React.ReactNode;
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/** @typedef {import("./ParallelProps.ts").ParallelProps} ParallelProps */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {ParallelProps} props
|
|
6
|
+
*/
|
|
7
|
+
export function Parallel(props) {
|
|
8
|
+
if (props.skipIf)
|
|
9
|
+
return null;
|
|
10
|
+
// Align prop sanitization with other structural components
|
|
11
|
+
const next = {
|
|
12
|
+
maxConcurrency: props.maxConcurrency,
|
|
13
|
+
id: props.id,
|
|
14
|
+
};
|
|
15
|
+
return React.createElement("smithers:parallel", next, props.children);
|
|
16
|
+
}
|