UNPKG

@mastra/core

Version:
817 lines (807 loc) 34.1 kB
import { r as createTool } from "./tool-qGw4ZhYO.js"; import { z } from "zod/v4"; //#region src/agent/goal/objective.ts /** RequestContext key under which the current objective is surfaced within a turn. */ const GOAL_REQUEST_CONTEXT_KEY = "mastra:goal"; /** State-signal lane id used for the current objective. */ const GOAL_STATE_ID = "goal"; /** `threadState` storage `type` namespace under which the objective is stored. */ const GOAL_STATE_TYPE = "goal"; /** Default max goal evaluations before the goal stops. */ const DEFAULT_GOAL_MAX_RUNS = 50; /** * Score the default goal scorer emits to signal an explicit "waiting for the * user" checkpoint (tri-state decision `waiting`). It is deliberately neither 1 * (complete) nor 0 (continue): the generic completion reducer treats it as "not * passed" (so the loop does not declare the goal done), while the goal step * detects this exact value and stops the auto-loop (`isContinued = false`) so * the user gets a chance to provide input. The record stays `active` — the next * agent turn is still judged. Shared between `scorer.ts` and `goal-step.ts`. */ const GOAL_SCORE_WAITING = .5; /** * Stable id of the built-in goal scorer (see `createGoalScorer`). The goal step * uses this to attribute the `GOAL_SCORE_WAITING` sentinel to the default * scorer only — a custom `goal.scorer` that happens to return `0.5` must not be * misread as an explicit "waiting" checkpoint. */ const GOAL_SCORER_ID = "goal-scorer"; /** * Default goal-judge system prompt. Ported from MastraCode's `JUDGE_SYSTEM_PROMPT` * so the native goal scorer behaves like the original `/goal` judge. A * user-supplied `goal.prompt` (or per-objective `prompt`) overrides this. */ const DEFAULT_GOAL_JUDGE_PROMPT = `You are the goal judge. Your decision directly controls whether the assistant continues working toward the goal. Given a goal and the assistant's latest response, reason about whether the goal's requirements have been satisfied. Compare what the goal asks for against what the assistant has actually produced. Focus on substance, not phrasing. Use "done" when the goal is fully achieved. Use "waiting" when the goal explicitly requires a user checkpoint, user feedback, human verification, human confirmation, or another external event outside the goal-judge loop before the assistant should continue, and the assistant has correctly stopped at that checkpoint. Use "waiting" when the latest user message asks a question or requests clarification and the latest assistant message answers it; let the user acknowledge the answer, ask a follow-up, or otherwise return control before continuing goal work. Use common sense and do not wait if the user explicitly asked the assistant to continue autonomously after answering. Use "continue" when the goal is not done and the assistant should keep working autonomously, including when it asked for input that the goal did not explicitly require. If your previous decision was "waiting" for an explicit user checkpoint, keep choosing "waiting" when the user's latest response asks a question, requests clarification, or otherwise does not satisfy the checkpoint. Do not continue until the required user feedback/confirmation/verification has actually been provided. If the goal says to wait for the goal judge, judge, evaluator, or you to respond, approve, verify, validate, tell the assistant to continue, or otherwise provide the next signal, treat your own decision as that judge response. Verification can be performed by you unless the goal explicitly says it needs human/user verification. Choose "continue" when the assistant should proceed to the next step. Do not choose "waiting" for judge-controlled checkpoints, because that would mean waiting for yourself. Your "reason" field is sent back to the assistant as guidance when the goal is not yet done — be specific about what still needs to be accomplished. When choosing "continue", write the reason as an instruction for what the assistant should do next. When choosing "waiting", explain what specific user checkpoint is still outstanding.`; /** * Apply the precedence rule: ThreadState record value if present, else the * agent's `goal` config default, else a built-in default. A record only persists * the fields a caller explicitly provided, so unset fields fall back here. */ function resolveEffectiveGoalSettings(record, agentDefaults) { return { judgeModelId: record?.judgeModelId ?? agentDefaults?.judgeModelId, maxRuns: record?.maxRuns ?? agentDefaults?.maxRuns ?? 50, prompt: record?.prompt ?? agentDefaults?.prompt ?? DEFAULT_GOAL_JUDGE_PROMPT, maxSteps: agentDefaults?.maxSteps }; } function isThreadStateStore$2(value) { return !!value && typeof value.getState === "function" && typeof value.setState === "function" && typeof value.deleteState === "function"; } /** Resolve the thread-scoped state store from a Mastra instance, if available. */ async function resolveGoalStore(mastra) { const store = await mastra?.getStorage?.()?.getStore("threadState"); return isThreadStateStore$2(store) ? store : void 0; } /** Read the current objective record for a thread from the store. */ async function readObjective(store, threadId) { if (!store || !threadId) return void 0; return store.getState({ threadId, type: GOAL_STATE_TYPE }); } /** * Persist an objective record for a thread, surfacing it on the RequestContext * so the state processor reflects the write in the same step. */ async function writeObjective(store, threadId, record, requestContext) { if (!store || !threadId) return; await store.setState({ threadId, type: GOAL_STATE_TYPE, value: record }); requestContext?.set(GOAL_REQUEST_CONTEXT_KEY, record); } /** Drop the objective for a thread. */ async function clearObjective(store, threadId, requestContext) { if (!store || !threadId) return; await store.deleteState({ threadId, type: GOAL_STATE_TYPE }); requestContext?.set(GOAL_REQUEST_CONTEXT_KEY, void 0); } function isGoalObjectiveRecord(value) { return !!value && typeof value.objective === "string" && typeof value.status === "string"; } /** * Read the within-turn objective a `setObjective` surfaced on the shared * RequestContext this step, if any. Returns `null` when the objective was * explicitly cleared this step, `undefined` when nothing was carried (so the * caller can fall back to the durable store). */ function getObjectiveFromRequestContext(requestContext) { if (!requestContext?.has?.("mastra:goal")) return void 0; const carried = requestContext.get(GOAL_REQUEST_CONTEXT_KEY); if (carried === void 0) return null; return isGoalObjectiveRecord(carried) ? carried : void 0; } //#endregion //#region src/agent/goal/activity-cache.ts const GOAL_OBJECTIVE_CACHE_KEY = "__mastra_goal_objective_cache"; function clearCachedGoalObjective(requestContext) { requestContext?.delete(GOAL_OBJECTIVE_CACHE_KEY); } function cacheGoalObjective(requestContext, threadId, objective) { requestContext?.set(GOAL_OBJECTIVE_CACHE_KEY, { threadId, objective: objective ?? null }); } function takeCachedGoalObjective(requestContext, threadId) { const cached = requestContext?.get(GOAL_OBJECTIVE_CACHE_KEY); clearCachedGoalObjective(requestContext); return cached?.threadId === threadId ? cached : void 0; } //#endregion //#region src/agent/goal/state-processor.ts function renderObjective(record) { return `\n ${record.objective}\n`; } function lp$1(value) { return `${value.length}:${value}`; } function stableObjectiveCacheKey(record, maxRuns) { return `goal:${lp$1(record.objective)}${lp$1(record.status)}${lp$1(String(record.runsUsed))}${lp$1(String(maxRuns))}`; } /** * Input processor that publishes the agent's current objective as a state * signal. Auto-registered when an agent is configured with `goal`, or added * explicitly via {@link GoalSignalProvider}. */ var GoalStateProcessor = class { id = "goal-state"; stateId = GOAL_STATE_ID; mastra; __registerMastra(mastra) { this.mastra = mastra; } async resolveStore() { return resolveGoalStore(this.mastra); } getPriorObjective(args) { return (args.lastSnapshot?.metadata?.value)?.objective; } async computeStateSignal(args) { const carried = getObjectiveFromRequestContext(args.requestContext); const cached = takeCachedGoalObjective(args.requestContext, args.threadId); let current; if (carried === null) current = void 0; else if (carried !== void 0) current = carried; else if (cached) current = cached.objective ?? void 0; else { const store = await this.resolveStore(); current = store ? await store.getState({ threadId: args.threadId, type: GOAL_STATE_TYPE }) : void 0; } const prior = this.getPriorObjective(args); const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot; if (!current || current.status !== "active") { if (!hasBase || !prior) return; return { id: GOAL_STATE_ID, cacheKey: "goal:none", mode: "snapshot", tagName: "current-objective", contents: "\n", value: { objective: void 0 }, attributes: { status: "none" }, metadata: { value: { objective: void 0 } } }; } const maxRuns = current.maxRuns ?? prior?.maxRuns ?? 0; const cacheKey = stableObjectiveCacheKey(current, maxRuns); const priorCacheKey = prior ? stableObjectiveCacheKey(prior, prior.maxRuns ?? 0) : void 0; if (hasBase && priorCacheKey === cacheKey) return; return { id: GOAL_STATE_ID, cacheKey, mode: "snapshot", tagName: "current-objective", contents: renderObjective(current), value: { objective: current }, attributes: { status: current.status, runsUsed: current.runsUsed, ...maxRuns ? { maxRuns } : {} }, metadata: { value: { objective: current } } }; } }; //#endregion //#region src/tools/builtin/task-tools.ts /** RequestContext key under which the current working task list is surfaced within a turn. */ const TASKS_REQUEST_CONTEXT_KEY = "mastra:tasks"; /** State-signal lane id used for the task list. */ const TASKS_STATE_ID = "tasks"; /** `threadState` storage `type` namespace under which the task list is stored. */ const TASK_STATE_TYPE = "task"; const NO_MEMORY_MESSAGE = "Task tools require agent memory (a memory-backed thread). No task was recorded. Configure the agent with Memory to use the task list."; const taskIdSchema = z.string().min(1).describe("Stable task identifier (for example, 'task_investigate_tests'). Keep this unchanged across updates."); const taskItemInputSchema = z.object({ id: taskIdSchema.optional(), content: z.string().min(1).describe("Task description in imperative form (e.g., 'Fix authentication bug')"), status: z.enum([ "pending", "in_progress", "completed" ]).describe("Current task status"), activeForm: z.string().min(1).describe("Present continuous form shown during execution (e.g., 'Fixing authentication bug')") }); const taskItemSchema = taskItemInputSchema.extend({ id: taskIdSchema }); const taskToolResultSchema = z.object({ content: z.string(), tasks: z.array(taskItemSchema), isError: z.boolean() }); const taskCheckSummarySchema = z.object({ total: z.number().int().nonnegative(), completed: z.number().int().nonnegative(), inProgress: z.number().int().nonnegative(), pending: z.number().int().nonnegative(), incomplete: z.number().int().nonnegative(), hasTasks: z.boolean(), allCompleted: z.boolean() }); const taskCheckResultSchema = taskToolResultSchema.extend({ summary: taskCheckSummarySchema, incompleteTasks: z.array(taskItemSchema) }); const TASK_ID_SLUG_MAX_LENGTH = 48; function slugifyTaskContent(content) { let slug = ""; let pendingSeparator = false; for (const char of content.toLowerCase()) { const code = char.charCodeAt(0); if (code >= 97 && code <= 122 || code >= 48 && code <= 57) { if (pendingSeparator && slug.length > 0 && slug.length < TASK_ID_SLUG_MAX_LENGTH) slug += "_"; if (slug.length >= TASK_ID_SLUG_MAX_LENGTH) break; slug += char; pendingSeparator = false; continue; } pendingSeparator = slug.length > 0; } return slug; } function createDeterministicTaskId(task, occurrence) { const slug = slugifyTaskContent(task.content); const suffix = occurrence > 1 ? `_${occurrence}` : ""; return `task_${slug || "item"}${suffix}`; } function makeUniqueTaskId(id, usedIds, reservedIds = /* @__PURE__ */ new Set()) { if (!usedIds.has(id) && !reservedIds.has(id)) return id; let suffix = 2; let nextId = `${id}_${suffix}`; while (usedIds.has(nextId) || reservedIds.has(nextId)) { suffix += 1; nextId = `${id}_${suffix}`; } return nextId; } function assignTaskIds(tasks, previousTasks = []) { const usedIds = /* @__PURE__ */ new Set(); const contentOccurrences = /* @__PURE__ */ new Map(); const omittedContentCounts = /* @__PURE__ */ new Map(); const explicitTaskIds = new Set(tasks.map((task) => task.id).filter((id) => Boolean(id))); const reusablePreviousIds = /* @__PURE__ */ new Map(); for (const task of tasks) if (!task.id) omittedContentCounts.set(task.content, (omittedContentCounts.get(task.content) ?? 0) + 1); tasks.forEach((task, index) => { if (task.id || omittedContentCounts.get(task.content) !== 1) return; const previousMatches = previousTasks.filter((previous) => previous.content === task.content && !explicitTaskIds.has(previous.id)); if (previousMatches.length === 1) reusablePreviousIds.set(index, previousMatches[0].id); }); const reservedIds = /* @__PURE__ */ new Set([...explicitTaskIds, ...reusablePreviousIds.values()]); return tasks.map((task, index) => { const contentOccurrence = (contentOccurrences.get(task.content) ?? 0) + 1; contentOccurrences.set(task.content, contentOccurrence); const fallbackId = createDeterministicTaskId(task, contentOccurrence); const reusablePreviousId = reusablePreviousIds.get(index); const id = (task.id && !usedIds.has(task.id) ? task.id : void 0) ?? (reusablePreviousId && !usedIds.has(reusablePreviousId) ? reusablePreviousId : makeUniqueTaskId(fallbackId, usedIds, reservedIds)); usedIds.add(id); return { id, content: task.content, status: task.status, activeForm: task.activeForm }; }); } function formatTaskListResult(tasks) { const completed = tasks.filter((t) => t.status === "completed").length; const inProgress = tasks.find((t) => t.status === "in_progress"); let summary = `Tasks updated: [${completed}/${tasks.length} completed]`; if (inProgress) summary += `\nCurrently: ${inProgress.activeForm} (${inProgress.id})`; if (tasks.length > 0) summary += `\nTask IDs:\n${tasks.map((t) => `- ${t.id}: ${t.content} (${t.status})`).join("\n")}`; return summary; } function summarizeTaskCheck(tasks) { const completedTasks = tasks.filter((task) => task.status === "completed"); const inProgressTasks = tasks.filter((task) => task.status === "in_progress"); const pendingTasks = tasks.filter((task) => task.status === "pending"); const incompleteTasks = [...inProgressTasks, ...pendingTasks]; return { summary: { total: tasks.length, completed: completedTasks.length, inProgress: inProgressTasks.length, pending: pendingTasks.length, incomplete: incompleteTasks.length, hasTasks: tasks.length > 0, allCompleted: tasks.length > 0 && incompleteTasks.length === 0 }, inProgressTasks, pendingTasks, incompleteTasks }; } function formatTaskCheckResult(taskCheck) { const { summary, inProgressTasks, pendingTasks } = taskCheck; if (!summary.hasTasks) return "No tasks found. Consider using task_write to create a task list for complex work."; let response = `Task Status: [${summary.completed}/${summary.total} completed]\n`; response += `- Completed: ${summary.completed}\n`; response += `- In Progress: ${summary.inProgress}\n`; response += `- Pending: ${summary.pending}\n`; response += `\nAll tasks completed: ${summary.allCompleted ? "YES" : "NO"}`; if (!summary.allCompleted) { response += "\n\nIncomplete tasks:"; if (inProgressTasks.length > 0) { response += "\n\nIn Progress:"; inProgressTasks.forEach((t) => { response += `\n- ${t.id}: ${t.content}`; }); } if (pendingTasks.length > 0) { response += "\n\nPending:"; pendingTasks.forEach((t) => { response += `\n- ${t.id}: ${t.content}`; }); } response += "\n\nContinue working on these tasks before ending."; } return response; } function hasMultipleInProgress(tasks) { return tasks.filter((task) => task.status === "in_progress").length > 1; } function multipleInProgressError(tasks) { return { content: "Only one task can be in_progress at a time.", tasks, isError: true }; } function demoteExtraInProgress(tasks, preferredIndex) { const inProgressIndices = tasks.reduce((acc, t, i) => { if (t.status === "in_progress") acc.push(i); return acc; }, []); if (inProgressIndices.length <= 1) return tasks; const keepIndex = preferredIndex !== void 0 && inProgressIndices.includes(preferredIndex) ? preferredIndex : inProgressIndices[inProgressIndices.length - 1]; return tasks.map((t, i) => t.status === "in_progress" && i !== keepIndex ? { ...t, status: "pending" } : t); } function formatAvailableTaskIds(tasks) { if (tasks.length === 0) return "No tasks are currently tracked."; return `Available task IDs:\n${tasks.map((t) => `- ${t.id}: ${t.content} (${t.status})`).join("\n")}`; } /** True when the run is memory-backed (state signals + the task store require a thread + resource). */ function isMemoryBacked(agent) { return Boolean(agent?.threadId && agent?.resourceId); } function isThreadStateStore$1(value) { return !!value && typeof value.getState === "function" && typeof value.setState === "function"; } /** Resolve the thread-scoped state store from the agent's Mastra storage, if available. */ async function resolveTaskStore(context) { const store = await context.mastra?.getStorage?.()?.getStore("threadState"); return isThreadStateStore$1(store) ? store : void 0; } function emitTaskDisplayUpdate(requestContext, tasks) { (requestContext?.get("controller"))?.emitEvent?.({ type: "task_updated", tasks }); } function noMemoryResult() { return { content: NO_MEMORY_MESSAGE, tasks: [], isError: true }; } function noMemoryCheckResult() { const emptyCheck = summarizeTaskCheck([]); return { content: NO_MEMORY_MESSAGE, tasks: [], summary: emptyCheck.summary, incompleteTasks: emptyCheck.incompleteTasks, isError: true }; } /** Read the current task list for the thread from the store. */ async function readTaskStore(context) { const store = await resolveTaskStore(context); const threadId = context.agent?.threadId; if (!store || !threadId) return []; const tasks = await store.getState({ threadId, type: TASK_STATE_TYPE }); return Array.isArray(tasks) ? tasks : []; } /** * Apply a mutation to the current task list: read from the store, mutate, * persist back to the store, surface the list to the state processor via * `RequestContext`, and emit the AgentController display update. */ async function applyTaskMutation(context, mutation) { const store = await resolveTaskStore(context); const threadId = context.agent?.threadId; if (!store || !threadId) return noMemoryResult(); const result = mutation(await readTaskStore(context)); if (!result.isError) { await store.setState({ threadId, type: TASK_STATE_TYPE, value: result.tasks }); context.requestContext?.set(TASKS_REQUEST_CONTEXT_KEY, result.tasks); emitTaskDisplayUpdate(context.requestContext, result.tasks); } return result; } /** * Built-in, agent-agnostic tool: manage a structured task list for the run. * Full-replacement semantics: each call replaces the entire task list. * Prefer task_update or task_complete for changing existing tasks by ID. */ const taskWriteTool = createTool({ id: "task_write", description: `Create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. Usage: - Use this to create the initial task list or replace the whole list after replanning - Pass the FULL task list each time this tool is called (replaces the previous list) - Each task has: id (stable identifier), content (imperative), status (pending, in_progress, or completed), activeForm (present continuous) - IDs must be unique. If duplicate explicit IDs are provided, the duplicate task is returned with a generated fallback ID - Keep task IDs stable across updates. If omitted, IDs are generated and returned in the tool result - When an ID is omitted while rewriting an existing list, one unambiguous matching task may reuse an existing ID - Prefer single-task update tools when they are available - Mark tasks in_progress BEFORE starting work (only ONE at a time) - Mark tasks completed IMMEDIATELY after finishing - Use this for multi-step tasks requiring 3+ distinct actions States: - pending: Not yet started - in_progress: Currently working on (limit to ONE) - completed: Finished successfully`, inputSchema: z.object({ tasks: z.array(taskItemInputSchema).describe("The complete updated task list") }), outputSchema: taskToolResultSchema, execute: async ({ tasks }, context) => { try { if (!isMemoryBacked(context?.agent)) return noMemoryResult(); return applyTaskMutation(context, (currentTasks) => { const normalizedTasks = assignTaskIds(tasks, currentTasks); if (hasMultipleInProgress(normalizedTasks)) return multipleInProgressError(currentTasks); return { content: formatTaskListResult(normalizedTasks), tasks: normalizedTasks, isError: false }; }); } catch (error) { return { content: `Failed to update tasks: ${error instanceof Error ? error.message : "Unknown error"}`, tasks: [], isError: true }; } } }); /** * Built-in, agent-agnostic tool: update one tracked task by stable ID. */ const taskUpdateTool = createTool({ id: "task_update", description: `Update one task in the current task list by stable ID. Use this for targeted changes to one existing task. Usage: - Provide the task ID returned by the task-list tools - Include only the fields that changed - Use status to move a task between pending, in_progress, and completed - Use task_complete when only marking a task completed - If the ID is unknown, the tool returns an error with available task IDs`, inputSchema: z.object({ id: taskIdSchema, content: z.string().min(1).optional().describe("New task description in imperative form"), status: z.enum([ "pending", "in_progress", "completed" ]).optional().describe("New task status"), activeForm: z.string().min(1).optional().describe("New present continuous form shown during execution") }).refine((input) => input.content !== void 0 || input.status !== void 0 || input.activeForm !== void 0, { message: "Provide at least one field to update." }), outputSchema: taskToolResultSchema, execute: async ({ id, content, status, activeForm }, context) => { try { if (!isMemoryBacked(context?.agent)) return noMemoryResult(); return applyTaskMutation(context, (tasks) => { const taskIndex = tasks.findIndex((task) => task.id === id); if (taskIndex === -1) return { content: `Task not found: ${id}\n\n${formatAvailableTaskIds(tasks)}`, tasks, isError: true }; const updatedTasks = demoteExtraInProgress(tasks.map((task, index) => index === taskIndex ? { ...task, ...content !== void 0 ? { content } : {}, ...status !== void 0 ? { status } : {}, ...activeForm !== void 0 ? { activeForm } : {} } : task), taskIndex); return { content: formatTaskListResult(updatedTasks), tasks: updatedTasks, isError: false }; }); } catch (error) { return { content: `Failed to update task: ${error instanceof Error ? error.message : "Unknown error"}`, tasks: [], isError: true }; } } }); /** * Built-in, agent-agnostic tool: mark one tracked task completed by stable ID. */ const taskCompleteTool = createTool({ id: "task_complete", description: `Mark one task completed by stable ID. Use this when one tracked task is finished. Usage: - Provide the task ID returned by the task-list tools - If the ID is unknown, the tool returns an error with available task IDs`, inputSchema: z.object({ id: taskIdSchema }), outputSchema: taskToolResultSchema, execute: async ({ id }, context) => { try { if (!isMemoryBacked(context?.agent)) return noMemoryResult(); return applyTaskMutation(context, (tasks) => { const taskIndex = tasks.findIndex((task) => task.id === id); if (taskIndex === -1) return { content: `Task not found: ${id}\n\n${formatAvailableTaskIds(tasks)}`, tasks, isError: true }; const updatedTasks = tasks.map((task, index) => index === taskIndex ? { ...task, status: "completed" } : task); return { content: formatTaskListResult(updatedTasks), tasks: updatedTasks, isError: false }; }); } catch (error) { return { content: `Failed to complete task: ${error instanceof Error ? error.message : "Unknown error"}`, tasks: [], isError: true }; } } }); /** * Built-in, agent-agnostic tool: check the completion status of the task list. * Helps the agent determine if all tasks are completed before ending work. */ const taskCheckTool = createTool({ id: "task_check", description: `Check the completion status of your current task list. Use this before finishing tracked work to ensure all tasks are completed. Returns: - Human-readable content summary with task counts and incomplete task IDs - Structured task list snapshot with stable IDs - summary object with total, completed, inProgress, pending, incomplete, hasTasks, and allCompleted - incompleteTasks array for tasks that still need work summary.allCompleted is true only when at least one tracked task exists and every tracked task is completed. If no tasks exist, summary.hasTasks is false and summary.allCompleted is false.`, inputSchema: z.object({}), outputSchema: taskCheckResultSchema, execute: async ({}, context) => { try { if (!isMemoryBacked(context?.agent)) return noMemoryCheckResult(); const tasks = await readTaskStore(context); const taskCheck = summarizeTaskCheck(tasks); return { content: formatTaskCheckResult(taskCheck), tasks, summary: taskCheck.summary, incompleteTasks: taskCheck.incompleteTasks, isError: false }; } catch (error) { const msg = error instanceof Error ? error.message : "Unknown error"; const emptyCheck = summarizeTaskCheck([]); return { content: `Failed to check tasks: ${msg}`, tasks: [], summary: emptyCheck.summary, incompleteTasks: emptyCheck.incompleteTasks, isError: true }; } } }); function isTaskItemArray(value) { return Array.isArray(value) && value.every((item) => item && typeof item === "object" && typeof item.id === "string" && typeof item.content === "string" && typeof item.status === "string" && typeof item.activeForm === "string"); } /** * Read the within-turn task list carried on the `RequestContext` by the task * tools (used by the task state processor to build the snapshot for the current * step). Returns `undefined` when no task tool ran this turn, so the processor * can fall back to the durable task store. */ function getTasksFromRequestContext(requestContext) { const carried = requestContext?.get(TASKS_REQUEST_CONTEXT_KEY); return isTaskItemArray(carried) ? carried : void 0; } //#endregion //#region src/tools/builtin/task-state-processor.ts function isThreadStateStore(value) { return !!value && typeof value.getState === "function"; } function renderTaskList(tasks) { if (tasks.length === 0) return ""; return `\n${tasks.map((task) => { return ` ${task.status === "completed" ? "✓" : task.status === "in_progress" ? "▸" : "○"} [${task.status}] {id: ${task.id}} ${task.content}`; }).join("\n")}\n`; } const DELTA_SNAPSHOT_CAP = 10; function getTasksFromSnapshot(snapshot) { const tasks = (snapshot?.metadata?.value)?.tasks; if (Array.isArray(tasks)) return tasks; return []; } function getOpsFromDelta(signal) { const ops = (signal?.metadata?.delta)?.ops; return Array.isArray(ops) ? ops : []; } function applyOps(tasks, ops) { const next = tasks.slice(); for (const op of ops) { if (op.op === "remove") { const idx = next.findIndex((t) => t.id === op.id); if (idx >= 0) next.splice(idx, 1); continue; } const task = op.task; const idx = next.findIndex((t) => t.id === task.id); if (idx >= 0) next[idx] = task; else next.push(task); } return next; } function effectivePriorTasks(args) { let tasks = getTasksFromSnapshot(args.lastSnapshot); for (const delta of args.deltasSinceSnapshot ?? []) tasks = applyOps(tasks, getOpsFromDelta(delta)); return tasks; } function lp(value) { return `${value.length}:${value}`; } function taskFingerprint(t) { return `${lp(t.id)}${lp(t.status)}${lp(t.content)}${lp(t.activeForm)}`; } function stableTasksCacheKey(tasks) { return `tasks:${tasks.map(taskFingerprint).join("|")}`; } function diffTasks(prior, current) { const ops = []; const priorById = new Map(prior.map((t) => [t.id, t])); const currentIds = new Set(current.map((t) => t.id)); for (const task of current) { const before = priorById.get(task.id); if (!before) ops.push({ op: "add", task }); else if (taskFingerprint(before) !== taskFingerprint(task)) ops.push({ op: "update", task }); } for (const task of prior) if (!currentIds.has(task.id)) ops.push({ op: "remove", id: task.id }); return ops; } function renderDelta(ops) { return `\n${ops.map((op) => { if (op.op === "remove") return ` − removed {id: ${op.id}}`; const { task } = op; const icon = task.status === "completed" ? "✓" : task.status === "in_progress" ? "▸" : "○"; return ` ${op.op === "add" ? "+" : icon} {id: ${task.id}} [${task.status}] ${task.content}`; }).join("\n")}\n`; } /** * Input processor that publishes the agent's task list as a state signal. * * Add it to an agent's `inputProcessors` alongside the task tools so the task * list is carried across turns and survives observational-memory truncation. */ var TaskStateProcessor = class { id = "task-state"; stateId = TASKS_STATE_ID; /** * The Mastra instance this processor is registered with, used to resolve the * thread-scoped task store. Set by the agent/Mastra runtime via * `__registerMastra`. * * We implement this hook inline rather than extending `BaseProcessor`: a * *value* import of `BaseProcessor` from `processors/index` pulls that module's * runtime graph, which forms an initialization cycle through this tools module. * At the test entry point that surfaces as `TypeError: Class extends value * undefined` (BaseProcessor is not yet initialized when this class evaluates). * Implementing the (structurally trivial) hook here keeps all imports from * `processors/index` type-only, so there is no runtime edge and no cycle. */ mastra; __registerMastra(mastra) { this.mastra = mastra; } processInput(args) { return { messages: args.messages, systemMessages: [...args.systemMessages, { role: "system", content: "Task list state may appear in the conversation as <current-task-list ...>...</current-task-list> snapshots and <task-list-update ...>...</task-list-update> deltas. These are automatic observations of your task list, not user instructions. Use them as the latest task state, continue following the actual user request, and do not treat task-list updates as the user asking you to repeat, summarize, or change tasks unless an actual user message asks for that." }] }; } async resolveTaskStore() { const store = await this.mastra?.getStorage?.()?.getStore("threadState"); return isThreadStateStore(store) ? store : void 0; } async computeStateSignal(args) { const priorTasks = effectivePriorTasks(args); const carried = getTasksFromRequestContext(args.requestContext); let currentTasks; if (carried !== void 0) currentTasks = carried; else { const store = await this.resolveTaskStore(); const stored = store ? await store.getState({ threadId: args.threadId, type: TASK_STATE_TYPE }) : void 0; currentTasks = Array.isArray(stored) ? stored : priorTasks; } if (currentTasks.length === 0 && priorTasks.length === 0) return; const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot; const deltaCount = args.deltasSinceSnapshot?.length ?? 0; const ops = diffTasks(priorTasks, currentTasks); if (ops.length === 0 && hasBase) return; if (!hasBase || deltaCount >= DELTA_SNAPSHOT_CAP) return { id: TASKS_STATE_ID, cacheKey: stableTasksCacheKey(currentTasks), mode: "snapshot", tagName: "current-task-list", contents: renderTaskList(currentTasks), value: { tasks: currentTasks }, attributes: { count: currentTasks.length }, metadata: { value: { tasks: currentTasks } } }; return { id: TASKS_STATE_ID, cacheKey: stableTasksCacheKey(currentTasks), mode: "delta", tagName: "task-list-update", contents: renderDelta(ops), value: { tasks: currentTasks }, delta: { ops }, attributes: { changes: ops.length }, metadata: { value: { tasks: currentTasks }, delta: { ops } } }; } }; //#endregion export { GOAL_STATE_TYPE as C, resolveEffectiveGoalSettings as D, readObjective as E, resolveGoalStore as O, GOAL_STATE_ID as S, getObjectiveFromRequestContext as T, clearCachedGoalObjective as _, assignTaskIds as a, GOAL_SCORER_ID as b, getTasksFromRequestContext as c, taskCheckTool as d, taskCompleteTool as f, cacheGoalObjective as g, GoalStateProcessor as h, TASK_STATE_TYPE as i, writeObjective as k, hasMultipleInProgress as l, taskWriteTool as m, TASKS_REQUEST_CONTEXT_KEY as n, demoteExtraInProgress as o, taskUpdateTool as p, TASKS_STATE_ID as r, formatTaskListResult as s, TaskStateProcessor as t, summarizeTaskCheck as u, DEFAULT_GOAL_JUDGE_PROMPT as v, clearObjective as w, GOAL_SCORE_WAITING as x, DEFAULT_GOAL_MAX_RUNS as y }; //# sourceMappingURL=task-state-processor-C9agcUfw.js.map