diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index e60f6d8bc..e08f5056d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -29,6 +29,7 @@ - Fixed hashline payload parsing to silently treat truly-blank lines as empty `~`-prefixed payload lines when more payload follows in the same run. The previous behavior broke at the blank ("payload line has no preceding +, <, or = operation.") even though the intent is obvious — the only ambiguity is between in-payload blanks and end-of-section blanks, and a one-line lookahead resolves it: blanks that precede a non-payload op still end the run cleanly as section separators. Recovers the common case of forgetting the leading separator on a blank inserted line without changing how trailing blanks between ops behave. - Rewrote the hashline edit prompt examples to use an ASCII-only `TITLE = "Mr"` → `"Mrs"` / `"Dr"` motif instead of the previous `" • "` and `"·"` separators. Some agents had been copying the middle-dot literal characters into real edits as if they were format scaffolding (e.g. emitting payload lines like `~ ·`), since the demo inserts were near-twins of the existing string. The new example keeps every original op shape (single-line replace, multiline replace, insert AFTER/BEFORE, append, delete, blank, plus both anti-patterns) but uses content that is obviously domain-specific and clearly distinct from any payload separator. Pure prompt change; no parser, schema, or runtime behavior is affected. +- Fixed `discoverAgents()` ignoring `disabledProviders` for the `claude-plugins` provider. Plugin roots from `~/.claude/plugins/` were scanned unconditionally, so agents from Claude Code marketplace plugins continued to appear in `/agents` and the Agent Control Center even when `disabledProviders: [claude-plugins]` was set. The discovery path now checks `isProviderEnabled("claude-plugins")` before calling `listClaudePluginRoots()`, matching how every other capability respects the disabled-providers set. ([#1075](https://github.com/can1357/oh-my-pi/issues/1075)) ## [15.0.1] - 2026-05-14 ### Breaking Changes diff --git a/packages/coding-agent/src/task/discovery.ts b/packages/coding-agent/src/task/discovery.ts index adb2bf1d9..b78d04bea 100644 --- a/packages/coding-agent/src/task/discovery.ts +++ b/packages/coding-agent/src/task/discovery.ts @@ -15,6 +15,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; +import { isProviderEnabled } from "../capability"; import { findAllNearestProjectConfigDirs, getConfigDirs } from "../config"; import { listClaudePluginRoots } from "../discovery/helpers"; import { loadBundledAgents, parseAgent } from "./agents"; @@ -87,8 +88,10 @@ export async function discoverAgents(cwd: string, home: string = os.homedir()): if (user) orderedDirs.push({ dir: user.path, source: "user" }); } - // Load agents from Claude Code marketplace plugins - const { roots: pluginRoots } = await listClaudePluginRoots(home, resolvedCwd); + // Load agents from Claude Code marketplace plugins (respects disabledProviders) + const { roots: pluginRoots } = isProviderEnabled("claude-plugins") + ? await listClaudePluginRoots(home, resolvedCwd) + : { roots: [] }; const sortedPluginRoots = [...pluginRoots].sort((a, b) => { if (a.scope === b.scope) return 0; return a.scope === "project" ? -1 : 1; diff --git a/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts b/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts new file mode 100644 index 000000000..cdca0d86a --- /dev/null +++ b/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts @@ -0,0 +1,80 @@ +/** + * Regression test for #1075: + * discoverAgents() must skip Claude plugin roots when claude-plugins is disabled. + */ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { disableProvider, enableProvider } from "../../src/capability"; +import { clearCache as clearFsCache } from "../../src/capability/fs"; +import { clearClaudePluginRootsCache } from "../../src/discovery/helpers"; +import { discoverAgents } from "../../src/task/discovery"; + +const PLUGIN_AGENT_MD = [ + "---", + "name: simplifier", + "description: A code simplifier agent from a Claude plugin", + "---", + "Simplify code.", +].join("\n"); + +describe("discoverAgents — claude-plugins disabled provider", () => { + let tempHome: string; + + beforeEach(() => { + tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-disco-home-")); + + // Build a fake Claude plugin install with an agents/ subdirectory. + const pluginInstallPath = path.join(tempHome, "plugin-cache", "code-simplifier"); + const agentsDir = path.join(pluginInstallPath, "agents"); + fs.mkdirSync(agentsDir, { recursive: true }); + fs.writeFileSync(path.join(agentsDir, "simplifier.md"), PLUGIN_AGENT_MD); + + // Register the plugin in the Claude registry so listClaudePluginRoots picks it up. + const claudePluginsDir = path.join(tempHome, ".claude", "plugins"); + fs.mkdirSync(claudePluginsDir, { recursive: true }); + fs.writeFileSync( + path.join(claudePluginsDir, "installed_plugins.json"), + JSON.stringify({ + version: 2, + plugins: { + "code-simplifier@claude-plugins-official": [ + { + installPath: pluginInstallPath, + version: "1.0.0", + scope: "user", + installedAt: "2025-01-01T00:00:00Z", + lastUpdated: "2025-01-01T00:00:00Z", + }, + ], + }, + }), + ); + + // Start each test with a clean provider + cache state. + enableProvider("claude-plugins"); + clearFsCache(); + clearClaudePluginRootsCache(); + }); + + afterEach(() => { + fs.rmSync(tempHome, { recursive: true, force: true }); + // Restore global state so other tests in the suite are not affected. + enableProvider("claude-plugins"); + clearFsCache(); + clearClaudePluginRootsCache(); + }); + + test("includes plugin agents when claude-plugins is enabled", async () => { + const { agents } = await discoverAgents(tempHome, tempHome); + expect(agents.map(a => a.name)).toContain("simplifier"); + }); + + test("excludes plugin agents when claude-plugins is disabled", async () => { + disableProvider("claude-plugins"); + clearClaudePluginRootsCache(); + const { agents } = await discoverAgents(tempHome, tempHome); + expect(agents.map(a => a.name)).not.toContain("simplifier"); + }); +});