@bastani/atomic 0.5.12-3 → 0.5.12-5
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/.agents/skills/workflow-creator/SKILL.md +24 -17
- package/.agents/skills/workflow-creator/references/agent-sessions.md +67 -24
- package/.agents/skills/workflow-creator/references/computation-and-validation.md +5 -3
- package/.agents/skills/workflow-creator/references/control-flow.md +25 -11
- package/.agents/skills/workflow-creator/references/discovery-and-verification.md +3 -2
- package/.agents/skills/workflow-creator/references/failure-modes.md +35 -36
- package/.agents/skills/workflow-creator/references/getting-started.md +25 -12
- package/.agents/skills/workflow-creator/references/session-config.md +26 -5
- package/.agents/skills/workflow-creator/references/state-and-data-flow.md +3 -3
- package/.agents/skills/workflow-creator/references/workflow-inputs.md +52 -47
- package/README.md +63 -41
- package/package.json +2 -4
- package/src/commands/cli/workflow.ts +1 -1
- package/src/sdk/components/workflow-picker-panel.tsx +109 -47
- package/src/sdk/define-workflow.test.ts +58 -0
- package/src/sdk/define-workflow.ts +48 -30
- package/src/sdk/providers/claude.ts +234 -233
- package/src/sdk/runtime/discovery.ts +2 -3
- package/src/sdk/runtime/executor.ts +6 -1
- package/src/sdk/types.ts +24 -19
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +11 -30
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +7 -4
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +6 -2
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +32 -38
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +5 -1
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +5 -1
- package/src/sdk/workflows/index.ts +2 -2
|
@@ -391,7 +391,7 @@ async function runPickerMode(
|
|
|
391
391
|
|
|
392
392
|
/**
|
|
393
393
|
* Execute a workflow selected via the picker. The picker already stores
|
|
394
|
-
* free-form prompts under the `prompt` key
|
|
394
|
+
* free-form prompts under the canonical `prompt` key,
|
|
395
395
|
* so we can hand the inputs record straight through — no split between
|
|
396
396
|
* "prompt" and "structured inputs" is needed.
|
|
397
397
|
*/
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
createCliRenderer,
|
|
31
31
|
type CliRenderer,
|
|
32
32
|
type KeyEvent,
|
|
33
|
+
type ScrollBoxRenderable,
|
|
33
34
|
type TextareaRenderable,
|
|
34
35
|
} from "@opentui/core";
|
|
35
36
|
import {
|
|
@@ -117,19 +118,6 @@ export interface WorkflowPickerResult {
|
|
|
117
118
|
inputs: Record<string, string>;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
/** Fallback field used when a workflow has no structured input schema. */
|
|
121
|
-
const DEFAULT_PROMPT_INPUT: WorkflowInput = {
|
|
122
|
-
name: "prompt",
|
|
123
|
-
type: "text",
|
|
124
|
-
required: true,
|
|
125
|
-
description: "what do you want this workflow to do?",
|
|
126
|
-
placeholder: "describe your task…",
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/** Stable single-element array for free-form workflows — avoids allocating
|
|
130
|
-
* a new `[DEFAULT_PROMPT_INPUT]` on every useMemo recomputation. */
|
|
131
|
-
const DEFAULT_FIELDS: WorkflowInput[] = [DEFAULT_PROMPT_INPUT];
|
|
132
|
-
|
|
133
121
|
// ─── Helpers ────────────────────────────────────
|
|
134
122
|
|
|
135
123
|
const SOURCE_DISPLAY: Record<Source, string> = {
|
|
@@ -477,8 +465,7 @@ const Preview = memo(function Preview({
|
|
|
477
465
|
wf: WorkflowWithMetadata;
|
|
478
466
|
}) {
|
|
479
467
|
const theme = usePickerTheme();
|
|
480
|
-
const args
|
|
481
|
-
wf.inputs.length > 0 ? wf.inputs : DEFAULT_FIELDS;
|
|
468
|
+
const args = wf.inputs;
|
|
482
469
|
|
|
483
470
|
return (
|
|
484
471
|
<box
|
|
@@ -512,13 +499,16 @@ const Preview = memo(function Preview({
|
|
|
512
499
|
</span>
|
|
513
500
|
</text>
|
|
514
501
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
502
|
+
{args.length > 0 && (
|
|
503
|
+
<>
|
|
504
|
+
<box height={2} />
|
|
505
|
+
<SectionLabel label="ARGUMENTS" />
|
|
506
|
+
<box height={1} />
|
|
507
|
+
{args.map((f) => (
|
|
508
|
+
<ArgumentRow key={f.name} field={f} />
|
|
509
|
+
))}
|
|
510
|
+
</>
|
|
511
|
+
)}
|
|
522
512
|
</box>
|
|
523
513
|
);
|
|
524
514
|
});
|
|
@@ -651,7 +641,7 @@ function EnumContent({
|
|
|
651
641
|
selected,
|
|
652
642
|
focused,
|
|
653
643
|
}: {
|
|
654
|
-
values: string[];
|
|
644
|
+
values: readonly string[];
|
|
655
645
|
selected: string;
|
|
656
646
|
focused: boolean;
|
|
657
647
|
}) {
|
|
@@ -690,12 +680,14 @@ function EnumContent({
|
|
|
690
680
|
}
|
|
691
681
|
|
|
692
682
|
const Field = memo(function Field({
|
|
683
|
+
id,
|
|
693
684
|
field,
|
|
694
685
|
value,
|
|
695
686
|
focused,
|
|
696
687
|
onFieldInput,
|
|
697
688
|
onTextChangeRef,
|
|
698
689
|
}: {
|
|
690
|
+
id?: string;
|
|
699
691
|
field: WorkflowInput;
|
|
700
692
|
value: string;
|
|
701
693
|
focused: boolean;
|
|
@@ -719,7 +711,7 @@ const Field = memo(function Field({
|
|
|
719
711
|
);
|
|
720
712
|
|
|
721
713
|
return (
|
|
722
|
-
<box flexDirection="column">
|
|
714
|
+
<box id={id} flexDirection="column">
|
|
723
715
|
<box
|
|
724
716
|
border
|
|
725
717
|
borderStyle="rounded"
|
|
@@ -789,6 +781,54 @@ function InputPhase({
|
|
|
789
781
|
}) {
|
|
790
782
|
const theme = usePickerTheme();
|
|
791
783
|
const isStructured = workflow.inputs.length > 0;
|
|
784
|
+
const scrollboxRef = useRef<ScrollBoxRenderable>(null);
|
|
785
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
786
|
+
|
|
787
|
+
// Auto-scroll to keep the focused field visible.
|
|
788
|
+
// Sync scrollTop immediately so the visibility check below
|
|
789
|
+
// marks the field as visible on the same render pass.
|
|
790
|
+
useEffect(() => {
|
|
791
|
+
const sb = scrollboxRef.current;
|
|
792
|
+
const field = fields[focusedFieldIdx];
|
|
793
|
+
if (!sb || !field) return;
|
|
794
|
+
sb.scrollChildIntoView(`field-${field.name}`);
|
|
795
|
+
setScrollTop(sb.scrollTop);
|
|
796
|
+
}, [focusedFieldIdx, fields]);
|
|
797
|
+
|
|
798
|
+
// Sync scrollTop on every OpenTUI render frame via renderBefore.
|
|
799
|
+
// This replaces a polling timer — it fires at the renderer's native
|
|
800
|
+
// frame rate so the focused field defocuses within one frame of
|
|
801
|
+
// scrolling out of view, preventing the terminal cursor from
|
|
802
|
+
// bleeding into the fixed header above.
|
|
803
|
+
const syncScrollFrame = useCallback(function (this: unknown) {
|
|
804
|
+
const sb = scrollboxRef.current;
|
|
805
|
+
if (!sb) return;
|
|
806
|
+
setScrollTop((prev) => {
|
|
807
|
+
const cur = sb.scrollTop;
|
|
808
|
+
return cur !== prev ? cur : prev;
|
|
809
|
+
});
|
|
810
|
+
}, []);
|
|
811
|
+
|
|
812
|
+
// The bordered content box (where the cursor lives) must be fully
|
|
813
|
+
// inside the viewport. If even one row is clipped the field loses
|
|
814
|
+
// focus so the cursor can never land in a clipped row.
|
|
815
|
+
const isFocusedFieldVisible = useMemo(() => {
|
|
816
|
+
const sb = scrollboxRef.current;
|
|
817
|
+
if (!sb) return true;
|
|
818
|
+
const vpH = sb.viewport.height;
|
|
819
|
+
if (vpH <= 0) return true;
|
|
820
|
+
let y = 0;
|
|
821
|
+
for (let i = 0; i < fields.length; i++) {
|
|
822
|
+
const f = fields[i]!;
|
|
823
|
+
const inputH = f.type === "text" ? TEXT_FIELD_LINES + 2 : 3;
|
|
824
|
+
if (i === focusedFieldIdx) {
|
|
825
|
+
return y >= scrollTop && y + inputH <= scrollTop + vpH;
|
|
826
|
+
}
|
|
827
|
+
// Caption row (1) + spacer row (1) below the bordered box.
|
|
828
|
+
y += inputH + 2;
|
|
829
|
+
}
|
|
830
|
+
return true;
|
|
831
|
+
}, [fields, focusedFieldIdx, scrollTop]);
|
|
792
832
|
|
|
793
833
|
return (
|
|
794
834
|
<box
|
|
@@ -839,7 +879,7 @@ function InputPhase({
|
|
|
839
879
|
<box flexDirection="row" height={1}>
|
|
840
880
|
<text>
|
|
841
881
|
<span fg={theme.textDim}>
|
|
842
|
-
<strong>
|
|
882
|
+
<strong>INPUTS</strong>
|
|
843
883
|
</span>
|
|
844
884
|
</text>
|
|
845
885
|
<box flexGrow={1} />
|
|
@@ -851,20 +891,48 @@ function InputPhase({
|
|
|
851
891
|
</box>
|
|
852
892
|
<box height={1} />
|
|
853
893
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
894
|
+
<scrollbox
|
|
895
|
+
ref={scrollboxRef}
|
|
896
|
+
scrollY
|
|
897
|
+
viewportCulling
|
|
898
|
+
flexGrow={1}
|
|
899
|
+
renderBefore={syncScrollFrame}
|
|
900
|
+
style={{
|
|
901
|
+
rootOptions: {
|
|
902
|
+
backgroundColor: "transparent",
|
|
903
|
+
border: false,
|
|
904
|
+
},
|
|
905
|
+
contentOptions: {
|
|
906
|
+
flexDirection: "column",
|
|
907
|
+
},
|
|
908
|
+
verticalScrollbarOptions: {
|
|
909
|
+
showArrows: false,
|
|
910
|
+
trackOptions: {
|
|
911
|
+
foregroundColor: theme.border,
|
|
912
|
+
backgroundColor: theme.backgroundElement,
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
}}
|
|
916
|
+
>
|
|
917
|
+
{fields.map((f, i) => {
|
|
918
|
+
const active = i === focusedFieldIdx && isFocusedFieldVisible;
|
|
919
|
+
return (
|
|
920
|
+
<Field
|
|
921
|
+
key={f.name}
|
|
922
|
+
id={`field-${f.name}`}
|
|
923
|
+
field={f}
|
|
924
|
+
value={values[f.name] ?? ""}
|
|
925
|
+
focused={active}
|
|
926
|
+
onFieldInput={onFieldInput}
|
|
927
|
+
onTextChangeRef={
|
|
928
|
+
f.type === "text" && active
|
|
929
|
+
? onTextChangeRef
|
|
930
|
+
: undefined
|
|
931
|
+
}
|
|
932
|
+
/>
|
|
933
|
+
);
|
|
934
|
+
})}
|
|
935
|
+
</scrollbox>
|
|
868
936
|
</box>
|
|
869
937
|
);
|
|
870
938
|
}
|
|
@@ -1171,12 +1239,8 @@ function usePickerKeyboard(state: PickerKeyboardState): void {
|
|
|
1171
1239
|
key.stopPropagation();
|
|
1172
1240
|
const wf = focusedWfRef.current;
|
|
1173
1241
|
if (wf) {
|
|
1174
|
-
const inputs: readonly WorkflowInput[] =
|
|
1175
|
-
wf.inputs.length > 0
|
|
1176
|
-
? wf.inputs
|
|
1177
|
-
: DEFAULT_FIELDS;
|
|
1178
1242
|
const initial: Record<string, string> = {};
|
|
1179
|
-
for (const f of inputs) {
|
|
1243
|
+
for (const f of wf.inputs) {
|
|
1180
1244
|
initial[f.name] =
|
|
1181
1245
|
f.default ??
|
|
1182
1246
|
(f.type === "enum" ? (f.values?.[0] ?? "") : "");
|
|
@@ -1290,9 +1354,7 @@ export function WorkflowPicker({
|
|
|
1290
1354
|
const focusedWf = entries[clampedEntryIdx]?.workflow;
|
|
1291
1355
|
|
|
1292
1356
|
const currentFields = useMemo<readonly WorkflowInput[]>(
|
|
1293
|
-
() => focusedWf
|
|
1294
|
-
? focusedWf.inputs
|
|
1295
|
-
: DEFAULT_FIELDS,
|
|
1357
|
+
() => focusedWf?.inputs ?? [],
|
|
1296
1358
|
[focusedWf],
|
|
1297
1359
|
);
|
|
1298
1360
|
const currentField = currentFields[focusedFieldIdx];
|
|
@@ -170,3 +170,61 @@ describe("WorkflowBuilder.compile()", () => {
|
|
|
170
170
|
expect(() => builder.compile()).toThrow("has no run callback");
|
|
171
171
|
});
|
|
172
172
|
});
|
|
173
|
+
|
|
174
|
+
describe("WorkflowBuilder.for()", () => {
|
|
175
|
+
test("returns the same builder instance (type-only narrowing)", () => {
|
|
176
|
+
const builder = defineWorkflow({ name: "test" });
|
|
177
|
+
const narrowed = builder.for<"copilot">();
|
|
178
|
+
// Same instance — .for() only changes the TypeScript type, not the value
|
|
179
|
+
expect(narrowed === (builder as unknown)).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("chains with run and compile", () => {
|
|
183
|
+
const def = defineWorkflow({
|
|
184
|
+
name: "test",
|
|
185
|
+
inputs: [{ name: "greeting", type: "string" }],
|
|
186
|
+
})
|
|
187
|
+
.for<"copilot">()
|
|
188
|
+
.run(async () => {})
|
|
189
|
+
.compile();
|
|
190
|
+
expect(def.__brand).toBe("WorkflowDefinition");
|
|
191
|
+
expect(def.inputs[0]?.name).toBe("greeting");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("typed inputs (compile-time)", () => {
|
|
196
|
+
test("structured inputs restrict ctx.inputs keys", () => {
|
|
197
|
+
// This test validates that the type system correctly narrows
|
|
198
|
+
// ctx.inputs to only declared field names. The assertions below
|
|
199
|
+
// are runtime no-ops — the real check is that tsc compiles this
|
|
200
|
+
// file without errors (or produces errors only where expected).
|
|
201
|
+
defineWorkflow({
|
|
202
|
+
name: "typed-test",
|
|
203
|
+
inputs: [
|
|
204
|
+
{ name: "greeting", type: "string", required: true },
|
|
205
|
+
{ name: "style", type: "enum", values: ["formal", "casual"] },
|
|
206
|
+
],
|
|
207
|
+
})
|
|
208
|
+
.for<"copilot">()
|
|
209
|
+
.run(async (ctx) => {
|
|
210
|
+
// Declared keys are valid
|
|
211
|
+
const _g: string | undefined = ctx.inputs.greeting;
|
|
212
|
+
const _s: string | undefined = ctx.inputs.style;
|
|
213
|
+
// Undeclared key — would be a compile error without @ts-expect-error
|
|
214
|
+
// @ts-expect-error — "prompt" is not a declared input
|
|
215
|
+
ctx.inputs.prompt;
|
|
216
|
+
expect(true).toBe(true);
|
|
217
|
+
})
|
|
218
|
+
.compile();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("free-form workflows allow any key", () => {
|
|
222
|
+
defineWorkflow({ name: "freeform-test" })
|
|
223
|
+
.for<"copilot">()
|
|
224
|
+
.run(async (ctx) => {
|
|
225
|
+
const _p: string | undefined = ctx.inputs.prompt;
|
|
226
|
+
expect(true).toBe(true);
|
|
227
|
+
})
|
|
228
|
+
.compile();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Workflow Builder — defines a workflow with a single `.run()` entry point.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* defineWorkflow
|
|
5
|
+
* defineWorkflow({ name: "my-workflow", inputs: [...] })
|
|
6
|
+
* .for<"copilot">()
|
|
6
7
|
* .run(async (ctx) => {
|
|
7
8
|
* await ctx.stage({ name: "research" }, {}, {}, async (s) => { ... });
|
|
8
9
|
* await ctx.stage({ name: "plan" }, {}, {}, async (s) => { ... });
|
|
@@ -58,16 +59,42 @@ function validateWorkflowInput(input: WorkflowInput, workflowName: string): void
|
|
|
58
59
|
* Chainable workflow builder. Records the run callback,
|
|
59
60
|
* then .compile() seals it into a WorkflowDefinition.
|
|
60
61
|
*/
|
|
61
|
-
export class WorkflowBuilder<A extends AgentType = AgentType> {
|
|
62
|
+
export class WorkflowBuilder<A extends AgentType = AgentType, N extends string = string> {
|
|
62
63
|
/** @internal Brand for detection across package boundaries */
|
|
63
64
|
readonly __brand = "WorkflowBuilder" as const;
|
|
64
65
|
private readonly options: WorkflowOptions;
|
|
65
|
-
private runFn: ((ctx: WorkflowContext<A>) => Promise<void>) | null = null;
|
|
66
|
+
private runFn: ((ctx: WorkflowContext<A, N>) => Promise<void>) | null = null;
|
|
66
67
|
|
|
67
68
|
constructor(options: WorkflowOptions) {
|
|
68
69
|
this.options = options;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Narrow the agent type for this workflow while preserving typed inputs.
|
|
74
|
+
*
|
|
75
|
+
* Use `.for<"copilot">()` **before** `.run()` instead of passing the
|
|
76
|
+
* agent as a type parameter to `defineWorkflow`. This allows TypeScript
|
|
77
|
+
* to infer input names from the `inputs` array AND narrow the agent
|
|
78
|
+
* type for `stage()` callbacks.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* defineWorkflow({
|
|
83
|
+
* name: "my-workflow",
|
|
84
|
+
* inputs: [{ name: "greeting", type: "string" }],
|
|
85
|
+
* })
|
|
86
|
+
* .for<"copilot">()
|
|
87
|
+
* .run(async (ctx) => {
|
|
88
|
+
* ctx.inputs.greeting; // ✓ typed
|
|
89
|
+
* ctx.inputs.prompt; // ✗ compile error
|
|
90
|
+
* })
|
|
91
|
+
* .compile();
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
for<B extends AgentType>(): WorkflowBuilder<B, N> {
|
|
95
|
+
return this as unknown as WorkflowBuilder<B, N>;
|
|
96
|
+
}
|
|
97
|
+
|
|
71
98
|
/**
|
|
72
99
|
* Set the workflow's entry point.
|
|
73
100
|
*
|
|
@@ -76,7 +103,7 @@ export class WorkflowBuilder<A extends AgentType = AgentType> {
|
|
|
76
103
|
* reading completed session outputs. Use native TypeScript control flow
|
|
77
104
|
* (loops, conditionals, `Promise.all()`) for orchestration.
|
|
78
105
|
*/
|
|
79
|
-
run(fn: (ctx: WorkflowContext<A>) => Promise<void>): this {
|
|
106
|
+
run(fn: (ctx: WorkflowContext<A, N>) => Promise<void>): this {
|
|
80
107
|
if (this.runFn) {
|
|
81
108
|
throw new Error("run() can only be called once per workflow.");
|
|
82
109
|
}
|
|
@@ -93,7 +120,7 @@ export class WorkflowBuilder<A extends AgentType = AgentType> {
|
|
|
93
120
|
* After calling compile(), the returned object is consumed by the
|
|
94
121
|
* Atomic CLI runtime.
|
|
95
122
|
*/
|
|
96
|
-
compile(): WorkflowDefinition<A> {
|
|
123
|
+
compile(): WorkflowDefinition<A, N> {
|
|
97
124
|
if (!this.runFn) {
|
|
98
125
|
throw new Error(
|
|
99
126
|
`Workflow "${this.options.name}" has no run callback. ` +
|
|
@@ -133,45 +160,36 @@ export class WorkflowBuilder<A extends AgentType = AgentType> {
|
|
|
133
160
|
/**
|
|
134
161
|
* Entry point for defining a workflow.
|
|
135
162
|
*
|
|
136
|
-
*
|
|
163
|
+
* Write the `inputs` array inline so TypeScript infers literal field
|
|
164
|
+
* names and enforces them on `ctx.inputs`. Use `.for<Agent>()` to
|
|
165
|
+
* narrow the agent type while keeping typed inputs:
|
|
137
166
|
*
|
|
138
167
|
* @example
|
|
139
168
|
* ```typescript
|
|
140
169
|
* import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
141
170
|
*
|
|
142
|
-
* export default defineWorkflow
|
|
171
|
+
* export default defineWorkflow({
|
|
143
172
|
* name: "hello",
|
|
144
173
|
* description: "Two-session demo",
|
|
174
|
+
* inputs: [
|
|
175
|
+
* { name: "greeting", type: "string", required: true },
|
|
176
|
+
* ],
|
|
145
177
|
* })
|
|
178
|
+
* .for<"copilot">()
|
|
146
179
|
* .run(async (ctx) => {
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* {},
|
|
150
|
-
* {},
|
|
151
|
-
* async (s) => {
|
|
152
|
-
* // s.client: CopilotClient, s.session: CopilotSession
|
|
153
|
-
* await s.session.send({ prompt: s.inputs.prompt ?? "" });
|
|
154
|
-
* s.save(await s.session.getMessages());
|
|
155
|
-
* },
|
|
156
|
-
* );
|
|
157
|
-
* await ctx.stage(
|
|
158
|
-
* { name: "summarize" },
|
|
159
|
-
* {},
|
|
160
|
-
* {},
|
|
161
|
-
* async (s) => {
|
|
162
|
-
* const research = await s.transcript(describe);
|
|
163
|
-
* // ...
|
|
164
|
-
* },
|
|
165
|
-
* );
|
|
180
|
+
* ctx.inputs.greeting; // ✓ string | undefined
|
|
181
|
+
* ctx.inputs.prompt; // ✗ compile error — not declared
|
|
166
182
|
* })
|
|
167
183
|
* .compile();
|
|
168
184
|
* ```
|
|
169
185
|
*/
|
|
170
|
-
export function defineWorkflow<
|
|
171
|
-
|
|
172
|
-
|
|
186
|
+
export function defineWorkflow<
|
|
187
|
+
const I extends readonly WorkflowInput[] = readonly WorkflowInput[],
|
|
188
|
+
>(
|
|
189
|
+
options: WorkflowOptions<I>,
|
|
190
|
+
): WorkflowBuilder<AgentType, I[number]["name"]> {
|
|
173
191
|
if (!options.name || options.name.trim() === "") {
|
|
174
192
|
throw new Error("Workflow name is required.");
|
|
175
193
|
}
|
|
176
|
-
return new WorkflowBuilder<
|
|
194
|
+
return new WorkflowBuilder<AgentType, I[number]["name"]>(options);
|
|
177
195
|
}
|