@mastra/core
Version:
288 lines (287 loc) • 9.95 kB
JavaScript
require("./tracing-BUrUJwCM.cjs");
const require_background_tasks = require("./background-tasks-lifNqs9M.cjs");
const require_agent = require("./agent-DCD4MApC.cjs");
let zod = require("zod");
//#region src/background-tasks/workflow.ts
const inputSchema = zod.z.object({ taskId: zod.z.string() });
const attemptOutcomeSchema = zod.z.enum([
"success",
"retry",
"failed",
"cancelled",
"timed_out"
]);
const attemptOutputSchema = zod.z.object({
taskId: zod.z.string(),
outcome: attemptOutcomeSchema,
result: zod.z.unknown().optional(),
error: zod.z.any().optional()
});
const bodyIOSchema = zod.z.object({
taskId: zod.z.string(),
done: zod.z.boolean().optional(),
result: zod.z.unknown().optional()
});
const bodyOutputSchema = zod.z.object({
taskId: zod.z.string(),
done: zod.z.boolean(),
result: zod.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 = require_agent.createStep$1({
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 = require_agent.createStep$1({
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 = require_agent.createWorkflow({
id: `${require_background_tasks.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 require_agent.createWorkflow({
id: require_background_tasks.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
exports.buildBackgroundTaskWorkflow = buildBackgroundTaskWorkflow;
//# sourceMappingURL=workflow-BHmP62a4.cjs.map