Merge PR #7344: fix(agent): stop leaking scout into prompts when it is disabled (@szavadsky)
# Conflicts: # packages/coding-agent/src/prompts/system/system-prompt.md
This commit is contained in:
@@ -46,6 +46,20 @@ describe("composeSpawnAdvisory", () => {
|
||||
expect(advisory).toContain("Coordinate:");
|
||||
});
|
||||
|
||||
it("drops the scout example from the specialization tip when scout is unavailable", () => {
|
||||
const advisory = composeSpawnAdvisory({
|
||||
agents: ["task", "task"],
|
||||
items: [worker(), worker()],
|
||||
depthCapacity: true,
|
||||
ircEnabled: true,
|
||||
willRunAsync: true,
|
||||
scoutAvailable: false,
|
||||
});
|
||||
expect(advisory).toContain("generic");
|
||||
expect(advisory).not.toContain("scout");
|
||||
expect(advisory).toContain("Coordinate:");
|
||||
});
|
||||
|
||||
it("drops the coordination suggestion on the sync path but keeps the specialization tip", () => {
|
||||
const advisory = composeSpawnAdvisory({
|
||||
agents: ["task", "task"],
|
||||
|
||||
@@ -78,7 +78,23 @@ describe("task tool advisory gating via suppressSpawnAdvisory", () => {
|
||||
} as unknown as ToolSession;
|
||||
}
|
||||
|
||||
async function spawnText(suppress: boolean): Promise<string> {
|
||||
function sessionWithScoutDisabled(): ToolSession {
|
||||
return {
|
||||
cwd: "/tmp",
|
||||
hasUI: false,
|
||||
// `task.disabledAgents` is what the task tool reads to drop scout from
|
||||
// the rendered description and the appended specialization advisory.
|
||||
settings: Settings.isolated({
|
||||
"task.isolation.mode": "none",
|
||||
"task.batch": true,
|
||||
"task.disabledAgents": ["scout"],
|
||||
}),
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => "*",
|
||||
} as unknown as ToolSession;
|
||||
}
|
||||
|
||||
async function spawnTextFor(s: ToolSession): Promise<string> {
|
||||
vi.spyOn(discoveryModule, "discoverAgents").mockResolvedValue({ agents: [agent], projectAgentsDir: null });
|
||||
vi.spyOn(executorModule, "runSubprocess").mockImplementation(
|
||||
async (options): Promise<SingleResult> => ({
|
||||
@@ -97,9 +113,7 @@ describe("task tool advisory gating via suppressSpawnAdvisory", () => {
|
||||
requests: 1,
|
||||
}),
|
||||
);
|
||||
const tool = await TaskTool.create(session(suppress));
|
||||
// Both items omit `agent`, so each resolves to the generic spawn-policy
|
||||
// default ("task") — the ≥2-generics condition the advisory gates on.
|
||||
const tool = await TaskTool.create(s);
|
||||
const result = await tool.execute("tc", {
|
||||
context: "shared fan-out background",
|
||||
tasks: [
|
||||
@@ -111,10 +125,14 @@ describe("task tool advisory gating via suppressSpawnAdvisory", () => {
|
||||
}
|
||||
|
||||
it("appends the specialization advisory when a batch resolves two generic workers", async () => {
|
||||
expect(await spawnText(false)).toContain('`agent: "scout"`');
|
||||
expect(await spawnTextFor(session(false))).toContain('`agent: "scout"`');
|
||||
});
|
||||
|
||||
it("drops the scout example when scout is disabled", async () => {
|
||||
expect(await spawnTextFor(sessionWithScoutDisabled())).not.toContain("scout");
|
||||
});
|
||||
|
||||
it("omits the advisory entirely when the session suppresses it", async () => {
|
||||
expect(await spawnText(true)).not.toContain('`agent: "scout"`');
|
||||
expect(await spawnTextFor(session(true))).not.toContain('`agent: "scout"`');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "bun:test";
|
||||
import { Settings } from "../../src/config/settings";
|
||||
import * as taskDiscovery from "../../src/task/discovery";
|
||||
import { TaskTool } from "../../src/task/index";
|
||||
import { isScoutSpawnable } from "../../src/task/spawn-policy";
|
||||
import type { AgentDefinition } from "../../src/task/types";
|
||||
import { getTaskSchema } from "../../src/task/types";
|
||||
import type { ToolSession } from "../../src/tools";
|
||||
@@ -60,3 +61,72 @@ describe("task spawn policy surfaces", () => {
|
||||
expect(description).not.toContain("### oracle");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isScoutSpawnable", () => {
|
||||
it("is true with no disabled agents and unrestricted spawns", () => {
|
||||
expect(isScoutSpawnable(undefined, "*")).toBe(true);
|
||||
expect(isScoutSpawnable([], "*")).toBe(true);
|
||||
});
|
||||
|
||||
it("is false when scout is disabled via task.disabledAgents", () => {
|
||||
expect(isScoutSpawnable(["scout"], "*")).toBe(false);
|
||||
expect(isScoutSpawnable(["scout", "reviewer"], "*")).toBe(false);
|
||||
});
|
||||
|
||||
it("is false when spawning is disabled", () => {
|
||||
expect(isScoutSpawnable(undefined, false)).toBe(false);
|
||||
expect(isScoutSpawnable(undefined, "")).toBe(false);
|
||||
});
|
||||
|
||||
it("is false when scout is not in the allowed spawn list", () => {
|
||||
expect(isScoutSpawnable(undefined, "reviewer")).toBe(false);
|
||||
});
|
||||
|
||||
it("is true when scout is in the allowed spawn list", () => {
|
||||
expect(isScoutSpawnable(undefined, "scout,reviewer")).toBe(true);
|
||||
expect(isScoutSpawnable(["reviewer"], "scout")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("task tool description scout gating", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
async function renderDescription(disabledScout: boolean): Promise<string> {
|
||||
vi.spyOn(taskDiscovery, "discoverAgents").mockResolvedValue({
|
||||
agents: [
|
||||
{ name: "scout", description: "Read-only scout.", systemPrompt: "Scout.", source: "bundled" },
|
||||
{ name: "reviewer", description: "Reviewer.", systemPrompt: "Review.", source: "bundled" },
|
||||
],
|
||||
projectAgentsDir: null,
|
||||
});
|
||||
const settings = Settings.isolated({
|
||||
"async.enabled": false,
|
||||
"task.batch": true,
|
||||
"task.isolation.mode": "none",
|
||||
...(disabledScout ? { "task.disabledAgents": ["scout"] } : {}),
|
||||
});
|
||||
const tool = await TaskTool.create({
|
||||
cwd: process.cwd(),
|
||||
hasUI: false,
|
||||
settings,
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => "*",
|
||||
} as unknown as ToolSession);
|
||||
return tool.description;
|
||||
}
|
||||
|
||||
it("mentions scout in the task description when scout is enabled", async () => {
|
||||
expect(await renderDescription(false)).toContain("scout");
|
||||
});
|
||||
|
||||
it("omits every scout reference from the task description when scout is disabled", async () => {
|
||||
const description = await renderDescription(true);
|
||||
expect(description).not.toContain("scout");
|
||||
// The read-only agent remains listed as an available agent (the spawn
|
||||
// policy only filters disabledAgents, so reviewer stays); only the
|
||||
// hard-coded scout guidance is dropped.
|
||||
expect(description).toContain("### reviewer");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user