@mastra/core
Version:
518 lines (516 loc) • 14.8 kB
JavaScript
import { MastraWorker, PullTransport } from './chunk-2JTOKKYG.js';
import { RequestContext } from './chunk-W4DLMY73.js';
// src/schedules/worker.ts
var TOPIC_AGENT_SCHEDULES = "agent-schedules";
var DEFAULT_GROUP = "mastra-agent-schedules";
var AgentScheduleWorker = class extends MastraWorker {
name = "agent-schedule";
#config;
#transport;
#pushCb;
#running = false;
constructor(config = {}) {
super();
this.#config = config;
}
async init(deps) {
await super.init(deps);
if (!deps.mastra) {
throw new Error("AgentScheduleWorker requires Mastra instance");
}
}
async start() {
if (this.#running) return;
if (!this.deps) throw new Error("AgentScheduleWorker: call init() before start()");
const modes = this.deps.pubsub.supportedModes ?? ["pull"];
if (!modes.includes("pull")) {
const cb = (event, ack, nack) => {
void this.#handleEvent(event, ack, nack);
};
this.#pushCb = cb;
await this.deps.pubsub.subscribe(TOPIC_AGENT_SCHEDULES, cb);
this.#running = true;
return;
}
const group = this.#config.group ?? DEFAULT_GROUP;
this.#transport = new PullTransport({
pubsub: this.deps.pubsub,
group,
topic: TOPIC_AGENT_SCHEDULES,
logger: this.deps.logger
});
await this.#transport.start({
route: (event, ack, nack) => this.#handleEvent(event, ack, nack)
});
this.#running = true;
}
async stop() {
if (!this.#running) return;
try {
if (this.#transport) {
await this.#transport.stop();
this.#transport = void 0;
}
if (this.#pushCb && this.deps) {
await this.deps.pubsub.unsubscribe(TOPIC_AGENT_SCHEDULES, this.#pushCb);
this.#pushCb = void 0;
}
} finally {
this.#running = false;
}
}
get isRunning() {
return this.#running;
}
async #handleEvent(event, ack, nack) {
if (event.type !== "agent-schedule.fire") {
await ack?.();
return;
}
const mastra = this.mastra;
const payload = event.data;
try {
await this.#dispatch(mastra, payload);
await ack?.();
} catch (err) {
this.deps?.logger?.error("AgentScheduleWorker: error processing agent-schedule.fire", {
scheduleId: payload?.scheduleId,
claimId: payload?.claimId,
error: err
});
await nack?.();
}
}
async #dispatch(mastra, data) {
const { scheduleId, claimId, scheduledFireAt, target } = data;
const actualFireAt = Date.now();
const result = await executeAgentSchedule(mastra, scheduleId, target, {
triggerKind: data.triggerKind ?? "schedule-fire",
firedAt: new Date(actualFireAt),
logger: this.deps?.logger
});
await this.#recordTrigger({
scheduleId,
claimId,
scheduledFireAt,
actualFireAt,
outcome: result.outcome,
runId: result.runId,
error: result.reason,
triggerKind: data.triggerKind ?? "schedule-fire"
});
}
async #recordTrigger(args) {
const store = await this.deps?.storage.getStore("schedules");
if (!store) return;
try {
await store.recordTrigger({
scheduleId: args.scheduleId,
runId: args.runId ?? args.claimId,
scheduledFireAt: args.scheduledFireAt,
actualFireAt: args.actualFireAt,
outcome: args.outcome,
error: args.error,
triggerKind: args.triggerKind
});
} catch (err) {
this.deps?.logger?.error("AgentScheduleWorker: failed to record trigger", {
scheduleId: args.scheduleId,
claimId: args.claimId,
error: err
});
}
}
};
async function selfClean(mastra, scheduleId) {
try {
const store = await mastra.getStorage()?.getStore("schedules");
if (!store) return;
await store.deleteSchedule(scheduleId);
} catch (error) {
mastra.getLogger?.()?.debug?.("agent-schedule self-clean failed", { scheduleId, error });
}
}
async function executeAgentSchedule(mastra, scheduleId, target, ctx = {}) {
const { agentId } = target;
const trigger = {
kind: ctx.triggerKind === "manual" ? "manual" : "cron",
firedAt: ctx.firedAt ?? /* @__PURE__ */ new Date()
};
const log = ctx.logger ?? mastra.getLogger?.();
const agent = (() => {
try {
return mastra.getAgentById(agentId);
} catch {
return null;
}
})();
if (!agent) {
await selfClean(mastra, scheduleId);
return {
status: "agent-missing",
outcome: "failed",
reason: `agent "${agentId}" no longer registered`
};
}
const hooks = mastra.__getScheduleHooks?.() ?? void 0;
const scheduleRef = hooks ? await loadScheduleRef(mastra, scheduleId, target, log) : scheduleRefFromTarget(scheduleId, target);
const rowDefaults = buildEffectiveFromTarget(target);
let prepared;
if (hooks?.prepare) {
try {
const prepareCtx = {
mastra,
agentId,
schedule: scheduleRef,
trigger
};
prepared = await hooks.prepare(prepareCtx);
} catch (err) {
await safeHookCall(
log,
() => hooks.onError?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
phase: "prepare",
error: err instanceof Error ? err : new Error(String(err)),
effective: rowDefaults
})
);
return {
status: "invalid-input",
outcome: "failed",
reason: err instanceof Error ? err.message : String(err)
};
}
}
if (prepared === null) {
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "skipped",
effective: rowDefaults
})
);
return { status: "fired", outcome: "skipped" };
}
const effective = mergeEffective(rowDefaults, prepared);
const scheduleRunMeta = {
scheduleId,
...effective.threadId ? { threadId: effective.threadId } : {}
};
if (effective.threadId) {
if (!effective.resourceId) {
const reason = "resourceId required when threadId is set";
await safeHookCall(
log,
() => hooks?.onError?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
phase: "run",
error: new Error(reason),
effective
})
);
return { status: "invalid-input", outcome: "failed", reason };
}
const memory = await agent.getMemory();
if (memory) {
const thread = await memory.getThreadById({ threadId: effective.threadId });
if (!thread) {
await selfClean(mastra, scheduleId);
const reason = `thread "${effective.threadId}" not found`;
await safeHookCall(
log,
() => hooks?.onError?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
phase: "run",
error: new Error(reason),
effective
})
);
return { status: "thread-missing", outcome: "failed", reason };
}
}
let signalResult;
try {
signalResult = agent.sendSignal(
{
type: effective.signalType ?? "notification",
tagName: effective.tagName ?? "schedule",
contents: effective.prompt,
...effective.attributes ? { attributes: effective.attributes } : {},
providerOptions: mergeProviderOptions(effective.providerOptions, scheduleRunMeta)
},
{
resourceId: effective.resourceId,
threadId: effective.threadId,
...effective.ifActive ? { ifActive: effective.ifActive } : {},
...effective.ifIdle ? { ifIdle: buildIfIdleOptions(effective.ifIdle) } : {}
}
);
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
await safeHookCall(
log,
() => hooks?.onError?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
phase: "run",
error,
effective
})
);
return { status: "invalid-input", outcome: "failed", reason: error.message };
}
let settled;
try {
settled = await signalResult.accepted;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
await safeHookCall(
log,
() => hooks?.onError?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
phase: "run",
error,
effective
})
);
return { status: "invalid-input", outcome: "failed", reason: error.message };
}
const action = settled.action;
const runId = "runId" in settled ? settled.runId : void 0;
if (action === "deliver") {
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "delivered",
runId,
joinedExistingRun: true,
effective
})
);
return { status: "signal-accepted", outcome: "delivered", runId };
}
if (action === "persist") {
if (signalResult.persisted) {
try {
await signalResult.persisted;
} catch {
}
}
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "persisted",
runId,
effective
})
);
return { status: "signal-accepted", outcome: "persisted", runId };
}
if (action === "discard") {
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "discarded",
runId,
effective
})
);
return { status: "signal-accepted", outcome: "discarded", runId };
}
if (action === "blocked") {
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "skipped",
runId,
effective
})
);
return { status: "skipped-thread-blocked", outcome: "skipped", runId };
}
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "succeeded",
runId,
effective
})
);
return { status: "signal-accepted", outcome: "succeeded", runId };
}
try {
const result = await agent.generate(effective.prompt, {
providerOptions: mergeProviderOptions(effective.providerOptions, scheduleRunMeta)
});
const runId = extractRunId(result);
await safeHookCall(
log,
() => hooks?.onFinish?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
outcome: "succeeded",
runId,
result: extractRunSnapshot(result),
effective
})
);
return { status: "fired", outcome: "succeeded", runId };
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
if (isAbortError(error)) {
await safeHookCall(
log,
() => hooks?.onAbort?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
runId: extractRunId(error) ?? scheduleId,
effective
})
);
return { status: "fired", outcome: "aborted" };
}
await safeHookCall(
log,
() => hooks?.onError?.({
mastra,
agentId,
schedule: scheduleRef,
trigger,
phase: "run",
error,
effective
})
);
return { status: "invalid-input", outcome: "failed", reason: error.message };
}
}
function buildEffectiveFromTarget(target) {
return {
threadId: target.threadId,
resourceId: target.resourceId,
prompt: target.prompt,
signalType: target.signalType,
tagName: target.tagName,
attributes: target.attributes,
providerOptions: target.providerOptions,
ifActive: target.ifActive,
ifIdle: target.ifIdle
};
}
function buildIfIdleOptions(ifIdle) {
const requestContext = ifIdle.streamOptions?.requestContext;
return {
...ifIdle.behavior ? { behavior: ifIdle.behavior } : {},
...ifIdle.attributes ? { attributes: ifIdle.attributes } : {},
...requestContext ? { streamOptions: { requestContext: new RequestContext(Object.entries(requestContext)) } } : {}
};
}
function mergeEffective(base, overrides) {
if (!overrides) return base;
return {
...base,
...overrides
};
}
function mergeProviderOptions(fromHook, scheduleRunMeta) {
const base = fromHook ?? {};
const baseMastra = base.mastra ?? {};
return {
...base,
mastra: {
...baseMastra,
schedule: scheduleRunMeta
}
};
}
async function loadScheduleRef(mastra, scheduleId, target, logger) {
try {
const schedule = await mastra.schedules.get(scheduleId);
if (schedule && schedule.agentId !== void 0) return { ...schedule, agentId: schedule.agentId };
} catch (err) {
logger?.debug?.("AgentScheduleWorker: failed to load schedule row for hook context", { scheduleId, error: err });
}
return scheduleRefFromTarget(scheduleId, target);
}
function scheduleRefFromTarget(scheduleId, target) {
return {
id: scheduleId,
agentId: target.agentId,
...target.name !== void 0 ? { name: target.name } : {}
};
}
async function safeHookCall(logger, fn) {
try {
await fn();
} catch (err) {
logger?.error?.("AgentScheduleWorker: hook threw, ignoring", { error: err });
}
}
function isAbortError(err) {
if (!err || typeof err !== "object") return false;
const name = err.name;
return name === "AbortError";
}
function extractRunId(value) {
if (value && typeof value === "object" && "runId" in value) {
const runId = value.runId;
if (typeof runId === "string") return runId;
}
return void 0;
}
function extractRunSnapshot(value) {
if (!value || typeof value !== "object") return void 0;
const v = value;
const snapshot = {};
if (typeof v.text === "string") snapshot.text = v.text;
if (v.usage && typeof v.usage === "object") snapshot.usage = v.usage;
if (typeof v.finishReason === "string") snapshot.finishReason = v.finishReason;
return Object.keys(snapshot).length > 0 ? snapshot : void 0;
}
export { AgentScheduleWorker, TOPIC_AGENT_SCHEDULES, executeAgentSchedule };
//# sourceMappingURL=worker-RIJ2XRDF.js.map
//# sourceMappingURL=worker-RIJ2XRDF.js.map