fix(cli): allowed extension tools in allowlists
Deferred --tools validation until extension discovery, then validated against built-in and registered tool names while preserving strict rejection of unknown names. Fixes #8421
This commit is contained in:
@@ -113,6 +113,7 @@ describe("parseArgs — unrecognized flag tracking (#2459)", () => {
|
||||
it("propagates unrecognizedFlags through applyExtensionFlags so callers can surface them", () => {
|
||||
const runner = {
|
||||
getFlags: () => new Map<string, { type: "boolean" | "string" }>([["spawn-peer", { type: "string" }]]),
|
||||
getToolNames: () => [],
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
const parsed = applyExtensionFlags(runner, ["--spawn-peer", "reviewer", "--typo"]);
|
||||
|
||||
@@ -15,6 +15,10 @@ class FakeExtensionFlagSink implements ExtensionFlagSink {
|
||||
]);
|
||||
}
|
||||
|
||||
getToolNames(): readonly string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
setFlagValue(name: string, value: boolean | string): void {
|
||||
this.#values.set(name, value);
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ describe("extension flags vs initial message", () => {
|
||||
const sessionId = "019ea530-ffff-7000-8000-000000000000";
|
||||
const sink: ExtensionFlagSink = {
|
||||
getFlags: () => extFlags,
|
||||
getToolNames: () => [],
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
const parsed = applyExtensionFlags(sink, ["--continue", sessionId]);
|
||||
@@ -123,6 +124,7 @@ describe("extension flags vs initial message", () => {
|
||||
const rawArgs = ["--continue", sessionId, "--spawn-peer", "reviewer", "do next"];
|
||||
const sink: ExtensionFlagSink = {
|
||||
getFlags: () => extFlags,
|
||||
getToolNames: () => [],
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
const parsed = applyExtensionFlags(sink, rawArgs);
|
||||
@@ -147,6 +149,7 @@ describe("extension flags vs initial message", () => {
|
||||
|
||||
const sink: ExtensionFlagSink = {
|
||||
getFlags: () => extFlags,
|
||||
getToolNames: () => [],
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
const extensionArgs = applyExtensionFlags(sink, rawArgs);
|
||||
@@ -212,6 +215,7 @@ describe("applyExtensionFlags (single-parser flag resolution)", () => {
|
||||
const values = new Map<string, boolean | string>();
|
||||
return {
|
||||
values,
|
||||
getToolNames: () => [],
|
||||
getFlags: () => flagMap,
|
||||
setFlagValue: (name, value) => {
|
||||
values.set(name, value);
|
||||
@@ -221,8 +225,8 @@ describe("applyExtensionFlags (single-parser flag resolution)", () => {
|
||||
it("returns null when there is no runner", () => {
|
||||
expect(applyExtensionFlags(undefined, ["--spawn-peer", "x", "task"])).toBeNull();
|
||||
});
|
||||
it("returns null when the runner registered no flags", () => {
|
||||
expect(applyExtensionFlags(fakeRunner({}), ["--whatever", "task"])).toBeNull();
|
||||
it("reparses with an empty extension registry so unknown flags remain visible", () => {
|
||||
expect(applyExtensionFlags(fakeRunner({}), ["--whatever", "task"])?.unrecognizedFlags).toEqual(["--whatever"]);
|
||||
});
|
||||
it("applies and strips a string flag in space form", () => {
|
||||
const runner = fakeRunner({ "spawn-peer": "string" });
|
||||
@@ -330,6 +334,7 @@ describe("registerFlag with built-in-named flags (r3323473227)", () => {
|
||||
);
|
||||
const sink: ExtensionFlagSink = {
|
||||
getFlags: () => ExtensionRunner.aggregateFlags([ext]),
|
||||
getToolNames: () => [],
|
||||
setFlagValue: (name, value) => {
|
||||
runtime.flagValues.set(name, value);
|
||||
},
|
||||
|
||||
@@ -85,19 +85,24 @@ describe("--session-dir", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("--tools legacy aliases", () => {
|
||||
describe("--tools validation", () => {
|
||||
it("maps search and find to grep and glob", () => {
|
||||
const result = parseArgs(["--tools", "search,find,grep"]);
|
||||
|
||||
expect(result.tools).toEqual(["grep", "glob"]);
|
||||
});
|
||||
|
||||
it("rejects unknown tool names instead of silently narrowing the toolset", () => {
|
||||
// Removed tools (ssh, job, irc, launch, search_tool_bm25) used to be
|
||||
// dropped with only a log-file warning, so `--tools bash,ssh` ran with
|
||||
// just bash and no visible notice.
|
||||
expect(() => parseArgs(["--tools", "bash,ssh"])).toThrow(CliUsageError);
|
||||
expect(() => parseArgs(["--tools", "bash,ssh"])).toThrow(/Unknown tool in --tools: ssh/);
|
||||
it("defers unknown-name validation until extension discovery", () => {
|
||||
expect(parseArgs(["--tools", "bash,intercom"]).tools).toEqual(["bash", "intercom"]);
|
||||
});
|
||||
|
||||
it("accepts registered extension tools and still rejects unknown names after discovery", () => {
|
||||
const extensionFlags = new Map<string, { type: "boolean" | "string" }>();
|
||||
expect(parseArgs(["--tools", "read,intercom"], extensionFlags, ["intercom"]).tools).toEqual(["read", "intercom"]);
|
||||
expect(() => parseArgs(["--tools", "bash,ssh"], extensionFlags, ["intercom"])).toThrow(CliUsageError);
|
||||
expect(() => parseArgs(["--tools", "bash,ssh"], extensionFlags, ["intercom"])).toThrow(
|
||||
/Unknown tool in --tools: ssh/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user