FN-6107: always summarize untitled agent-created tasks
Ensure agent-created untitled tasks still request AI-generated titles even when project auto-summarization is disabled. - force summarize=true for agent-created tasks that do not provide an explicit title - add engine tests covering untitled, titled, and title-generation behavior with autoSummarizeTitles disabled - document the agent-task summarization behavior in settings docs - add a patch changeset for the published CLI package Files changed: .changeset/agent-task-title-summaries.md | 5 ++ docs/settings-reference.md | 2 +- packages/engine/src/__tests__/agent-tools.test.ts | 63 +++++++++++++++++++++++ packages/engine/src/agent-tools.ts | 1 + 4 files changed, 70 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-6107 Fusion-Task-Lineage: d0c25981-a5cb-48ca-b835-9267817b4edd
This commit is contained in:
5
.changeset/agent-task-title-summaries.md
Normal file
5
.changeset/agent-task-title-summaries.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Agent-created tasks without explicit titles now request AI title summarization regardless of the project auto-summarize setting.
|
||||
@@ -472,7 +472,7 @@ Default notes:
|
||||
| `memoryBackupRetention` | `number` | `14` | Number of memory backups to retain. |
|
||||
| `memoryBackupDir` | `string` | `".fusion/backups/memory"` | Relative memory backup directory path. |
|
||||
| `memoryBackupScope` | `"project" \| "agents" \| "all"` | `"all"` | Backup scope: project memory, agent memory, or both. |
|
||||
| `autoSummarizeTitles` | `boolean` | `false` | Auto-generate titles for long untitled descriptions across dashboard/API task creation and agent/tool-created tasks. |
|
||||
| `autoSummarizeTitles` | `boolean` | `false` | Auto-generate titles for long untitled descriptions across dashboard/API task creation. Agent-created tasks from `fn_task_create` and `fn_delegate_task` always request summarization for untitled tasks, regardless of this setting. |
|
||||
| `useAiMergeCommitSummary` | `boolean` | `true` | Use AI-generated merge commit summaries (subject + bullet body + diff-stat) instead of raw step-commit subject lists. |
|
||||
| `titleSummarizerProvider` | `string` | `undefined` | Provider for title summarization. |
|
||||
| `titleSummarizerModelId` | `string` | `undefined` | Model ID for title summarization. |
|
||||
|
||||
@@ -144,6 +144,7 @@ describe("createTaskCreateTool", () => {
|
||||
dependencies: ["PROJ-001"],
|
||||
column: "triage",
|
||||
priority: undefined,
|
||||
summarize: true,
|
||||
source: undefined,
|
||||
}, {
|
||||
settings: { autoSummarizeTitles: false },
|
||||
@@ -167,6 +168,7 @@ describe("createTaskCreateTool", () => {
|
||||
|
||||
expect(store.createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
priority: "high",
|
||||
summarize: true,
|
||||
}), expect.objectContaining({ settings: { autoSummarizeTitles: false } }));
|
||||
});
|
||||
|
||||
@@ -220,6 +222,7 @@ describe("createTaskCreateTool", () => {
|
||||
await tool.execute("call-1", { description: "Test" } as any, undefined, undefined, {} as any);
|
||||
|
||||
expect(store.createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
summarize: true,
|
||||
source: {
|
||||
sourceType: "agent_heartbeat",
|
||||
sourceAgentId: "agent-123",
|
||||
@@ -245,6 +248,7 @@ describe("createTaskCreateTool", () => {
|
||||
await tool.execute("call-1", { description: "spawn follow-up" } as any, undefined, undefined, {} as any);
|
||||
|
||||
expect(store.createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
summarize: true,
|
||||
source: {
|
||||
sourceType: "agent_heartbeat",
|
||||
sourceAgentId: "agent-123",
|
||||
@@ -297,6 +301,65 @@ describe("createTaskCreateTool", () => {
|
||||
expect(second.wasDuplicate).toBe(true);
|
||||
expect(store.createTask).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("passes summarize: true when no title provided", async () => {
|
||||
const created = { id: "FN-201", description: "untitled follow-up", dependencies: [], column: "triage" };
|
||||
const store = {
|
||||
getSettings: vi.fn().mockResolvedValue({ autoSummarizeTitles: false }),
|
||||
createTask: vi.fn().mockResolvedValue(created),
|
||||
};
|
||||
|
||||
await createAgentTask(store as any, { description: "untitled follow-up" } as any);
|
||||
|
||||
expect(store.createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
description: "untitled follow-up",
|
||||
summarize: true,
|
||||
}), expect.objectContaining({ settings: { autoSummarizeTitles: false } }));
|
||||
});
|
||||
|
||||
it("does not set summarize when title is provided", async () => {
|
||||
const created = { id: "FN-202", title: "Explicit title", description: "titled follow-up", dependencies: [], column: "triage" };
|
||||
const store = {
|
||||
getSettings: vi.fn().mockResolvedValue({ autoSummarizeTitles: false }),
|
||||
createTask: vi.fn().mockResolvedValue(created),
|
||||
};
|
||||
|
||||
await createAgentTask(store as any, { title: "Explicit title", description: "titled follow-up" } as any);
|
||||
|
||||
expect(store.createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
title: "Explicit title",
|
||||
summarize: undefined,
|
||||
}), expect.objectContaining({ settings: { autoSummarizeTitles: false } }));
|
||||
});
|
||||
|
||||
it("summarize triggers title generation even when autoSummarizeTitles is false", async () => {
|
||||
const summarizeSpy = vi.spyOn(core, "summarizeTitle").mockResolvedValue("Generated title");
|
||||
const created = { id: "FN-203", description: "untitled follow-up", dependencies: [], column: "triage" };
|
||||
const store = {
|
||||
getSettings: vi.fn().mockResolvedValue({
|
||||
autoSummarizeTitles: false,
|
||||
titleSummarizerProvider: "openai",
|
||||
titleSummarizerModelId: "gpt-4o-mini",
|
||||
}),
|
||||
createTask: vi.fn().mockResolvedValue(created),
|
||||
};
|
||||
|
||||
await createAgentTask(store as any, { description: "untitled follow-up" } as any, { rootDir: "/repo" });
|
||||
|
||||
const createInput = vi.mocked(store.createTask).mock.calls[0]?.[0];
|
||||
const createOptions = vi.mocked(store.createTask).mock.calls[0]?.[1] as { onSummarize?: (description: string) => Promise<string | null> };
|
||||
expect(createInput).toEqual(expect.objectContaining({
|
||||
description: "untitled follow-up",
|
||||
summarize: true,
|
||||
}));
|
||||
expect(createOptions).toEqual(expect.objectContaining({
|
||||
settings: { autoSummarizeTitles: false },
|
||||
}));
|
||||
expect(createOptions.onSummarize).toBeTypeOf("function");
|
||||
await expect(createOptions.onSummarize?.("Long agent-created task description")).resolves.toBe("Generated title");
|
||||
expect(summarizeSpy).toHaveBeenCalledWith("Long agent-created task description", "/repo", "openai", "gpt-4o-mini");
|
||||
summarizeSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createDelegateTaskTool", () => {
|
||||
|
||||
@@ -779,6 +779,7 @@ export async function createAgentTask(
|
||||
input.githubTracking?.enabled !== false && resolvedTracking.enabled;
|
||||
const createInput: TaskCreateInput = {
|
||||
...input,
|
||||
summarize: !input.title?.trim() ? true : undefined,
|
||||
source: nextSource,
|
||||
githubTracking: shouldPrefillGithubTrackingEnabled
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user