/** * MissingInputsForm * * Renders the agent-emitted form spec (FormField[]) for inputs the agent could * not draft. The agent decides per-field which widget the plugin should use: * * - 'text' → * - 'select' → onChange(e.currentTarget.value)}> {field.options.map((opt) => ( ))} ); } // MultiSelectWidget — checkbox group capped at `field.max`. Value is the // array of currently-checked option labels. Used today for output selection // (the agent asks "which outputs?" via the reserved __outputs name; the // user's picks are routed into `runParams.outputs[]` by dispatchPreview). // // Unchecked rows are disabled once `value.length >= max` so the user can't // exceed the cap. Re-clicking a checked row always works (drops the entry). function MultiSelectWidget({ field, value, onChange, }: { field: FormField & { kind: 'multiSelect' }; value: unknown; onChange: (value: unknown) => void; }) { const selected: string[] = Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : []; const selectedSet = new Set(selected); const max = typeof field.max === 'number' && field.max > 0 ? field.max : null; const atCap = max !== null && selected.length >= max; const toggle = (opt: string) => { if (selectedSet.has(opt)) { onChange(selected.filter((s) => s !== opt)); } else { if (atCap) return; // ignore — UI also disables this row onChange([...selected, opt]); } }; return ( {max !== null ? ( Pick up to {max} ({selected.length}/{max} selected) ) : null} {field.options.map((opt) => { const checked = selectedSet.has(opt); const disabled = !checked && atCap; return ( toggle(opt)} /> {opt} ); })} ); } function TextWidget({ field, value, onChange, }: { field: FormField; value: unknown; onChange: (value: unknown) => void; }) { const stringValue = toStringInputValue(value); return ( onChange(e.currentTarget.value)} placeholder={placeholderFor(field)} /> ); } // One-tap "use suggested default" chip — only renders when the form field // carries a non-null suggestedDefault. Returns null otherwise so callers // can drop it unconditionally without spread guards. function SuggestedDefaultButton({ field, value, onChange, }: { field: FormField; value: unknown; onChange: (value: unknown) => void; }) { if (!field.suggestedDefault) return null; const stringValue = toStringInputValue(value); const isAcceptingSuggested = stringValue === String(field.suggestedDefault.value); return (