feat(FN-3706): gate research tools behind experimental feature

The merge introduces a research tool surface gating mechanism: shared availability helpers in core and engine, applied to the executor and triage agent so research prompts and tool exposure are gated behind experimental-feature flags, with tests covering the new logic.

Fusion-Task-Id: FN-3706
This commit is contained in:
Fusion
2026-05-07 15:50:20 -07:00
committed by gsxdsm
parent 8bbb7346b8
commit c38b7cd002
11 changed files with 231 additions and 24 deletions

View File

@@ -587,9 +587,9 @@ describe("buildSpecificationPrompt", () => {
});
describe("TRIAGE_SYSTEM_PROMPT", () => {
it("includes bounded research guidance", () => {
expect(TRIAGE_SYSTEM_PROMPT).toContain("fn_research_run");
expect(TRIAGE_SYSTEM_PROMPT).toContain("Keep research bounded");
it("does not include unconditional research guidance", () => {
expect(TRIAGE_SYSTEM_PROMPT).not.toContain("fn_research_run");
expect(TRIAGE_SYSTEM_PROMPT).not.toContain("Keep research bounded");
});
it("requires specs to keep lint, tests, build, and typecheck green even outside initial file scope", () => {
@@ -889,7 +889,7 @@ describe("fast-mode triage", () => {
}
});
it("omits research tools when researchView experimental flag is disabled", async () => {
it("omits research tools and prompt guidance when researchView experimental flag is disabled", async () => {
const task = createTriageTask({ id: "FN-FAST-005", executionMode: "fast" });
const store = createMockStore({
getSettings: vi.fn().mockResolvedValue({
@@ -904,8 +904,10 @@ describe("fast-mode triage", () => {
});
let capturedTools: any[] = [];
let capturedSystemPrompt = "";
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
capturedTools = opts.customTools;
capturedSystemPrompt = opts.systemPrompt;
return {
session: {
state: {},
@@ -924,6 +926,42 @@ describe("fast-mode triage", () => {
expect(capturedTools.some((tool: any) => tool.name === "fn_research_list")).toBe(false);
expect(capturedTools.some((tool: any) => tool.name === "fn_research_get")).toBe(false);
expect(capturedTools.some((tool: any) => tool.name === "fn_research_cancel")).toBe(false);
expect(capturedSystemPrompt).not.toContain("fn_research_run");
});
it("includes research prompt guidance when researchView experimental flag is enabled", async () => {
const task = createTriageTask({ id: "FN-FAST-006", executionMode: "fast" });
const store = createMockStore({
getSettings: vi.fn().mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
experimentalFeatures: { researchView: true },
} as Settings),
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: task.id, attachments: [], comments: [] }),
});
let capturedSystemPrompt = "";
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
capturedSystemPrompt = opts.systemPrompt;
return {
session: {
state: {},
sessionManager: { getLeafId: vi.fn().mockReturnValue(null) },
prompt: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn(),
navigateTree: vi.fn(),
},
};
});
const processor = new TriageProcessor(store, "/tmp/root");
await processor.specifyTask(task);
expect(capturedSystemPrompt).toContain("fn_research_run");
expect(capturedSystemPrompt).toContain("Keep research bounded");
});
});