fix(core): re-arm plugin command watchers when sources are unchanged

The early return skipped watch(), so a watcher parked on an ancestor stayed
there after an empty plugins directory was created, and a plugin added later
was never discovered.

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
This commit is contained in:
Saoud Rizwan
2026-10-19 00:45:32 +00:00
co-authored by Saoud Rizwan
parent 8e7816a75a
commit a4ebe137a6
2 changed files with 21 additions and 1 deletions
@@ -181,6 +181,23 @@ describe("runtime plugin catalogs", () => {
{ timeout: 3000 },
);
});
it("discovers plugins added after an empty plugins directory appears", async () => {
const workspacePath = await workspace();
await mkdir(join(workspacePath, ".cline"));
const load = vi.fn(async () => loaded());
const manager = new PluginCommandManager({ load });
managers.push(manager);
await manager.list({ workspacePath });
const directory = join(workspacePath, ".cline", "plugins");
await mkdir(directory);
await new Promise((resolve) => setTimeout(resolve, 500));
const calls = load.mock.calls.length;
await writeFile(join(directory, "echo.js"), "export default {};");
await vi.waitFor(
() => expect(load.mock.calls.length).toBeGreaterThan(calls),
{ timeout: 3000 },
);
});
it.each([
"add",
"edit",
@@ -233,8 +233,11 @@ export class PluginCommandManager implements PluginCommandsApi {
sourceKey !== undefined &&
sourceKey === entry.sourceKey &&
entry.failedPaths?.length === 0
)
) {
// A watched root may have just been created; move the watchers into it.
this.watch(entry);
return;
}
entry.sourceKey = sourceKey;
await Promise.all(
entry.loaded.map((loaded) => loaded.shutdown?.().catch(() => {})),