diff --git a/examples/src/anchor-browser.ts b/examples/src/anchor-browser.ts index e54bc78..e580a28 100644 --- a/examples/src/anchor-browser.ts +++ b/examples/src/anchor-browser.ts @@ -2,7 +2,6 @@ import { createAnthropic } from "@ai-sdk/anthropic"; import { createVercelAIProvider } from "@trymeka/ai-provider-vercel"; import { createAnchorBrowserComputerProvider } from "@trymeka/computer-provider-anchor-browser"; import { createAgent } from "@trymeka/core/ai/agent"; -import { z } from "zod"; /** * This example shows how to use the Anthropic model to run a task. @@ -32,18 +31,19 @@ const agent = createAgent({ const session = await agent.initializeSession(); console.log("session live url", session.get()?.liveUrl); const task = await session.runTask({ - instructions: "Read the top article and summarize them", + instructions: + "Go through the show hacker news and identify what the top 7 posts have in common. Distill down to the most important 3 points and summarize them while using the original text as evidence. Also include the timing for the various posts.", initialUrl: "https://news.ycombinator.com", - outputSchema: z.object({ - articles: z.array( - z.object({ - title: z.string(), - url: z.string(), - summary: z.string(), - author: z.string(), - }), - ), - }), + // outputSchema: z.object({ + // articles: z.array( + // z.object({ + // title: z.string(), + // url: z.string(), + // summary: z.string(), + // author: z.string(), + // }), + // ), + // }), }); console.log("Task", JSON.stringify(task.result, null, 2)); diff --git a/packages/core/src/ai/agent.ts b/packages/core/src/ai/agent.ts index ac077d2..4bd5bf4 100644 --- a/packages/core/src/ai/agent.ts +++ b/packages/core/src/ai/agent.ts @@ -1,6 +1,12 @@ import { z } from "zod"; import type { AIProvider, AgentLog, AgentMessage, Session, Task } from "."; -import { type Tool, createCompleteTaskTool, createWaitTool } from "../tools"; +import { + SessionToDoListStore, + type Tool, + createCompleteTaskTool, + createToDoListTool, + createWaitTool, +} from "../tools"; import { type ComputerProvider, createComputerTool } from "../tools/computer"; import { ComputerProviderError, ToolCallError } from "../tools/errors"; import { SessionMemoryStore, createMemoryTool } from "../tools/memory"; @@ -174,6 +180,7 @@ export function createAgent(options: { // Create persistent memory store for this task const memoryStore = new SessionMemoryStore(); + const todoListStore = new SessionToDoListStore(); const coreTools = { computer_action: createComputerTool({ @@ -199,6 +206,9 @@ export function createAgent(options: { wait: createWaitTool({ computerProvider, }), + todo_list: createToDoListTool({ + toDoListStore: todoListStore, + }), }; // biome-ignore lint/suspicious/noExplicitAny: user defined @@ -257,6 +267,20 @@ export function createAgent(options: { }); } + const taskListContext = todoListStore.getTaskListContext(); + if (taskListContext) { + // Add task list context as the first user message so it's always visible + messages.unshift({ + role: "user", + content: [ + { + type: "text", + text: taskListContext, + }, + ], + }); + } + return messages; } diff --git a/packages/core/src/ai/prompts/system.ts b/packages/core/src/ai/prompts/system.ts index 67c5236..80ad503 100644 --- a/packages/core/src/ai/prompts/system.ts +++ b/packages/core/src/ai/prompts/system.ts @@ -91,6 +91,16 @@ You have access to a persistent memory system that survives beyond the conversat - Use of this tool is essential for any data that is related to the final outcome of the task. - Actions: store (new), update (modify), retrieve (get), delete (remove), list (show keys) +## TASK LIST TOOL + +You have access to a task list system to help you plan and execute your work: +- **task_list**: Create, manage, and track a list of tasks. + - Use this to break down complex goals into smaller, manageable steps. + - Before starting a complex task, create a plan using 'task_list' with the 'add' action. + - As you complete each step, update its status using the 'update' action (e.g., set to 'in-progress' or 'completed'). + - Regularly review the task list with the 'list' action to stay on track. + - Actions: 'add' (new tasks), 'update' (modify status/description), 'list' (show all tasks) + This planning information helps maintain context and progress tracking across steps. You will see your previous planning context in the conversation marked with [PLANNING - Step X]. ## TASK COMPLETION TOOL diff --git a/packages/core/src/tools/index.ts b/packages/core/src/tools/index.ts index 7edd4b4..3715234 100644 --- a/packages/core/src/tools/index.ts +++ b/packages/core/src/tools/index.ts @@ -42,4 +42,10 @@ export { createMemoryTool, type MemoryStore, } from "./memory"; +export { + createToDoListTool, + SessionToDoListStore, + type ToDoListStore, + type ToDo, +} from "./todo-list"; export { ToolCallError, ComputerProviderError } from "./errors"; diff --git a/packages/core/src/tools/todo-list.ts b/packages/core/src/tools/todo-list.ts new file mode 100644 index 0000000..758d917 --- /dev/null +++ b/packages/core/src/tools/todo-list.ts @@ -0,0 +1,247 @@ +import z from "zod"; +import type { Tool } from "."; +import { createAgentLogUpdate } from "../utils/agent-log"; + +const ToDoListToolSchema = z.discriminatedUnion("action", [ + z.object({ + action: z.literal("add"), + tasks: z + .array( + z.object({ + description: z + .string() + .describe("The description of the task to add."), + }), + ) + .describe("An array of tasks to add to the list."), + }), + z.object({ + action: z.literal("update"), + tasks: z + .array( + z.object({ + id: z.string().describe("The ID of the task to update."), + status: z + .enum(["pending", "in-progress", "completed", "cancelled"]) + .describe("The new status of the task."), + description: z + .string() + .optional() + .describe("A new description for the task."), + }), + ) + .describe("An array of tasks to update."), + }), + z.object({ + action: z.literal("list"), + }), +]); + +export interface ToDo { + id: string; + description: string; + status: "pending" | "in-progress" | "completed" | "cancelled"; +} + +export interface ToDoListStore { + get(id: string): ToDo | undefined | Promise; + add(tasks: { description: string }[]): ToDo[] | Promise; + update( + updates: { + id: string; + status?: "pending" | "in-progress" | "completed" | "cancelled"; + description?: string | undefined; + }[], + ): (ToDo | undefined)[] | Promise<(ToDo | undefined)[]>; + list(): ToDo[] | Promise; + clear(): void | Promise; +} + +export class SessionToDoListStore implements ToDoListStore { + private store = new Map(); + private nextId = 1; + + private generateId(): string { + return (this.nextId++).toString(); + } + + add(tasks: { description: string }[]): ToDo[] { + const newTasks: ToDo[] = []; + for (const task of tasks) { + const newTask: ToDo = { + id: this.generateId(), + description: task.description, + status: "pending", + }; + this.store.set(newTask.id, newTask); + newTasks.push(newTask); + } + return newTasks; + } + + update( + updates: { + id: string; + status?: "pending" | "in-progress" | "completed" | "cancelled"; + description?: string; + }[], + ): (ToDo | undefined)[] { + return updates.map((update) => { + const task = this.store.get(update.id); + if (task) { + if (update.status) { + task.status = update.status; + } + if (update.description) { + task.description = update.description; + } + this.store.set(task.id, task); + return task; + } + return undefined; + }); + } + + get(id: string): ToDo | undefined { + return this.store.get(id); + } + + list(): ToDo[] { + return Array.from(this.store.values()); + } + + clear(): void { + this.store.clear(); + } + + // Get all tasks as formatted text for context injection + getTaskListContext(): string { + if (this.store.size === 0) { + return ""; + } + + const tasks = Array.from(this.store.values()); + return `CURRENT TASK LIST:\n${tasks + .map((task) => `[${task.status}] ${task.id}: ${task.description}`) + .join("\n")}\n`; + } +} + +export function createToDoListTool({ + toDoListStore, +}: { + toDoListStore: ToDoListStore; +}): Tool { + return { + description: + "Create, manage, and track a list of tasks to complete the user's request. Use this to break down complex tasks into smaller steps and track your progress.", + schema: ToDoListToolSchema, + execute: async (args, context) => { + try { + switch (args.action) { + case "add": { + const newTasks = await toDoListStore.add(args.tasks); + const response = { + role: "user" as const, + content: [ + { + type: "text" as const, + text: `Successfully added ${ + newTasks.length + } task(s). Here is the updated task list: +${(await toDoListStore.list()) + .map((t) => `[${t.status}] ${t.id}: ${t.description}`) + .join("\n")} +Please proceed with the next step.`, + }, + ], + }; + return { + type: "response", + response, + updateCurrentAgentLog: createAgentLogUpdate({ + toolCallId: context.toolCallId, + toolName: "task_list", + args, + response, + }), + }; + } + case "update": { + await toDoListStore.update(args.tasks); + const response = { + role: "user" as const, + content: [ + { + type: "text" as const, + text: `Successfully updated task(s). Here is the updated task list: +${(await toDoListStore.list()) + .map((t) => `[${t.status}] ${t.id}: ${t.description}`) + .join("\n")} +Please proceed with the next step.`, + }, + ], + }; + return { + type: "response", + response, + updateCurrentAgentLog: createAgentLogUpdate({ + toolCallId: context.toolCallId, + toolName: "task_list", + args, + response, + }), + }; + } + + case "list": { + const tasks = await toDoListStore.list(); + const response = { + role: "user" as const, + content: [ + { + type: "text" as const, + text: + tasks.length > 0 + ? `Current task list:\n${tasks + .map((t) => `[${t.status}] ${t.id}: ${t.description}`) + .join("\n")}\nPlease proceed with the next step.` + : "The task list is empty. Please add tasks to get started.", + }, + ], + }; + return { + type: "response", + response, + updateCurrentAgentLog: createAgentLogUpdate({ + toolCallId: context.toolCallId, + toolName: "task_list", + args, + response, + }), + }; + } + default: + return { + type: "completion", + output: { + // @ts-expect-error - action is never + result: `Unknown action in task list tool: ${args.action}`, + success: false, + }, + }; + } + } catch (error) { + return { + type: "completion", + output: { + result: `Task list operation failed: ${ + error instanceof Error ? error.message : String(error) + }`, + success: false, + }, + }; + } + }, + }; +}