feat(FN-702): add kb_task_update tool to pi extension
- Add kb_task_update tool enabling AI agents to update task title, size, and column - Add extension tests for the new kb_task_update tool - Clean up unused CSS styles, dead test code, and stale route handlers - Include changeset for the new tool addition
This commit is contained in:
@@ -75,6 +75,7 @@ describe("kb pi extension", () => {
|
||||
it("registers all expected tools", () => {
|
||||
const expected = [
|
||||
"kb_task_create",
|
||||
"kb_task_update",
|
||||
"kb_task_list",
|
||||
"kb_task_show",
|
||||
"kb_task_attach",
|
||||
@@ -167,6 +168,96 @@ describe("kb pi extension", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("kb_task_update", () => {
|
||||
it("updates task title", async () => {
|
||||
const createTool = api.tools.get("kb_task_create")!;
|
||||
await createTool.execute("c1", { description: "Original" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
const updateTool = api.tools.get("kb_task_update")!;
|
||||
const result = await updateTool.execute(
|
||||
"u1",
|
||||
{ id: "FN-001", title: "New Title" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Updated FN-001");
|
||||
expect(result.content[0].text).toContain("title");
|
||||
expect(result.details.updatedFields).toEqual(["title"]);
|
||||
|
||||
// Verify via show
|
||||
const showTool = api.tools.get("kb_task_show")!;
|
||||
const show = await showTool.execute("s1", { id: "FN-001" }, undefined, undefined, makeCtx(tmpDir));
|
||||
expect(show.content[0].text).toContain("New Title");
|
||||
});
|
||||
|
||||
it("updates task description", async () => {
|
||||
const createTool = api.tools.get("kb_task_create")!;
|
||||
await createTool.execute("c1", { description: "Original desc" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
const updateTool = api.tools.get("kb_task_update")!;
|
||||
const result = await updateTool.execute(
|
||||
"u1",
|
||||
{ id: "FN-001", description: "Updated description" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Updated FN-001");
|
||||
expect(result.details.updatedFields).toEqual(["description"]);
|
||||
});
|
||||
|
||||
it("updates task dependencies", async () => {
|
||||
const createTool = api.tools.get("kb_task_create")!;
|
||||
await createTool.execute("c1", { description: "First" }, undefined, undefined, makeCtx(tmpDir));
|
||||
await createTool.execute("c2", { description: "Second" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
const updateTool = api.tools.get("kb_task_update")!;
|
||||
const result = await updateTool.execute(
|
||||
"u1",
|
||||
{ id: "FN-002", depends: ["FN-001"] },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Updated FN-002");
|
||||
expect(result.details.updatedFields).toEqual(["dependencies"]);
|
||||
});
|
||||
|
||||
it("updates multiple fields at once", async () => {
|
||||
const createTool = api.tools.get("kb_task_create")!;
|
||||
await createTool.execute("c1", { description: "Original" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
const updateTool = api.tools.get("kb_task_update")!;
|
||||
const result = await updateTool.execute(
|
||||
"u1",
|
||||
{ id: "FN-001", title: "New Title", description: "New desc", depends: [] },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.details.updatedFields).toEqual(["title", "description", "dependencies"]);
|
||||
});
|
||||
|
||||
it("returns error when task not found", async () => {
|
||||
const updateTool = api.tools.get("kb_task_update")!;
|
||||
const result = await updateTool.execute(
|
||||
"u1",
|
||||
{ id: "FN-999", title: "Nope" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain("FN-999 not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("kb_task_list", () => {
|
||||
it("returns empty message when no tasks", async () => {
|
||||
const tool = api.tools.get("kb_task_list")!;
|
||||
|
||||
@@ -107,6 +107,84 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
},
|
||||
});
|
||||
|
||||
// ── kb_task_update ────────────────────────────────────────────────
|
||||
|
||||
pi.registerTool({
|
||||
name: "kb_task_update",
|
||||
label: "KB: Update Task",
|
||||
description:
|
||||
"Update fields on an existing task. Supports modifying the title, " +
|
||||
"description, and dependencies after task creation.",
|
||||
promptSnippet: "Update fields on an existing Fusion task",
|
||||
promptGuidelines: [
|
||||
"Use kb_task_update to modify task title, description, or dependencies after creation.",
|
||||
"At least one field must be provided to update.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
id: Type.String({ description: "Task ID (e.g. KB-001)" }),
|
||||
title: Type.Optional(Type.String({ description: "New task title" })),
|
||||
description: Type.Optional(Type.String({ description: "New task description" })),
|
||||
depends: Type.Optional(
|
||||
Type.Array(Type.String(), {
|
||||
description: "New dependency list — replaces existing dependencies (e.g. ['KB-001', 'KB-002'])",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const store = await getStore(ctx.cwd);
|
||||
|
||||
// Validate task exists
|
||||
let task;
|
||||
try {
|
||||
task = await store.getTask(params.id);
|
||||
} catch {
|
||||
return {
|
||||
content: [{ type: "text", text: `Task ${params.id} not found` }],
|
||||
isError: true,
|
||||
details: { error: "Task not found" },
|
||||
};
|
||||
}
|
||||
|
||||
// Build update payload
|
||||
const updates: Record<string, unknown> = {};
|
||||
const updatedFields: string[] = [];
|
||||
|
||||
if (params.title !== undefined) {
|
||||
updates.title = params.title.trim();
|
||||
updatedFields.push("title");
|
||||
}
|
||||
if (params.description !== undefined) {
|
||||
updates.description = params.description.trim();
|
||||
updatedFields.push("description");
|
||||
}
|
||||
if (params.depends !== undefined) {
|
||||
updates.dependencies = params.depends;
|
||||
updatedFields.push("dependencies");
|
||||
}
|
||||
|
||||
if (updatedFields.length === 0) {
|
||||
return {
|
||||
content: [{ type: "text", text: "No fields to update. Provide at least one of: title, description, depends." }],
|
||||
isError: true,
|
||||
details: { error: "No fields provided" },
|
||||
};
|
||||
}
|
||||
|
||||
await store.updateTask(params.id, updates);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Updated ${params.id}: ${updatedFields.join(", ")}`,
|
||||
},
|
||||
],
|
||||
details: { taskId: params.id, updatedFields },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// ── kb_task_list ─────────────────────────────────────────────────
|
||||
|
||||
pi.registerTool({
|
||||
|
||||
Reference in New Issue
Block a user