@mastra/core
Version:
167 lines (166 loc) • 6.57 kB
JavaScript
const require_agent = require("./agent-DCD4MApC.cjs");
//#region src/channels/agent-controller-channels.ts
/**
* Runs an AgentController inside chat channels (Slack, Discord, ...).
*
* Extends {@link AgentChannels} so all inbound machinery (thread mapping,
* history, attachments, event context) and all outbound rendering
* (`ChatChannelOutputProcessor` with native streaming, tool cards, typing
* status) are reused unchanged. Only the dispatch seams differ: instead of
* routing into a bare agent, inbound messages route into a controller
* `Session` — one durable session per chat thread, keyed by the mapped
* Mastra thread's `resourceId`.
*
* V1 targets long-lived servers: controller sessions are in-memory objects
* and do not survive process restarts.
*/
var AgentControllerChannels = class extends require_agent.AgentChannels {
controller = null;
/**
* Session resourceIds whose adapter can't render approval buttons, so their
* runs must auto-approve tools (`requireToolApproval: false`) instead of
* parking forever on an approval nobody can answer. Kept outside session
* state on purpose: state is validated against the controller's
* `stateSchema`, which would strip (or reject) an injected flag. Refreshed
* on every inbound message; in-memory only, matching the v1 long-lived
* server scope.
*/
autoApproveResourceIds = /* @__PURE__ */ new Set();
/** @internal Called by AgentController's constructor to bind itself. */
__setController(controller) {
this.controller = controller;
}
/**
* @internal Consulted by the controller's run-option builder: `true` when
* the session's channel adapter can't render approval buttons and tool
* calls must auto-approve (the session-side equivalent of the base agent
* path's `autoResumeSuspendedTools`).
*/
__isAutoApproveResource(resourceId) {
return this.autoApproveResourceIds.has(resourceId);
}
/**
* @internal No-op override. The controller attaches this instance to its
* mode agents via `Agent.setChannels`, which calls `__setAgent(agent)` —
* with multiple mode agents the last one would win. Every `this.agent` use
* is overridden in this subclass, so keep the base field unset rather than
* holding a misleading ref.
*/
__setAgent(_agent) {}
getOwnerId() {
return this.controller?.id ?? null;
}
getWebhookBasePath() {
return `/api/agent-controllers/${this.getOwnerId()}`;
}
getMastra() {
return this.controller?.getMastra();
}
/**
* One session per chat thread: unless the user supplied a custom
* `resolveResourceId`, key new Mastra threads (and therefore controller
* sessions) off the platform + external thread id.
*/
resolveChannelResourceId(args) {
const base = super.resolveChannelResourceId(args);
if (typeof base === "function") return base;
return `channel:${args.chatThread.id}`;
}
/**
* Route an inbound chat message into the controller session bound to this
* chat thread. Output renders back to the platform through the channels
* output processor: the `requestContext` built by the base class (carrying
* the channel context and render context) flows through the session into
* the run.
*/
async dispatchInboundMessage(args) {
const { signalContents, attributes, providerOptions, requestContext, thread, autoResumeSuspendedTools } = args;
const session = await this.getSessionForThread(thread, requestContext);
const sessionResourceId = thread.resourceId;
if (autoResumeSuspendedTools) this.autoApproveResourceIds.add(sessionResourceId);
else this.autoApproveResourceIds.delete(sessionResourceId);
await session.sendSignal({
content: signalContents,
ifActive: { attributes },
ifIdle: { attributes },
requestContext,
providerOptions
}).accepted;
}
/**
* Resolve an approval-card "approve" action against the controller session's
* parked tool-approval gate. The run engine — awaiting the gate inside its
* stream-consumer loop — performs the actual resume itself and keeps
* consuming, so the continuation renders through the output processor.
*/
async dispatchApproval(args) {
await this.respondToSessionApproval({
decision: "approve",
...args
});
}
/**
* Resolve an approval-card "deny" action against the controller session's
* parked tool-approval gate (see {@link dispatchApproval}).
*/
async dispatchDecline(args) {
await this.respondToSessionApproval({
decision: "decline",
...args
});
}
/**
* Shared approve/decline path. Never calls the session's internal
* `approveToolCall`/`declineToolCall` executors directly — the engine parked
* at the gate owns the resume. `respondToToolApproval` is a silent no-op
* when nothing is armed or the toolCallId mismatches, so staleness is
* pre-checked explicitly (an armed gate does not survive process restarts,
* so restart-recovered approvals are always stale — consistent with the
* v1 long-lived-server scope).
*/
async respondToSessionApproval({ decision, toolCallId, requestContext, memory }) {
const session = await this.getSessionForThread({
id: memory.thread,
resourceId: memory.resource
});
if (!session.approval.isArmed() || session.approval.getToolCallId() !== toolCallId) {
this.log("info", `Ignoring stale tool ${decision === "approve" ? "approval" : "denial"} action (no matching parked approval for toolCallId=${toolCallId})`);
return;
}
session.respondToToolApproval({
decision,
toolCallId,
requestContext
});
}
/**
* Get-or-create the durable controller session for a mapped channel thread
* and bind it to that thread. Keyed off the thread's own `resourceId` so
* pre-existing threads (custom resolveResourceId, or created before this
* feature) always pass the session's thread-ownership check.
*/
async getSessionForThread(thread, requestContext) {
const controller = this.requireController();
const channelResourceId = thread.resourceId;
const session = await controller.createSession({
resourceId: channelResourceId,
id: channelResourceId,
ownerId: controller.id,
requestContext
});
if (session.thread.getId() !== thread.id) await session.thread.switch({ threadId: thread.id });
return session;
}
requireController() {
if (!this.controller) throw new Error("AgentControllerChannels is not bound to an AgentController. Pass it via `channels` in AgentControllerConfig.");
return this.controller;
}
};
//#endregion
Object.defineProperty(exports, "AgentControllerChannels", {
enumerable: true,
get: function() {
return AgentControllerChannels;
}
});
//# sourceMappingURL=channels-DpBkVl9k.cjs.map