@mastra/core
Version:
288 lines (287 loc) • 9.78 kB
JavaScript
import "./tracing-Bm0k4FBA.js";
import { s as BACKGROUND_TASK_WORKFLOW_ID } from "./background-tasks-6lJjk3_t.js";
import { $t as createWorkflow, un as createStep } from "./agent-Dj30gJa3.js";
import { z } from "zod";
//#region src/background-tasks/workflow.ts
const inputSchema = z.object({ taskId: z.string() });
const attemptOutcomeSchema = z.enum([
"success",
"retry",
"failed",
"cancelled",
"timed_out"
]);
const attemptOutputSchema = z.object({
taskId: z.string(),
outcome: attemptOutcomeSchema,
result: z.unknown().optional(),
error: z.any().optional()
});
const bodyIOSchema = z.object({
taskId: z.string(),
done: z.boolean().optional(),
result: z.unknown().optional()
});
const bodyOutputSchema = z.object({
taskId: z.string(),
done: z.boolean(),
result: z.unknown().optional()
});
const WORKFLOW_STATUS_TO_PERSIST = [
"suspended",
"pending",
"paused",
"waiting"
];
/**
* Builds the per-task workflow that owns executor + retries.
*
* Uses the standard (default) execution engine so the workflow runs entirely
* in-process on whatever host calls `run.start()`. This is critical for
* distributed deployments where the background-task worker must
* execute tools locally — routing through the evented pipeline would send
* step execution to the orchestration worker / API, which don't have the
* internal workflow or task contexts registered.
*
* Shape: outer workflow runs an inner `[run-attempt, classify-outcome]`
* workflow inside a `dountil` loop. `run-attempt` invokes the executor and
* categorises the outcome; `classify-outcome` persists final state, advances
* retry bookkeeping, and decides whether the loop is done. The dountil
* predicate exits on `done === true`.
*
* Step bodies close over `manager` directly — the bg-tasks layer is the only
* consumer of the `@internal` private fields.
*/
function buildBackgroundTaskWorkflow(manager) {
const runAttemptStep = createStep({
id: "run-attempt",
inputSchema: bodyIOSchema,
outputSchema: attemptOutputSchema,
execute: async ({ inputData, abortSignal: workflowAbortSignal, suspend, resumeData }) => {
const { taskId } = inputData;
const storage = await manager.getStorage();
const task = await storage.getTask(taskId);
if (!task || task.status === "cancelled") {
manager.deregisterTaskContext(taskId);
return {
taskId,
outcome: "cancelled"
};
}
const executor = manager.taskContexts.get(taskId)?.executor ?? (task.agentId ? manager.getStaticExecutor(`${task.agentId}:${task.toolName}`) : void 0) ?? manager.getStaticExecutor(task.toolName);
if (!executor) {
const errorInfo = { message: `No executor registered for tool "${task.toolName}". Register the tool on Mastra (so workers can resolve it cross-process) or run the task in the same process as the producer.` };
await storage.updateTask(taskId, {
status: "failed",
error: errorInfo,
completedAt: /* @__PURE__ */ new Date()
});
const failedTask = await storage.getTask(taskId);
if (failedTask) {
await manager.runLocalCompletionHooks(failedTask, "failed", { error: errorInfo });
await manager.publishLifecycleEvent("task.failed", failedTask);
}
manager.deregisterTaskContext(taskId);
throw new Error(errorInfo.message);
}
const progressThrottleMs = manager.config.progressThrottleMs;
const shouldThrottleProgress = typeof progressThrottleMs === "number" && Number.isFinite(progressThrottleMs) && progressThrottleMs > 0;
let lastProgressEmitMs;
const onProgress = async (chunk) => {
if (shouldThrottleProgress) {
const now = Date.now();
if (lastProgressEmitMs !== void 0 && now - lastProgressEmitMs < progressThrottleMs) return;
lastProgressEmitMs = now;
}
await manager.publishLifecycleEvent("task.output", {
...task,
chunk
});
};
const abortController = new AbortController();
manager.activeAbortControllers.set(taskId, abortController);
const onWorkflowAbort = () => abortController.abort(/* @__PURE__ */ new Error("Task cancelled"));
if (workflowAbortSignal.aborted) abortController.abort(/* @__PURE__ */ new Error("Task cancelled"));
else workflowAbortSignal.addEventListener("abort", onWorkflowAbort, { once: true });
const timeoutHandle = setTimeout(() => {
abortController.abort(/* @__PURE__ */ new Error(`Task timed out after ${task.timeoutMs}ms`));
}, task.timeoutMs);
let pendingSuspend;
const wrappedSuspend = async (data, suspendOptions) => {
await storage.updateTask(taskId, {
status: "suspended",
suspendPayload: data,
suspendedAt: /* @__PURE__ */ new Date()
});
const suspendedTask = await storage.getTask(taskId);
if (suspendedTask) {
await manager.runLocalSuspendHooks(suspendedTask);
await manager.publishLifecycleEvent("task.suspended", suspendedTask);
}
pendingSuspend = {
data,
suspendOptions
};
};
try {
const result = await executor.execute(task.args, {
abortSignal: abortController.signal,
onProgress,
suspend: wrappedSuspend,
resumeData
});
if (pendingSuspend) return suspend(pendingSuspend.data, pendingSuspend.suspendOptions);
return {
taskId,
outcome: "success",
result
};
} catch (error) {
const currentTask = await storage.getTask(taskId);
if (!currentTask || currentTask.status === "cancelled") {
manager.deregisterTaskContext(taskId);
return {
taskId,
outcome: "cancelled"
};
}
if (abortController.signal.aborted || error?.name === "AbortError" || error?.message === "Task cancelled" || error?.message?.startsWith("Task timed out after ")) return {
taskId,
outcome: "timed_out"
};
if (error?.name === "FGADeniedError") return {
taskId,
outcome: "failed",
error: {
name: error.name,
message: error?.message ?? "Authorization denied",
stack: error?.stack
}
};
return {
taskId,
outcome: "retry",
error: {
message: error?.message ?? "Unknown error",
stack: error?.stack
}
};
} finally {
clearTimeout(timeoutHandle);
workflowAbortSignal.removeEventListener("abort", onWorkflowAbort);
manager.activeAbortControllers.delete(taskId);
}
}
});
const classifyOutcomeStep = createStep({
id: "classify-outcome",
inputSchema: attemptOutputSchema,
outputSchema: bodyOutputSchema,
execute: async ({ inputData }) => {
const { taskId, outcome, result, error } = inputData;
const storage = await manager.getStorage();
const task = await storage.getTask(taskId);
if (!task) return {
taskId,
done: true
};
if (outcome === "cancelled") {
manager.deregisterTaskContext(taskId);
return {
taskId,
done: true
};
}
if (outcome === "timed_out") {
const status = task.status;
if (status !== "timed_out" && status !== "cancelled") {
await storage.updateTask(taskId, {
status: "timed_out",
error: { message: `Task timed out after ${task.timeoutMs}ms` },
completedAt: /* @__PURE__ */ new Date()
});
const timedOutTask = await storage.getTask(taskId);
if (timedOutTask) await manager.publishLifecycleEvent("task.failed", timedOutTask);
}
return {
taskId,
done: true
};
}
if (outcome === "success") {
if (task.status === "cancelled") {
manager.deregisterTaskContext(taskId);
return {
taskId,
done: true
};
}
await storage.updateTask(taskId, {
status: "completed",
result,
completedAt: /* @__PURE__ */ new Date()
});
const completedTask = await storage.getTask(taskId);
if (completedTask) {
await manager.runLocalCompletionHooks(completedTask, "completed", { result });
await manager.publishLifecycleEvent("task.completed", completedTask);
}
return {
taskId,
done: true,
result
};
}
if (outcome === "retry" && task.retryCount < task.maxRetries) {
await storage.updateTask(taskId, {
retryCount: task.retryCount + 1,
error: void 0,
startedAt: /* @__PURE__ */ new Date()
});
return {
taskId,
done: false
};
}
const errorInfo = error ?? { message: "Unknown error" };
await storage.updateTask(taskId, {
status: "failed",
error: errorInfo,
completedAt: /* @__PURE__ */ new Date()
});
const failedTask = await storage.getTask(taskId);
if (failedTask) {
await manager.runLocalCompletionHooks(failedTask, "failed", { error: errorInfo });
await manager.publishLifecycleEvent("task.failed", failedTask);
}
const thrown = new Error(errorInfo.message);
if (errorInfo.name) thrown.name = errorInfo.name;
if (errorInfo.stack) thrown.stack = errorInfo.stack;
throw thrown;
}
});
const attemptBodyWorkflow = createWorkflow({
id: `${BACKGROUND_TASK_WORKFLOW_ID}__attempt`,
inputSchema: bodyIOSchema,
outputSchema: bodyOutputSchema,
steps: [runAttemptStep, classifyOutcomeStep],
options: {
validateInputs: false,
shouldPersistSnapshot: ({ workflowStatus }) => WORKFLOW_STATUS_TO_PERSIST.includes(workflowStatus),
tracingPolicy: { internal: 1 }
}
}).then(runAttemptStep).then(classifyOutcomeStep).commit();
return createWorkflow({
id: BACKGROUND_TASK_WORKFLOW_ID,
inputSchema,
outputSchema: bodyOutputSchema,
steps: [attemptBodyWorkflow],
options: {
shouldPersistSnapshot: ({ workflowStatus }) => WORKFLOW_STATUS_TO_PERSIST.includes(workflowStatus),
tracingPolicy: { internal: 1 }
}
}).dountil(attemptBodyWorkflow, async ({ inputData }) => inputData?.done === true).commit();
}
//#endregion
export { buildBackgroundTaskWorkflow };
//# sourceMappingURL=workflow-Ch1Z6o3l.js.map