mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
refactor: move session lifecycle and outbound fallbacks into plugins
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => {
|
||||
const getThreadBindingManagerMock = vi.fn();
|
||||
@@ -19,28 +22,34 @@ const hoisted = vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../../extensions/discord/src/monitor/thread-bindings.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<
|
||||
typeof import("../../../extensions/discord/src/monitor/thread-bindings.js")
|
||||
>();
|
||||
vi.mock("../../plugins/runtime/index.js", async () => {
|
||||
const discordThreadBindings = await vi.importActual<
|
||||
typeof import("../../../extensions/discord/src/monitor/thread-bindings.js")
|
||||
>("../../../extensions/discord/src/monitor/thread-bindings.js");
|
||||
return {
|
||||
...actual,
|
||||
getThreadBindingManager: hoisted.getThreadBindingManagerMock,
|
||||
setThreadBindingIdleTimeoutBySessionKey: hoisted.setThreadBindingIdleTimeoutBySessionKeyMock,
|
||||
setThreadBindingMaxAgeBySessionKey: hoisted.setThreadBindingMaxAgeBySessionKeyMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../../extensions/telegram/src/thread-bindings.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("../../../extensions/telegram/src/thread-bindings.js")>();
|
||||
return {
|
||||
...actual,
|
||||
setTelegramThreadBindingIdleTimeoutBySessionKey:
|
||||
hoisted.setTelegramThreadBindingIdleTimeoutBySessionKeyMock,
|
||||
setTelegramThreadBindingMaxAgeBySessionKey:
|
||||
hoisted.setTelegramThreadBindingMaxAgeBySessionKeyMock,
|
||||
createPluginRuntime: () => ({
|
||||
channel: {
|
||||
discord: {
|
||||
threadBindings: {
|
||||
getManager: hoisted.getThreadBindingManagerMock,
|
||||
resolveIdleTimeoutMs: discordThreadBindings.resolveThreadBindingIdleTimeoutMs,
|
||||
resolveInactivityExpiresAt:
|
||||
discordThreadBindings.resolveThreadBindingInactivityExpiresAt,
|
||||
resolveMaxAgeMs: discordThreadBindings.resolveThreadBindingMaxAgeMs,
|
||||
resolveMaxAgeExpiresAt: discordThreadBindings.resolveThreadBindingMaxAgeExpiresAt,
|
||||
setIdleTimeoutBySessionKey: hoisted.setThreadBindingIdleTimeoutBySessionKeyMock,
|
||||
setMaxAgeBySessionKey: hoisted.setThreadBindingMaxAgeBySessionKeyMock,
|
||||
unbindBySessionKey: vi.fn(),
|
||||
},
|
||||
},
|
||||
telegram: {
|
||||
threadBindings: {
|
||||
setIdleTimeoutBySessionKey: hoisted.setTelegramThreadBindingIdleTimeoutBySessionKeyMock,
|
||||
setMaxAgeBySessionKey: hoisted.setTelegramThreadBindingMaxAgeBySessionKeyMock,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -168,6 +177,9 @@ function createFakeThreadBindingManager(binding: FakeBinding | null) {
|
||||
|
||||
describe("/session idle and /session max-age", () => {
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
|
||||
);
|
||||
hoisted.getThreadBindingManagerMock.mockReset();
|
||||
hoisted.setThreadBindingIdleTimeoutBySessionKeyMock.mockReset();
|
||||
hoisted.setThreadBindingMaxAgeBySessionKeyMock.mockReset();
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import {
|
||||
formatThreadBindingDurationLabel,
|
||||
getThreadBindingManager,
|
||||
resolveThreadBindingIdleTimeoutMs,
|
||||
resolveThreadBindingInactivityExpiresAt,
|
||||
resolveThreadBindingMaxAgeExpiresAt,
|
||||
resolveThreadBindingMaxAgeMs,
|
||||
setThreadBindingIdleTimeoutBySessionKey,
|
||||
setThreadBindingMaxAgeBySessionKey,
|
||||
} from "../../../extensions/discord/src/monitor/thread-bindings.js";
|
||||
import {
|
||||
setTelegramThreadBindingIdleTimeoutBySessionKey,
|
||||
setTelegramThreadBindingMaxAgeBySessionKey,
|
||||
} from "../../../extensions/telegram/src/thread-bindings.js";
|
||||
import { resolveFastModeState } from "../../agents/fast-mode.js";
|
||||
import { formatThreadBindingDurationLabel } from "../../channels/thread-bindings-messages.js";
|
||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||
import { isRestartEnabled } from "../../config/commands.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
@@ -20,6 +7,7 @@ import { getSessionBindingService } from "../../infra/outbound/session-binding-s
|
||||
import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";
|
||||
import { scheduleGatewaySigusr1Restart, triggerOpenClawRestart } from "../../infra/restart.js";
|
||||
import { loadCostUsageSummary, loadSessionCostSummary } from "../../infra/session-cost-usage.js";
|
||||
import { createPluginRuntime } from "../../plugins/runtime/index.js";
|
||||
import { formatTokenCount, formatUsd } from "../../utils/usage-format.js";
|
||||
import { parseActivationCommand } from "../group-activation.js";
|
||||
import { parseSendPolicyCommand } from "../send-policy.js";
|
||||
@@ -34,6 +22,7 @@ const SESSION_COMMAND_PREFIX = "/session";
|
||||
const SESSION_DURATION_OFF_VALUES = new Set(["off", "disable", "disabled", "none", "0"]);
|
||||
const SESSION_ACTION_IDLE = "idle";
|
||||
const SESSION_ACTION_MAX_AGE = "max-age";
|
||||
const channelRuntime = createPluginRuntime().channel;
|
||||
|
||||
function resolveSessionCommandUsage() {
|
||||
return "Usage: /session idle <duration|off> | /session max-age <duration|off> (example: /session idle 24h)";
|
||||
@@ -385,7 +374,9 @@ export const handleSessionCommand: CommandHandler = async (params, allowTextComm
|
||||
params.ctx.MessageThreadId != null ? String(params.ctx.MessageThreadId).trim() : "";
|
||||
const telegramConversationId = onTelegram ? resolveTelegramConversationId(params) : undefined;
|
||||
|
||||
const discordManager = onDiscord ? getThreadBindingManager(accountId) : null;
|
||||
const discordManager = onDiscord
|
||||
? channelRuntime.discord.threadBindings.getManager(accountId)
|
||||
: null;
|
||||
if (onDiscord && !discordManager) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
@@ -433,13 +424,13 @@ export const handleSessionCommand: CommandHandler = async (params, allowTextComm
|
||||
}
|
||||
|
||||
const idleTimeoutMs = onDiscord
|
||||
? resolveThreadBindingIdleTimeoutMs({
|
||||
? channelRuntime.discord.threadBindings.resolveIdleTimeoutMs({
|
||||
record: discordBinding!,
|
||||
defaultIdleTimeoutMs: discordManager!.getIdleTimeoutMs(),
|
||||
})
|
||||
: resolveTelegramBindingDurationMs(telegramBinding!, "idleTimeoutMs", 24 * 60 * 60 * 1000);
|
||||
const idleExpiresAt = onDiscord
|
||||
? resolveThreadBindingInactivityExpiresAt({
|
||||
? channelRuntime.discord.threadBindings.resolveInactivityExpiresAt({
|
||||
record: discordBinding!,
|
||||
defaultIdleTimeoutMs: discordManager!.getIdleTimeoutMs(),
|
||||
})
|
||||
@@ -447,13 +438,13 @@ export const handleSessionCommand: CommandHandler = async (params, allowTextComm
|
||||
? resolveTelegramBindingLastActivityAt(telegramBinding!) + idleTimeoutMs
|
||||
: undefined;
|
||||
const maxAgeMs = onDiscord
|
||||
? resolveThreadBindingMaxAgeMs({
|
||||
? channelRuntime.discord.threadBindings.resolveMaxAgeMs({
|
||||
record: discordBinding!,
|
||||
defaultMaxAgeMs: discordManager!.getMaxAgeMs(),
|
||||
})
|
||||
: resolveTelegramBindingDurationMs(telegramBinding!, "maxAgeMs", 0);
|
||||
const maxAgeExpiresAt = onDiscord
|
||||
? resolveThreadBindingMaxAgeExpiresAt({
|
||||
? channelRuntime.discord.threadBindings.resolveMaxAgeExpiresAt({
|
||||
record: discordBinding!,
|
||||
defaultMaxAgeMs: discordManager!.getMaxAgeMs(),
|
||||
})
|
||||
@@ -528,24 +519,24 @@ export const handleSessionCommand: CommandHandler = async (params, allowTextComm
|
||||
const updatedBindings = (() => {
|
||||
if (onDiscord) {
|
||||
return action === SESSION_ACTION_IDLE
|
||||
? setThreadBindingIdleTimeoutBySessionKey({
|
||||
? channelRuntime.discord.threadBindings.setIdleTimeoutBySessionKey({
|
||||
targetSessionKey: discordBinding!.targetSessionKey,
|
||||
accountId,
|
||||
idleTimeoutMs: durationMs,
|
||||
})
|
||||
: setThreadBindingMaxAgeBySessionKey({
|
||||
: channelRuntime.discord.threadBindings.setMaxAgeBySessionKey({
|
||||
targetSessionKey: discordBinding!.targetSessionKey,
|
||||
accountId,
|
||||
maxAgeMs: durationMs,
|
||||
});
|
||||
}
|
||||
return action === SESSION_ACTION_IDLE
|
||||
? setTelegramThreadBindingIdleTimeoutBySessionKey({
|
||||
? channelRuntime.telegram.threadBindings.setIdleTimeoutBySessionKey({
|
||||
targetSessionKey: telegramBinding!.targetSessionKey,
|
||||
accountId,
|
||||
idleTimeoutMs: durationMs,
|
||||
})
|
||||
: setTelegramThreadBindingMaxAgeBySessionKey({
|
||||
: channelRuntime.telegram.threadBindings.setMaxAgeBySessionKey({
|
||||
targetSessionKey: telegramBinding!.targetSessionKey,
|
||||
accountId,
|
||||
maxAgeMs: durationMs,
|
||||
|
||||
@@ -102,15 +102,21 @@ vi.mock("../plugins/hook-runner-global.js", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../extensions/discord/src/monitor/thread-bindings.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<
|
||||
typeof import("../../extensions/discord/src/monitor/thread-bindings.js")
|
||||
>();
|
||||
vi.mock("../plugins/runtime/runtime-discord.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/runtime/runtime-discord.js")>();
|
||||
return {
|
||||
...actual,
|
||||
unbindThreadBindingsBySessionKey: (params: unknown) =>
|
||||
threadBindingMocks.unbindThreadBindingsBySessionKey(params),
|
||||
createRuntimeDiscord: () => {
|
||||
const runtime = actual.createRuntimeDiscord();
|
||||
return {
|
||||
...runtime,
|
||||
threadBindings: {
|
||||
...runtime.threadBindings,
|
||||
unbindBySessionKey: (params: unknown) =>
|
||||
threadBindingMocks.unbindThreadBindingsBySessionKey(params),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { unbindThreadBindingsBySessionKey } from "../../extensions/discord/src/monitor/thread-bindings.js";
|
||||
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
|
||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { clearBootstrapSnapshot } from "../agents/bootstrap-cache.js";
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
import { logVerbose } from "../globals.js";
|
||||
import { createInternalHookEvent, triggerInternalHook } from "../hooks/internal-hooks.js";
|
||||
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||
import { createPluginRuntime } from "../plugins/runtime/index.js";
|
||||
import {
|
||||
isSubagentSessionKey,
|
||||
normalizeAgentId,
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from "./session-utils.js";
|
||||
|
||||
const ACP_RUNTIME_CLEANUP_TIMEOUT_MS = 15_000;
|
||||
const channelRuntime = createPluginRuntime().channel;
|
||||
|
||||
function stripRuntimeModelState(entry?: SessionEntry): SessionEntry | undefined {
|
||||
if (!entry) {
|
||||
@@ -70,7 +71,7 @@ export async function emitSessionUnboundLifecycleEvent(params: {
|
||||
emitHooks?: boolean;
|
||||
}) {
|
||||
const targetKind = isSubagentSessionKey(params.targetSessionKey) ? "subagent" : "acp";
|
||||
unbindThreadBindingsBySessionKey({
|
||||
channelRuntime.discord.threadBindings.unbindBySessionKey({
|
||||
targetSessionKey: params.targetSessionKey,
|
||||
targetKind,
|
||||
reason: params.reason,
|
||||
|
||||
@@ -22,7 +22,6 @@ import type {
|
||||
ExecApprovalRequest,
|
||||
ExecApprovalResolved,
|
||||
} from "./exec-approvals.js";
|
||||
import { resolveBuiltInExecApprovalAdapter } from "./outbound/built-in-channel-adapters.js";
|
||||
import { deliverOutboundPayloads } from "./outbound/deliver.js";
|
||||
|
||||
const log = createSubsystemLogger("gateway/exec-approvals");
|
||||
@@ -119,8 +118,7 @@ function shouldSkipForwardingFallback(params: {
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
const adapter =
|
||||
getChannelPlugin(channel)?.execApprovals ?? resolveBuiltInExecApprovalAdapter(channel);
|
||||
const adapter = getChannelPlugin(channel)?.execApprovals;
|
||||
return (
|
||||
adapter?.shouldSuppressForwardingFallback?.({
|
||||
cfg: params.cfg,
|
||||
@@ -278,9 +276,7 @@ function buildRequestPayloadForTarget(
|
||||
): ReplyPayload {
|
||||
const channel = normalizeMessageChannel(target.channel) ?? target.channel;
|
||||
const pluginPayload = channel
|
||||
? (
|
||||
getChannelPlugin(channel)?.execApprovals ?? resolveBuiltInExecApprovalAdapter(channel)
|
||||
)?.buildPendingPayload?.({
|
||||
? getChannelPlugin(channel)?.execApprovals?.buildPendingPayload?.({
|
||||
cfg,
|
||||
request,
|
||||
target,
|
||||
@@ -415,9 +411,7 @@ export function createExecApprovalForwarder(
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
await (
|
||||
getChannelPlugin(channel)?.execApprovals ?? resolveBuiltInExecApprovalAdapter(channel)
|
||||
)?.beforeDeliverPending?.({
|
||||
await getChannelPlugin(channel)?.execApprovals?.beforeDeliverPending?.({
|
||||
cfg,
|
||||
target,
|
||||
payload,
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { Separator, TextDisplay } from "@buape/carbon";
|
||||
import {
|
||||
listDiscordAccountIds,
|
||||
resolveDiscordAccount,
|
||||
} from "../../../extensions/discord/src/accounts.js";
|
||||
import { isDiscordExecApprovalClientEnabled } from "../../../extensions/discord/src/exec-approvals.js";
|
||||
import { DiscordUiContainer } from "../../../extensions/discord/src/ui.js";
|
||||
import { listTelegramAccountIds } from "../../../extensions/telegram/src/accounts.js";
|
||||
import { buildTelegramExecApprovalButtons } from "../../../extensions/telegram/src/approval-buttons.js";
|
||||
import {
|
||||
isTelegramExecApprovalClientEnabled,
|
||||
resolveTelegramExecApprovalTarget,
|
||||
} from "../../../extensions/telegram/src/exec-approvals.js";
|
||||
import type { ChannelExecApprovalAdapter } from "../../channels/plugins/types.adapters.js";
|
||||
import type { ChannelCrossContextComponentsFactory } from "../../channels/plugins/types.core.js";
|
||||
import type { ChannelId } from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { normalizeMessageChannel } from "../../utils/message-channel.js";
|
||||
import { resolveExecApprovalCommandDisplay } from "../exec-approval-command-display.js";
|
||||
import { buildExecApprovalPendingReplyPayload } from "../exec-approval-reply.js";
|
||||
|
||||
const BUILT_IN_DISCORD_CROSS_CONTEXT_COMPONENTS: ChannelCrossContextComponentsFactory = (
|
||||
params,
|
||||
) => {
|
||||
const trimmed = params.message.trim();
|
||||
const components: Array<TextDisplay | Separator> = [];
|
||||
if (trimmed) {
|
||||
components.push(new TextDisplay(params.message));
|
||||
components.push(new Separator({ divider: true, spacing: "small" }));
|
||||
}
|
||||
components.push(new TextDisplay(`*From ${params.originLabel}*`));
|
||||
return [new DiscordUiContainer({ cfg: params.cfg, accountId: params.accountId, components })];
|
||||
};
|
||||
|
||||
function hasDiscordExecApprovalDmRoute(cfg: OpenClawConfig): boolean {
|
||||
return listDiscordAccountIds(cfg).some((accountId) => {
|
||||
const execApprovals = resolveDiscordAccount({ cfg, accountId }).config.execApprovals;
|
||||
if (!execApprovals?.enabled || (execApprovals.approvers?.length ?? 0) === 0) {
|
||||
return false;
|
||||
}
|
||||
const target = execApprovals.target ?? "dm";
|
||||
return target === "dm" || target === "both";
|
||||
});
|
||||
}
|
||||
|
||||
function hasTelegramExecApprovalDmRoute(cfg: OpenClawConfig): boolean {
|
||||
return listTelegramAccountIds(cfg).some((accountId) => {
|
||||
if (!isTelegramExecApprovalClientEnabled({ cfg, accountId })) {
|
||||
return false;
|
||||
}
|
||||
const target = resolveTelegramExecApprovalTarget({ cfg, accountId });
|
||||
return target === "dm" || target === "both";
|
||||
});
|
||||
}
|
||||
|
||||
const BUILT_IN_DISCORD_EXEC_APPROVALS: ChannelExecApprovalAdapter = {
|
||||
getInitiatingSurfaceState: ({ cfg, accountId }) =>
|
||||
isDiscordExecApprovalClientEnabled({ cfg, accountId })
|
||||
? { kind: "enabled" }
|
||||
: { kind: "disabled" },
|
||||
hasConfiguredDmRoute: ({ cfg }) => hasDiscordExecApprovalDmRoute(cfg),
|
||||
shouldSuppressForwardingFallback: ({ cfg, target }) =>
|
||||
(normalizeMessageChannel(target.channel) ?? target.channel) === "discord" &&
|
||||
isDiscordExecApprovalClientEnabled({ cfg, accountId: target.accountId }),
|
||||
};
|
||||
|
||||
const BUILT_IN_TELEGRAM_EXEC_APPROVALS: ChannelExecApprovalAdapter = {
|
||||
getInitiatingSurfaceState: ({ cfg, accountId }) =>
|
||||
isTelegramExecApprovalClientEnabled({ cfg, accountId })
|
||||
? { kind: "enabled" }
|
||||
: { kind: "disabled" },
|
||||
hasConfiguredDmRoute: ({ cfg }) => hasTelegramExecApprovalDmRoute(cfg),
|
||||
shouldSuppressForwardingFallback: ({ cfg, target, request }) => {
|
||||
const channel = normalizeMessageChannel(target.channel) ?? target.channel;
|
||||
if (channel !== "telegram") {
|
||||
return false;
|
||||
}
|
||||
const requestChannel = normalizeMessageChannel(request.request.turnSourceChannel ?? "");
|
||||
if (requestChannel !== "telegram") {
|
||||
return false;
|
||||
}
|
||||
const accountId = target.accountId?.trim() || request.request.turnSourceAccountId?.trim();
|
||||
return isTelegramExecApprovalClientEnabled({ cfg, accountId });
|
||||
},
|
||||
buildPendingPayload: ({ request, nowMs }) => {
|
||||
const payload = buildExecApprovalPendingReplyPayload({
|
||||
approvalId: request.id,
|
||||
approvalSlug: request.id.slice(0, 8),
|
||||
approvalCommandId: request.id,
|
||||
command: resolveExecApprovalCommandDisplay(request.request).commandText,
|
||||
cwd: request.request.cwd ?? undefined,
|
||||
host: request.request.host === "node" ? "node" : "gateway",
|
||||
nodeId: request.request.nodeId ?? undefined,
|
||||
expiresAtMs: request.expiresAtMs,
|
||||
nowMs,
|
||||
});
|
||||
const buttons = buildTelegramExecApprovalButtons(request.id);
|
||||
if (!buttons) {
|
||||
return payload;
|
||||
}
|
||||
return {
|
||||
...payload,
|
||||
channelData: {
|
||||
...payload.channelData,
|
||||
telegram: { buttons },
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export function resolveBuiltInCrossContextComponentsFactory(
|
||||
channel: ChannelId,
|
||||
): ChannelCrossContextComponentsFactory | undefined {
|
||||
return channel === "discord" ? BUILT_IN_DISCORD_CROSS_CONTEXT_COMPONENTS : undefined;
|
||||
}
|
||||
|
||||
export function resolveBuiltInExecApprovalAdapter(
|
||||
channel: ChannelId,
|
||||
): ChannelExecApprovalAdapter | undefined {
|
||||
if (channel === "discord") {
|
||||
return BUILT_IN_DISCORD_EXEC_APPROVALS;
|
||||
}
|
||||
if (channel === "telegram") {
|
||||
return BUILT_IN_TELEGRAM_EXEC_APPROVALS;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { parseTelegramTarget } from "../../../extensions/telegram/src/targets.js";
|
||||
import type { ChatType } from "../../channels/chat-type.js";
|
||||
import type { ChannelId } from "../../channels/plugins/types.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js";
|
||||
|
||||
export type BuiltInExplicitTarget = {
|
||||
to: string;
|
||||
threadId?: string | number;
|
||||
chatType?: ChatType;
|
||||
};
|
||||
|
||||
export function resolveBuiltInExplicitTarget(
|
||||
channel: ChannelId,
|
||||
raw: string,
|
||||
): BuiltInExplicitTarget | null {
|
||||
if (channel === "telegram") {
|
||||
const target = parseTelegramTarget(raw);
|
||||
return {
|
||||
to: target.chatId,
|
||||
threadId: target.messageThreadId,
|
||||
chatType: target.chatType === "unknown" ? undefined : target.chatType,
|
||||
};
|
||||
}
|
||||
|
||||
if (channel === "whatsapp") {
|
||||
const normalized = normalizeWhatsAppTarget(raw);
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
to: normalized,
|
||||
chatType: isWhatsAppGroupJid(normalized) ? "group" : "direct",
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveBuiltInTargetChatType(channel: ChannelId, to: string): ChatType | undefined {
|
||||
return resolveBuiltInExplicitTarget(channel, to)?.chatType;
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type { TopLevelComponents } from "@buape/carbon";
|
||||
import { getChannelPlugin } from "../../channels/plugins/index.js";
|
||||
import type { ChannelId } from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { resolveBuiltInCrossContextComponentsFactory } from "./built-in-channel-adapters.js";
|
||||
|
||||
export type CrossContextComponentsBuilder = (message: string) => TopLevelComponents[];
|
||||
|
||||
@@ -23,9 +22,7 @@ const DEFAULT_ADAPTER: ChannelMessageAdapter = {
|
||||
};
|
||||
|
||||
export function getChannelMessageAdapter(channel: ChannelId): ChannelMessageAdapter {
|
||||
const adapter =
|
||||
getChannelPlugin(channel)?.messaging?.buildCrossContextComponents ??
|
||||
resolveBuiltInCrossContextComponentsFactory(channel);
|
||||
const adapter = getChannelPlugin(channel)?.messaging?.buildCrossContextComponents;
|
||||
if (adapter) {
|
||||
return {
|
||||
supportsComponentsV2: true,
|
||||
|
||||
@@ -387,7 +387,7 @@ describe("resolveSessionDeliveryTarget", () => {
|
||||
expect(resolved.threadId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps :topic: parsing when the telegram plugin registry is unavailable", () => {
|
||||
it("keeps raw :topic: targets when the telegram plugin registry is unavailable", () => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
|
||||
const resolved = resolveSessionDeliveryTarget({
|
||||
@@ -401,8 +401,8 @@ describe("resolveSessionDeliveryTarget", () => {
|
||||
explicitTo: "63448508:topic:1008013",
|
||||
});
|
||||
|
||||
expect(resolved.to).toBe("63448508");
|
||||
expect(resolved.threadId).toBe(1008013);
|
||||
expect(resolved.to).toBe("63448508:topic:1008013");
|
||||
expect(resolved.threadId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("explicitThreadId takes priority over :topic: parsed value", () => {
|
||||
|
||||
@@ -16,10 +16,6 @@ import {
|
||||
isDeliverableMessageChannel,
|
||||
normalizeMessageChannel,
|
||||
} from "../../utils/message-channel.js";
|
||||
import {
|
||||
resolveBuiltInExplicitTarget,
|
||||
resolveBuiltInTargetChatType,
|
||||
} from "./built-in-channel-messaging.js";
|
||||
import {
|
||||
normalizeDeliverableOutboundChannel,
|
||||
resolveOutboundChannelPlugin,
|
||||
@@ -78,7 +74,7 @@ function parseExplicitTargetWithPlugin(params: {
|
||||
return (
|
||||
resolveOutboundChannelPlugin({ channel: provider })?.messaging?.parseExplicitTarget?.({
|
||||
raw,
|
||||
}) ?? resolveBuiltInExplicitTarget(provider, raw)
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -422,7 +418,7 @@ function inferChatTypeFromTarget(params: {
|
||||
return (
|
||||
resolveOutboundChannelPlugin({
|
||||
channel: params.channel,
|
||||
})?.messaging?.inferTargetChatType?.({ to }) ?? resolveBuiltInTargetChatType(params.channel, to)
|
||||
})?.messaging?.inferTargetChatType?.({ to }) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,16 @@ import {
|
||||
listDiscordDirectoryPeersLive,
|
||||
} from "../../../extensions/discord/src/directory-live.js";
|
||||
import { monitorDiscordProvider } from "../../../extensions/discord/src/monitor.js";
|
||||
import {
|
||||
getThreadBindingManager,
|
||||
resolveThreadBindingIdleTimeoutMs,
|
||||
resolveThreadBindingInactivityExpiresAt,
|
||||
resolveThreadBindingMaxAgeExpiresAt,
|
||||
resolveThreadBindingMaxAgeMs,
|
||||
setThreadBindingIdleTimeoutBySessionKey,
|
||||
setThreadBindingMaxAgeBySessionKey,
|
||||
unbindThreadBindingsBySessionKey,
|
||||
} from "../../../extensions/discord/src/monitor/thread-bindings.js";
|
||||
import { probeDiscord } from "../../../extensions/discord/src/probe.js";
|
||||
import { resolveDiscordChannelAllowlist } from "../../../extensions/discord/src/resolve-channels.js";
|
||||
import { resolveDiscordUserAllowlist } from "../../../extensions/discord/src/resolve-users.js";
|
||||
@@ -36,6 +46,16 @@ export function createRuntimeDiscord(): PluginRuntimeChannel["discord"] {
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
monitorDiscordProvider,
|
||||
threadBindings: {
|
||||
getManager: getThreadBindingManager,
|
||||
resolveIdleTimeoutMs: resolveThreadBindingIdleTimeoutMs,
|
||||
resolveInactivityExpiresAt: resolveThreadBindingInactivityExpiresAt,
|
||||
resolveMaxAgeMs: resolveThreadBindingMaxAgeMs,
|
||||
resolveMaxAgeExpiresAt: resolveThreadBindingMaxAgeExpiresAt,
|
||||
setIdleTimeoutBySessionKey: setThreadBindingIdleTimeoutBySessionKey,
|
||||
setMaxAgeBySessionKey: setThreadBindingMaxAgeBySessionKey,
|
||||
unbindBySessionKey: unbindThreadBindingsBySessionKey,
|
||||
},
|
||||
typing: {
|
||||
pulse: sendTypingDiscord,
|
||||
start: async ({ channelId, accountId, cfg, intervalMs }) =>
|
||||
|
||||
@@ -15,6 +15,10 @@ import {
|
||||
sendTypingTelegram,
|
||||
unpinMessageTelegram,
|
||||
} from "../../../extensions/telegram/src/send.js";
|
||||
import {
|
||||
setTelegramThreadBindingIdleTimeoutBySessionKey,
|
||||
setTelegramThreadBindingMaxAgeBySessionKey,
|
||||
} from "../../../extensions/telegram/src/thread-bindings.js";
|
||||
import { resolveTelegramToken } from "../../../extensions/telegram/src/token.js";
|
||||
import { telegramMessageActions } from "../../channels/plugins/actions/telegram.js";
|
||||
import { createTelegramTypingLease } from "./runtime-telegram-typing.js";
|
||||
@@ -30,6 +34,10 @@ export function createRuntimeTelegram(): PluginRuntimeChannel["telegram"] {
|
||||
sendPollTelegram,
|
||||
monitorTelegramProvider,
|
||||
messageActions: telegramMessageActions,
|
||||
threadBindings: {
|
||||
setIdleTimeoutBySessionKey: setTelegramThreadBindingIdleTimeoutBySessionKey,
|
||||
setMaxAgeBySessionKey: setTelegramThreadBindingMaxAgeBySessionKey,
|
||||
},
|
||||
typing: {
|
||||
pulse: sendTypingTelegram,
|
||||
start: async ({ to, accountId, cfg, intervalMs, messageThreadId }) =>
|
||||
|
||||
@@ -98,6 +98,16 @@ export type PluginRuntimeChannel = {
|
||||
sendMessageDiscord: typeof import("../../../extensions/discord/src/send.js").sendMessageDiscord;
|
||||
sendPollDiscord: typeof import("../../../extensions/discord/src/send.js").sendPollDiscord;
|
||||
monitorDiscordProvider: typeof import("../../../extensions/discord/src/monitor.js").monitorDiscordProvider;
|
||||
threadBindings: {
|
||||
getManager: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").getThreadBindingManager;
|
||||
resolveIdleTimeoutMs: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").resolveThreadBindingIdleTimeoutMs;
|
||||
resolveInactivityExpiresAt: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").resolveThreadBindingInactivityExpiresAt;
|
||||
resolveMaxAgeMs: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").resolveThreadBindingMaxAgeMs;
|
||||
resolveMaxAgeExpiresAt: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").resolveThreadBindingMaxAgeExpiresAt;
|
||||
setIdleTimeoutBySessionKey: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").setThreadBindingIdleTimeoutBySessionKey;
|
||||
setMaxAgeBySessionKey: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").setThreadBindingMaxAgeBySessionKey;
|
||||
unbindBySessionKey: typeof import("../../../extensions/discord/src/monitor/thread-bindings.js").unbindThreadBindingsBySessionKey;
|
||||
};
|
||||
typing: {
|
||||
pulse: typeof import("../../../extensions/discord/src/send.js").sendTypingDiscord;
|
||||
start: (params: {
|
||||
@@ -138,6 +148,10 @@ export type PluginRuntimeChannel = {
|
||||
sendPollTelegram: typeof import("../../../extensions/telegram/src/send.js").sendPollTelegram;
|
||||
monitorTelegramProvider: typeof import("../../../extensions/telegram/src/monitor.js").monitorTelegramProvider;
|
||||
messageActions: typeof import("../../channels/plugins/actions/telegram.js").telegramMessageActions;
|
||||
threadBindings: {
|
||||
setIdleTimeoutBySessionKey: typeof import("../../../extensions/telegram/src/thread-bindings.js").setTelegramThreadBindingIdleTimeoutBySessionKey;
|
||||
setMaxAgeBySessionKey: typeof import("../../../extensions/telegram/src/thread-bindings.js").setTelegramThreadBindingMaxAgeBySessionKey;
|
||||
};
|
||||
typing: {
|
||||
pulse: typeof import("../../../extensions/telegram/src/send.js").sendTypingTelegram;
|
||||
start: (params: {
|
||||
|
||||
Reference in New Issue
Block a user