fix(pipeline): decouple Runner from extension permissions (LANG-54) (#2723)

* fix(pipeline): decouple runner from extension allowlist

Co-authored-by: multica-agent <[email protected]>

* fix(pipeline): complete runner diagnostics and real runtime coverage

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
RockChinQ
2026-10-19 23:07:11 +08:00
committed by GitHub
co-authored by multica-agent
parent 5a6ddbff6f
commit d9e4e7d9df
24 changed files with 1012 additions and 35 deletions
@@ -3,7 +3,7 @@ import { Bot, Puzzle, ExternalLink, Loader2, Store } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import { getCloudServiceClientSync } from '@/app/infra/http';
import { getCloudServiceClientSync, httpClient } from '@/app/infra/http';
import { useInstalledPluginIcon } from '@/app/infra/hooks/useInstalledPluginIcon';
import type { IDynamicFormItemOption } from '@/app/infra/entities/form/dynamic';
import type { PluginV4 } from '@/app/infra/entities/plugin';
@@ -233,6 +233,45 @@ export default function RunnerSelect({
useState<RunnerCatalog['installedPluginDescriptions']>({});
const [catalogLoading, setCatalogLoading] = useState(true);
const [catalogError, setCatalogError] = useState(false);
const [runnerProblem, setRunnerProblem] = useState<
'missing' | 'disabled' | 'runtime' | null
>(null);
const [diagnosing, setDiagnosing] = useState(false);
const diagnoseRunner = useCallback(async () => {
const pluginId = runnerPluginId(value);
if (!pluginId) return null;
const status = await httpClient.getPluginSystemStatus();
if (!status.is_enable || !status.is_connected) return 'runtime' as const;
const { plugins } = await httpClient.getPlugins();
const plugin = plugins.find((item) => {
const metadata = item.manifest.manifest.metadata;
return `${metadata.author}/${metadata.name}` === pluginId;
});
if (!plugin) return 'missing' as const;
if (!plugin.enabled) return 'disabled' as const;
return options.some((option) => option.name === value)
? null
: ('missing' as const);
}, [value, options]);
useEffect(() => {
let cancelled = false;
setRunnerProblem(null);
setDiagnosing(true);
void diagnoseRunner()
.then((problem) => {
if (!cancelled) setRunnerProblem(problem);
})
.catch(() => {
if (!cancelled) setRunnerProblem('runtime');
})
.finally(() => {
if (!cancelled) setDiagnosing(false);
});
return () => {
cancelled = true;
};
}, [diagnoseRunner]);
const [pendingInstall, setPendingInstall] = useState(() =>
readPendingRunnerInstall(installScope),
);
@@ -405,6 +444,8 @@ export default function RunnerSelect({
option={selectedOption}
eventProcessor={usage === 'event'}
/>
) : value ? (
<span className="truncate">{value}</span>
) : (
<SelectValue
placeholder={t(
@@ -539,6 +580,24 @@ export default function RunnerSelect({
</a>
</SelectContent>
</Select>
{!diagnosing && runnerProblem && (
<div
role="alert"
className="space-y-1 text-xs text-amber-700 dark:text-amber-400"
>
<p>{t(`agents.runnerDiagnosis.${runnerProblem}`)}</p>
<a
href="/home/plugins"
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1 underline"
>
{t('agents.runnerDiagnosis.repair')}
<ExternalLink className="size-3" />
</a>
<p>{t('agents.runnerDiagnosis.draft')}</p>
</div>
)}
{installError && (
<p role="alert" className="text-sm text-destructive">
@@ -353,6 +353,9 @@ export default function PipelineExtension({
<div className="space-y-6">
{/* Plugins Section */}
<div className="space-y-3">
<p className="text-sm text-muted-foreground">
{t('pipelines.extensions.pluginScopeHint')}
</p>
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground">
{t('pipelines.extensions.pluginsTitle')}
@@ -723,6 +723,9 @@ const PipelineFormComponent = forwardRef<
<Card key={stage.name}>
<CardHeader>
<CardTitle>{extractI18nObject(stage.label)}</CardTitle>
<CardDescription>
{t('pipelines.extensions.runnerScopeHint')}
</CardDescription>
{stage.description && (
<CardDescription>
{extractI18nObject(stage.description)}