Compare commits

...

1 Commits

Author SHA1 Message Date
J
79b874100a fix: apply single skill overwrite immediately
Co-authored-by: multica-agent <github@multica.ai>
2026-06-12 14:31:05 +08:00
2 changed files with 81 additions and 7 deletions

View File

@@ -465,6 +465,66 @@ describe("RuntimeLocalSkillImportPanel", () => {
expect(await screen.findByText("Updated")).toBeInTheDocument();
});
it("applies a single creator conflict when clicking overwrite", async () => {
mockResolveRuntimeLocalSkillImport
.mockResolvedValueOnce({
status: "conflict",
conflict: {
existing_skill_id: "existing-skill-1",
existing_created_by: "user-1",
can_overwrite: true,
},
})
.mockResolvedValueOnce({
status: "updated",
skill: {
...MOCK_IMPORTED_SKILL_A,
id: "existing-skill-1",
},
});
renderPanel();
expect(
await screen.findByText("Review Helper", {}, { timeout: 5000 }),
).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /Review Helper/i }));
const importButton = screen.getByRole("button", {
name: /Import to Workspace/i,
});
await waitFor(() => expect(importButton).not.toBeDisabled(), {
timeout: 5000,
});
fireEvent.click(importButton);
expect(
await screen.findByText(/A skill with this name already exists/i),
).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /^Overwrite$/i }));
await waitFor(
() => {
expect(mockResolveRuntimeLocalSkillImport).toHaveBeenLastCalledWith(
"runtime-1",
{
skill_key: "review-helper",
name: "Review Helper",
description: "Review pull requests",
supports_conflict: true,
action: "overwrite",
target_skill_id: "existing-skill-1",
},
);
},
{ timeout: 5000 },
);
expect(await screen.findByText("Updated")).toBeInTheDocument();
});
it("keeps bulk completion behavior when conflict resolution leaves one success", async () => {
mockRuntimeLocalSkillsOptions.mockReturnValue({
queryKey: ["runtimes", "local-skills", "runtime-1"],

View File

@@ -307,12 +307,14 @@ function ConflictResolutionPanel({
conflicts,
resolutions,
onChange,
onResolveNow,
onOverwriteAll,
onSkipAll,
}: {
conflicts: BulkImportResult[];
resolutions: Record<string, ConflictResolutionState>;
onChange: (key: string, next: ConflictResolutionState) => void;
onResolveNow?: (key: string, next: ConflictResolutionState) => void;
onOverwriteAll: () => void;
onSkipAll: () => void;
}) {
@@ -401,12 +403,17 @@ function ConflictResolutionPanel({
variant={
resolution.action === "overwrite" ? "default" : "outline"
}
onClick={() =>
onChange(r.key, {
onClick={() => {
const next = {
action: "overwrite",
renameName: resolution.renameName,
})
}
} satisfies ConflictResolutionState;
if (single && r.conflict?.can_overwrite && onResolveNow) {
onResolveNow(r.key, next);
} else {
onChange(r.key, next);
}
}}
disabled={!r.conflict?.can_overwrite}
>
<RefreshCw className="h-3 w-3" />
@@ -723,7 +730,9 @@ export function RuntimeLocalSkillImportPanel({
}));
};
const handleApplyConflictResolutions = async () => {
const handleApplyConflictResolutions = async (
resolutionOverrides: Record<string, ConflictResolutionState> = {},
) => {
if (!selectedRuntimeId || pendingConflicts.length === 0) return;
const conflicts = [...pendingConflicts];
@@ -748,7 +757,8 @@ export function RuntimeLocalSkillImportPanel({
}));
for (const r of conflicts) {
const resolution = conflictResolutions[r.key];
const resolution =
resolutionOverrides[r.key] ?? conflictResolutions[r.key];
if (!resolution) {
applyResult(r.key, {
status: "failed",
@@ -893,6 +903,10 @@ export function RuntimeLocalSkillImportPanel({
conflicts={pendingConflicts}
resolutions={conflictResolutions}
onChange={setConflictResolution}
onResolveNow={(key, next) => {
setConflictResolution(key, next);
void handleApplyConflictResolutions({ [key]: next });
}}
onOverwriteAll={() => {
setConflictResolutions((prev) => {
const next = { ...prev };
@@ -1143,7 +1157,7 @@ export function RuntimeLocalSkillImportPanel({
<Button
type="button"
size="sm"
onClick={handleApplyConflictResolutions}
onClick={() => void handleApplyConflictResolutions()}
disabled={!canApplyConflictResolutions}
>
{t(($) => $.runtime_import.conflict_apply_button)}