fix(coding): remediate ML-07 plugin authority

This commit is contained in:
2026-08-27 20:40:58 +08:00
parent 9407c67df2
commit cf13aa7eba
29 changed files with 1008 additions and 214 deletions

View File

@@ -26,7 +26,16 @@ const EMPTY_FILES: string[] = [];
type DrawerMode = 'models' | 'knowledge' | 'skills' | null;
type ProjectIdentityKind = ProjectIdentityChoice['kind'];
type SkillStructureEntry = { path: string; type: 'file' | 'directory' };
type SkillInfo = { id: string; name: string; description: string; location: string; content: string; entries: SkillStructureEntry[] };
type SkillInfo = {
id: string;
name: string;
description: string;
available: boolean;
effective: boolean;
location: string;
content: string;
entries: SkillStructureEntry[];
};
function createCustomAgent(index: number, modelKey: string): CodingProjectAgent {
const now = new Date().toISOString();
@@ -154,6 +163,8 @@ export function ProjectConfiguration() {
.then((result) => setSkills(result.skills.map((skill) => ({
id: skill.id,
...getSkillDisplayInfo(skill.id),
available: skill.available,
effective: skill.effective,
location: skill.location ?? '',
content: skill.content ?? '',
entries: skill.entries ?? [{ path: 'SKILL.md', type: 'file' }],

View File

@@ -11,10 +11,8 @@ import { cn } from '@/lib/utils';
import { codingPluginsStore, useCodingPluginsStore } from '@/stores/coding-plugins';
import { useCodingWorkspaceStore } from '@/stores/coding-workspace';
import type { DataServiceInstanceState } from '../../../shared/data-service';
import { BUNDLED_CODING_PLUGIN_DEFINITIONS, type CodingPluginDefinition } from '../../../shared/coding-plugins';
const PLUGIN_SETTINGS_SURFACES = { 'data-service': DataServicePluginSettings } as const;
const definitions = new Map<string, CodingPluginDefinition>(BUNDLED_CODING_PLUGIN_DEFINITIONS.map((definition) => [definition.id, definition]));
const STATE_TEXT: Record<CodingPluginItem['state'], string> = {
unavailable: '暂不可用', disabled: '未启用', identity_required: '需要项目 ID',
@@ -60,8 +58,6 @@ export function ProjectPluginsView(props: ViewProps) {
const [selectedId, setSelectedId] = useState(props.projection.items[0]?.id ?? null);
const [disableCandidate, setDisableCandidate] = useState<CodingPluginItem | null>(null);
const selected = props.projection.items.find(({ id }) => id === selectedId) ?? props.projection.items[0] ?? null;
const definition = selected ? definitions.get(selected.id) : undefined;
const billing = selected?.capabilities.flatMap(({ operations }) => operations.map(({ billing }) => billing))[0] ?? null;
const SettingsSurface = selected?.settingsSurface && selected.settingsSurface in PLUGIN_SETTINGS_SURFACES
? PLUGIN_SETTINGS_SURFACES[selected.settingsSurface as keyof typeof PLUGIN_SETTINGS_SURFACES]
: null;
@@ -92,19 +88,18 @@ export function ProjectPluginsView(props: ViewProps) {
<section className="mt-6 grid gap-3 sm:grid-cols-2 lg:grid-cols-4" aria-label="插件概览">
<div className="rounded-xl bg-surface-subtle p-3"><p className="text-xs text-muted-foreground">发布者</p><p className="mt-1 font-semibold">MakeLore</p></div>
<div className="rounded-xl bg-surface-subtle p-3"><p className="text-xs text-muted-foreground">包版本</p><p className="mt-1 font-semibold">{selected.version}</p></div>
<div className="rounded-xl bg-surface-subtle p-3"><p className="text-xs text-muted-foreground">合同版本</p><p className="mt-1 font-semibold">v{definition?.contractVersion ?? '—'}</p></div>
<div className="rounded-xl bg-surface-subtle p-3"><p className="text-xs text-muted-foreground">后端</p><p className="mt-1 font-semibold">{definition?.requiresBackend ? '需要' : '不需要'}</p></div>
<div className="rounded-xl bg-surface-subtle p-3"><p className="text-xs text-muted-foreground">合同版本</p><p className="mt-1 font-semibold">v{selected.contractVersion}</p></div>
<div className="rounded-xl bg-surface-subtle p-3"><p className="text-xs text-muted-foreground">后端</p><p className="mt-1 font-semibold">{selected.requiresBackend ? '需要' : '不需要'}</p></div>
</section>
{selected.state === 'identity_required' ? <Button type="button" variant="outline" className="mt-5" onClick={() => navigate('/project-config')}>创建或绑定项目 ID</Button> : null}
{selected.state === 'authentication_required' ? <Button type="button" variant="outline" className="mt-5" onClick={() => navigate('/login', { state: { from: '/project-plugins' } })}>前往登录</Button> : null}
<section className="mt-7" aria-labelledby="plugin-capabilities-heading"><h3 id="plugin-capabilities-heading" className="text-balance text-lg font-semibold">能力与权限</h3>
<div className="mt-3 flex flex-wrap gap-2">{selected.capabilities.map((capability) => <Badge key={capability.id} variant="secondary">{capability.id}</Badge>)}</div>
<div className="mt-3 space-y-3">{definition?.tools.map((tool) => <div key={tool.name} className="rounded-xl bg-surface-subtle p-4"><div className="flex flex-wrap items-center gap-2"><code className="font-mono text-sm font-semibold">{tool.name}</code><Badge variant="outline">{MUTATION_TEXT[tool.mutation]}</Badge></div><p className="mt-2 text-pretty text-sm text-muted-foreground">{tool.description}</p><p className="mt-2 flex items-center gap-2 text-xs"><ShieldCheck className="h-4 w-4 text-brand" />{tool.permissions.map((permission) => PERMISSION_TEXT[permission] ?? permission).join('、')}</p></div>)}</div>
<div className="mt-3 space-y-4">{selected.capabilities.map((capability) => <div key={capability.id} className="rounded-xl border border-border/70 p-4"><h4 className="font-mono text-sm font-semibold">{capability.id}</h4><div className="mt-3 space-y-3">{capability.operations.map((operation) => <div key={operation.id} className="rounded-lg bg-surface-subtle p-3"><div className="flex flex-wrap items-center gap-2"><code className="font-mono text-sm font-semibold">{operation.id}</code>{operation.tool ? <><Badge variant="outline">{MUTATION_TEXT[operation.tool.mutation]}</Badge><code className="text-xs text-muted-foreground">{operation.tool.name}</code></> : <Badge variant="outline">SDK</Badge>}</div>{operation.tool ? <><p className="mt-2 text-pretty text-sm text-muted-foreground">{operation.tool.description}</p><p className="mt-2 flex items-center gap-2 text-xs"><ShieldCheck className="h-4 w-4 text-brand" />{operation.tool.permissions.map((permission) => PERMISSION_TEXT[permission] ?? permission).join('、')}</p></> : <p className="mt-2 text-pretty text-sm text-muted-foreground">由生成的 Data Service SDK 使用,不作为 Pi 工具暴露。</p>}<p className="mt-2 text-pretty text-xs font-medium">{billingText(operation.billing)}</p></div>)}</div></div>)}</div>
</section>
<section className="mt-7" aria-labelledby="plugin-agents-heading"><div className="flex flex-wrap items-center justify-between gap-3"><h3 id="plugin-agents-heading" className="text-balance text-lg font-semibold">伙伴分配</h3><Button type="button" variant="outline" onClick={() => navigate('/project-config')}>管理伙伴技能</Button></div>
<div className="mt-3 flex flex-wrap gap-2">{selected.skills.flatMap(({ assignedAgentIds }) => assignedAgentIds).length ? selected.skills.flatMap(({ assignedAgentIds }) => assignedAgentIds).map((id) => <Badge key={id} variant="secondary">{props.agentNames[id] ?? '未知伙伴'}</Badge>) : <p className="text-pretty text-sm text-muted-foreground">尚未分配给任何伙伴。</p>}</div>
</section>
<section className="mt-7" aria-labelledby="plugin-billing-heading"><h3 id="plugin-billing-heading" className="text-balance text-lg font-semibold">用量与计费</h3>{billing ? <><p className="mt-2 text-pretty text-sm">{billingText(billing)}</p>{billing.mode === 'platform_metered' ? <p className="mt-1 text-pretty text-xs text-muted-foreground">钱包流水由权益所有者查看,当前页面不扩大财务权限。</p> : null}</> : <p className="mt-2 text-pretty text-sm">计费策略暂不可用,相关调用已停用。</p>}</section>
<section className="mt-7" aria-labelledby="plugin-billing-heading"><h3 id="plugin-billing-heading" className="text-balance text-lg font-semibold">用量与计费</h3><p className="mt-2 text-pretty text-sm">各操作的计费模式已在上方能力列表中逐项显示;平台计量操作的流水仍由权益所有者查看。</p></section>
{SettingsSurface && selected.enabled ? <section className="mt-7" aria-labelledby="plugin-settings-heading"><h3 id="plugin-settings-heading" className="mb-3 text-balance text-lg font-semibold">配置</h3><SettingsSurface state={selected.state} projectName={props.projectName} data={props.dataService} pending={props.pending} onConfigure={props.onConfigure} onReset={props.onReset} onRemoveCollection={props.onRemoveCollection} onRemoveProject={props.onRemoveProject} /></section> : null}
</article>
) : <p className="text-sm text-muted-foreground">当前没有可用插件。</p>}