src / agents / tools / tasks.ts
src / agents / tools / tasks.ts
import { tool, type Tool } from "@lmstudio/sdk";
import { z } from "zod";
import { AgenticError } from "../../core/errors";
import { errorResult, okResult } from "../../core/result";
import type {
NewTaskItem,
TaskItemStatus,
TaskPriority,
} from "../../tasks/taskStore";
import type { AgentToolContext } from "./context";
/** `task_board` — the durable To-Do board linked to the run. */
export function createTaskBoardTool(ctx: AgentToolContext): Tool {
const { state, beforeTool } = ctx;
return tool({
name: "task_board",
description:
"Inspect or update the durable To-Do board linked to this run. Keep status and evidence current so progress survives context compression.",
parameters: {
action: z.enum(["show", "next", "add", "update", "note", "checkpoint"]),
items: z
.array(
z.object({
text: z.string(),
status: z
.enum(["pending", "in_progress", "completed", "blocked", "canceled"])
.optional(),
priority: z.enum(["low", "normal", "high", "critical"]).optional(),
details: z.string().optional(),
depends_on: z.array(z.string()).optional(),
}),
)
.optional(),
item_id: z.string().optional(),
text: z.string().optional(),
status: z
.enum(["pending", "in_progress", "completed", "blocked", "canceled"])
.optional(),
priority: z.enum(["low", "normal", "high", "critical"]).optional(),
details: z.string().optional(),
depends_on: z.array(z.string()).optional(),
evidence: z.array(z.string()).optional(),
note: z.string().optional(),
decision: z.string().optional(),
summary: z.string().optional(),
decisions: z.array(z.string()).optional(),
blockers: z.array(z.string()).optional(),
next: z.array(z.string()).optional(),
},
implementation: async (input: {
action: "show" | "next" | "add" | "update" | "note" | "checkpoint";
items?: Array<{
text: string;
status?: TaskItemStatus;
priority?: TaskPriority;
details?: string;
depends_on?: string[];
}>;
item_id?: string;
text?: string;
status?: TaskItemStatus;
priority?: TaskPriority;
details?: string;
depends_on?: string[];
evidence?: string[];
note?: string;
decision?: string;
summary?: string;
decisions?: string[];
blockers?: string[];
next?: string[];
}) => {
await beforeTool("task_board");
if (!state.taskBoardId) {
return errorResult(
"tasks.agent",
new AgenticError("NOT_FOUND", "This run is not linked to a task board."),
);
}
try {
let board;
switch (input.action) {
case "show":
case "next":
board = await ctx.tasks.load(state.taskBoardId);
break;
case "add": {
const items: NewTaskItem[] = (input.items ?? []).map((item) => ({
text: item.text,
status: item.status,
priority: item.priority,
details: item.details,
dependsOn: item.depends_on,
}));
board = await ctx.tasks.addItems(state.taskBoardId, items);
break;
}
case "update":
if (!input.item_id) {
throw new AgenticError("INVALID_INPUT", "task_board update requires item_id.");
}
board = await ctx.tasks.updateItem(state.taskBoardId, input.item_id, {
text: input.text,
status: input.status,
priority: input.priority,
details: input.details,
dependsOn: input.depends_on,
evidence: input.evidence,
});
break;
case "note":
board = await ctx.tasks.addNote(state.taskBoardId, {
note: input.note,
decision: input.decision,
});
break;
case "checkpoint":
if (!input.summary) {
throw new AgenticError(
"INVALID_INPUT",
"task_board checkpoint requires summary.",
);
}
board = await ctx.tasks.checkpoint(state.taskBoardId, {
summary: input.summary,
decisions: input.decisions,
blockers: input.blockers,
next: input.next,
});
break;
}
const next = ctx.tasks.nextActionable(board);
return okResult({
operation: `tasks.agent.${input.action}`,
summary: `${board.id}: ${next.length} actionable item(s) remain.`,
data: {
id: board.id,
title: board.title,
status: board.status,
items: input.action === "show" ? board.items : undefined,
nextActionable: next.slice(0, 20),
statePath: ctx.tasks.stateRelativePath(board.id),
},
importance: "critical",
facts: [
`task board ${board.id} ${board.status}`,
...next.slice(0, 15).map((item) => `${item.id} ${item.status}: ${item.text}`),
],
omit: ["data.items"],
});
} catch (error) {
return errorResult("tasks.agent", error, "high");
}
},
});
}
import { tool, type Tool } from "@lmstudio/sdk";
import { z } from "zod";
import { AgenticError } from "../../core/errors";
import { errorResult, okResult } from "../../core/result";
import type {
NewTaskItem,
TaskItemStatus,
TaskPriority,
} from "../../tasks/taskStore";
import type { AgentToolContext } from "./context";
/** `task_board` — the durable To-Do board linked to the run. */
export function createTaskBoardTool(ctx: AgentToolContext): Tool {
const { state, beforeTool } = ctx;
return tool({
name: "task_board",
description:
"Inspect or update the durable To-Do board linked to this run. Keep status and evidence current so progress survives context compression.",
parameters: {
action: z.enum(["show", "next", "add", "update", "note", "checkpoint"]),
items: z
.array(
z.object({
text: z.string(),
status: z
.enum(["pending", "in_progress", "completed", "blocked", "canceled"])
.optional(),
priority: z.enum(["low", "normal", "high", "critical"]).optional(),
details: z.string().optional(),
depends_on: z.array(z.string()).optional(),
}),
)
.optional(),
item_id: z.string().optional(),
text: z.string().optional(),
status: z
.enum(["pending", "in_progress", "completed", "blocked", "canceled"])
.optional(),
priority: z.enum(["low", "normal", "high", "critical"]).optional(),
details: z.string().optional(),
depends_on: z.array(z.string()).optional(),
evidence: z.array(z.string()).optional(),
note: z.string().optional(),
decision: z.string().optional(),
summary: z.string().optional(),
decisions: z.array(z.string()).optional(),
blockers: z.array(z.string()).optional(),
next: z.array(z.string()).optional(),
},
implementation: async (input: {
action: "show" | "next" | "add" | "update" | "note" | "checkpoint";
items?: Array<{
text: string;
status?: TaskItemStatus;
priority?: TaskPriority;
details?: string;
depends_on?: string[];
}>;
item_id?: string;
text?: string;
status?: TaskItemStatus;
priority?: TaskPriority;
details?: string;
depends_on?: string[];
evidence?: string[];
note?: string;
decision?: string;
summary?: string;
decisions?: string[];
blockers?: string[];
next?: string[];
}) => {
await beforeTool("task_board");
if (!state.taskBoardId) {
return errorResult(
"tasks.agent",
new AgenticError("NOT_FOUND", "This run is not linked to a task board."),
);
}
try {
let board;
switch (input.action) {
case "show":
case "next":
board = await ctx.tasks.load(state.taskBoardId);
break;
case "add": {
const items: NewTaskItem[] = (input.items ?? []).map((item) => ({
text: item.text,
status: item.status,
priority: item.priority,
details: item.details,
dependsOn: item.depends_on,
}));
board = await ctx.tasks.addItems(state.taskBoardId, items);
break;
}
case "update":
if (!input.item_id) {
throw new AgenticError("INVALID_INPUT", "task_board update requires item_id.");
}
board = await ctx.tasks.updateItem(state.taskBoardId, input.item_id, {
text: input.text,
status: input.status,
priority: input.priority,
details: input.details,
dependsOn: input.depends_on,
evidence: input.evidence,
});
break;
case "note":
board = await ctx.tasks.addNote(state.taskBoardId, {
note: input.note,
decision: input.decision,
});
break;
case "checkpoint":
if (!input.summary) {
throw new AgenticError(
"INVALID_INPUT",
"task_board checkpoint requires summary.",
);
}
board = await ctx.tasks.checkpoint(state.taskBoardId, {
summary: input.summary,
decisions: input.decisions,
blockers: input.blockers,
next: input.next,
});
break;
}
const next = ctx.tasks.nextActionable(board);
return okResult({
operation: `tasks.agent.${input.action}`,
summary: `${board.id}: ${next.length} actionable item(s) remain.`,
data: {
id: board.id,
title: board.title,
status: board.status,
items: input.action === "show" ? board.items : undefined,
nextActionable: next.slice(0, 20),
statePath: ctx.tasks.stateRelativePath(board.id),
},
importance: "critical",
facts: [
`task board ${board.id} ${board.status}`,
...next.slice(0, 15).map((item) => `${item.id} ${item.status}: ${item.text}`),
],
omit: ["data.items"],
});
} catch (error) {
return errorResult("tasks.agent", error, "high");
}
},
});
}