From 499341cdc1fb6cfb84878e40858d785e43abe15d Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Wed, 7 Jan 2026 02:04:46 -0800 Subject: [PATCH] feat(coding-agent): allow explicit -e flags with --no-extensions --no-extensions now skips discovery but still loads extensions specified via -e flags. This gives users three modes: 1. Default: auto-discover + any -e additions 2. --no-extensions: no extensions at all 3. --no-extensions -e foo.js: only load foo.js, skip discovery fixes #524 --- packages/coding-agent/src/main.ts | 17 +++++++++-- packages/coding-agent/test/args.test.ts | 6 ++++ .../test/extensions-discovery.test.ts | 30 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index d6f2ae9b..33e9d938 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -18,7 +18,12 @@ import type { AgentSession } from "./core/agent-session.js"; import { createEventBus } from "./core/event-bus.js"; import { exportFromFile } from "./core/export-html/index.js"; -import { discoverAndLoadExtensions, type ExtensionUIContext, type LoadedExtension } from "./core/extensions/index.js"; +import { + discoverAndLoadExtensions, + type ExtensionUIContext, + type LoadedExtension, + loadExtensions, +} from "./core/extensions/index.js"; import type { ModelRegistry } from "./core/model-registry.js"; import { resolveModelScope, type ScopedModel } from "./core/model-resolver.js"; import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk.js"; @@ -328,7 +333,15 @@ export async function main(args: string[]) { time("SettingsManager.create"); let loadedExtensions: LoadedExtension[] = []; - if (!firstPass.noExtensions) { + if (firstPass.noExtensions) { + // --no-extensions disables discovery, but explicit -e flags still work + const explicitPaths = firstPass.extensions ?? []; + if (explicitPaths.length > 0) { + const result = await loadExtensions(explicitPaths, cwd, eventBus); + loadedExtensions = result.extensions; + time("loadExtensions"); + } + } else { // Merge CLI --extension args with settings.json extensions const extensionPaths = [...settingsManager.getExtensionPaths(), ...(firstPass.extensions ?? [])]; const result = await discoverAndLoadExtensions(extensionPaths, cwd, agentDir, eventBus); diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts index 2b3a8af6..bb9e7862 100644 --- a/packages/coding-agent/test/args.test.ts +++ b/packages/coding-agent/test/args.test.ts @@ -155,6 +155,12 @@ describe("parseArgs", () => { const result = parseArgs(["--no-extensions"]); expect(result.noExtensions).toBe(true); }); + + test("parses --no-extensions with explicit -e flags", () => { + const result = parseArgs(["--no-extensions", "-e", "foo.ts", "-e", "bar.ts"]); + expect(result.noExtensions).toBe(true); + expect(result.extensions).toEqual(["foo.ts", "bar.ts"]); + }); }); describe("--no-skills flag", () => { diff --git a/packages/coding-agent/test/extensions-discovery.test.ts b/packages/coding-agent/test/extensions-discovery.test.ts index fa2edf88..2db8e7bb 100644 --- a/packages/coding-agent/test/extensions-discovery.test.ts +++ b/packages/coding-agent/test/extensions-discovery.test.ts @@ -443,4 +443,34 @@ describe("extensions discovery", () => { expect(result.extensions).toHaveLength(1); expect(result.extensions[0].flags.has("--my-flag")).toBe(true); }); + + it("loadExtensions only loads explicit paths without discovery", async () => { + // Create discoverable extensions (would be found by discoverAndLoadExtensions) + fs.writeFileSync(path.join(extensionsDir, "discovered.ts"), extensionCodeWithTool("discovered")); + + // Create explicit extension outside discovery path + const explicitPath = path.join(tempDir, "explicit.ts"); + fs.writeFileSync(explicitPath, extensionCodeWithTool("explicit")); + + // Use loadExtensions directly to skip discovery + const { loadExtensions } = await import("../src/core/extensions/loader.js"); + const result = await loadExtensions([explicitPath], tempDir); + + expect(result.errors).toHaveLength(0); + expect(result.extensions).toHaveLength(1); + expect(result.extensions[0].tools.has("explicit")).toBe(true); + expect(result.extensions[0].tools.has("discovered")).toBe(false); + }); + + it("loadExtensions with no paths loads nothing", async () => { + // Create discoverable extensions (would be found by discoverAndLoadExtensions) + fs.writeFileSync(path.join(extensionsDir, "discovered.ts"), extensionCode); + + // Use loadExtensions directly with empty paths + const { loadExtensions } = await import("../src/core/extensions/loader.js"); + const result = await loadExtensions([], tempDir); + + expect(result.errors).toHaveLength(0); + expect(result.extensions).toHaveLength(0); + }); });