({
plugin,
pluginId,
onDone
}: Props)
| 57 | onDone: (outcome: 'configured' | 'skipped' | 'error', detail?: string) => void; |
| 58 | }; |
| 59 | export function PluginOptionsFlow({ |
| 60 | plugin, |
| 61 | pluginId, |
| 62 | onDone |
| 63 | }: Props): React.ReactNode { |
| 64 | // Build the step list once at mount. Re-calling after a save would drop the |
| 65 | // item we just configured. |
| 66 | const [steps] = React.useState<ConfigStep[]>(() => { |
| 67 | const result: ConfigStep[] = []; |
| 68 | |
| 69 | // Top-level manifest.userConfig |
| 70 | const unconfigured = getUnconfiguredOptions(plugin); |
| 71 | if (Object.keys(unconfigured).length > 0) { |
| 72 | result.push({ |
| 73 | key: 'top-level', |
| 74 | title: `Configure ${plugin.name}`, |
| 75 | subtitle: 'Plugin options', |
| 76 | schema: unconfigured, |
| 77 | load: () => loadPluginOptions(pluginId), |
| 78 | save: values => savePluginOptions(pluginId, values, plugin.manifest.userConfig!) |
| 79 | }); |
| 80 | } |
| 81 | |
| 82 | // Per-channel userConfig (assistant-mode channels) |
| 83 | const channels: UnconfiguredChannel[] = getUnconfiguredChannels(plugin); |
| 84 | for (const channel of channels) { |
| 85 | result.push({ |
| 86 | key: `channel:${channel.server}`, |
| 87 | title: `Configure ${channel.displayName}`, |
| 88 | subtitle: `Plugin: ${plugin.name}`, |
| 89 | schema: channel.configSchema, |
| 90 | load: () => loadMcpServerUserConfig(pluginId, channel.server) ?? undefined, |
| 91 | save: values_0 => saveMcpServerUserConfig(pluginId, channel.server, values_0, channel.configSchema) |
| 92 | }); |
| 93 | } |
| 94 | return result; |
| 95 | }); |
| 96 | const [index, setIndex] = React.useState(0); |
| 97 | |
| 98 | // Latest-ref: lets the effect close over the current onDone without |
| 99 | // re-running when the parent re-renders. |
| 100 | const onDoneRef = React.useRef(onDone); |
| 101 | onDoneRef.current = onDone; |
| 102 | |
| 103 | // Nothing to configure → tell the caller and render nothing. Effect, |
| 104 | // not inline call: calling setState in the parent during our render |
| 105 | // is a React rules-of-hooks violation. |
| 106 | React.useEffect(() => { |
| 107 | if (steps.length === 0) { |
| 108 | onDoneRef.current('skipped'); |
| 109 | } |
| 110 | }, [steps.length]); |
| 111 | if (steps.length === 0) { |
| 112 | return null; |
| 113 | } |
| 114 | const current = steps[index]!; |
| 115 | function handleSave(values_1: PluginOptionValues): void { |
| 116 | try { |
nothing calls this directly
no test coverage detected