mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
fix: pass message routing context to send actions
This commit is contained in:
@@ -1,171 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CliDeps } from "../cli/outbound-send-deps.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { messageCommand } from "./message.js";
|
||||
|
||||
let testConfig: Record<string, unknown> = {};
|
||||
const resolveCommandConfigWithSecrets = vi.hoisted(() =>
|
||||
vi.fn(async ({ config }: { config: unknown }) => ({
|
||||
resolvedConfig: config,
|
||||
effectiveConfig: config,
|
||||
diagnostics: [] as string[],
|
||||
})),
|
||||
);
|
||||
const runMessageAction = vi.hoisted(() =>
|
||||
vi.fn(async () => ({
|
||||
kind: "send" as const,
|
||||
channel: "telegram" as const,
|
||||
action: "send" as const,
|
||||
to: "123456",
|
||||
handledBy: "core" as const,
|
||||
payload: { ok: true },
|
||||
dryRun: false,
|
||||
})),
|
||||
);
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: () => testConfig,
|
||||
}));
|
||||
|
||||
vi.mock("../cli/command-config-resolution.js", () => ({
|
||||
resolveCommandConfigWithSecrets,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/outbound/message-action-runner.js", () => ({
|
||||
runMessageAction,
|
||||
}));
|
||||
|
||||
describe("messageCommand agent routing", () => {
|
||||
beforeEach(() => {
|
||||
testConfig = {};
|
||||
resolveCommandConfigWithSecrets.mockClear();
|
||||
runMessageAction.mockClear();
|
||||
});
|
||||
|
||||
it("passes resolved command config and scoped secret targets to the outbound runner", async () => {
|
||||
const rawConfig = {
|
||||
channels: {
|
||||
telegram: {
|
||||
token: { $secret: "vault://telegram/token" },
|
||||
},
|
||||
},
|
||||
};
|
||||
const resolvedConfig = {
|
||||
channels: {
|
||||
telegram: {
|
||||
token: "12345:resolved-token",
|
||||
},
|
||||
},
|
||||
};
|
||||
testConfig = rawConfig;
|
||||
resolveCommandConfigWithSecrets.mockResolvedValueOnce({
|
||||
resolvedConfig,
|
||||
effectiveConfig: resolvedConfig,
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
await messageCommand(
|
||||
{
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
target: "123456",
|
||||
message: "hi",
|
||||
json: true,
|
||||
},
|
||||
{} as CliDeps,
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(resolveCommandConfigWithSecrets).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: rawConfig,
|
||||
commandName: "message",
|
||||
}),
|
||||
);
|
||||
const call = resolveCommandConfigWithSecrets.mock.calls[0]?.[0] as {
|
||||
targetIds?: Set<string>;
|
||||
};
|
||||
expect(call.targetIds).toBeInstanceOf(Set);
|
||||
expect([...(call.targetIds ?? [])].every((id) => id.startsWith("channels.telegram."))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(runMessageAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cfg: resolvedConfig,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("passes the resolved default agent id to the outbound runner", async () => {
|
||||
testConfig = {
|
||||
agents: {
|
||||
list: [{ id: "alpha" }, { id: "ops", default: true }],
|
||||
},
|
||||
};
|
||||
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
await messageCommand(
|
||||
{
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
target: "123456",
|
||||
message: "hi",
|
||||
json: true,
|
||||
},
|
||||
{} as CliDeps,
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(runMessageAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: "ops",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "defaults senderIsOwner to true for local message runs",
|
||||
opts: {},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "honors explicit senderIsOwner override",
|
||||
opts: { senderIsOwner: false },
|
||||
expected: false,
|
||||
},
|
||||
])("$name", async ({ opts, expected }) => {
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
await messageCommand(
|
||||
{
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
target: "123456",
|
||||
message: "hi",
|
||||
json: true,
|
||||
...opts,
|
||||
},
|
||||
{} as CliDeps,
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(runMessageAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
senderIsOwner: expected,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -167,9 +167,22 @@ const createTelegramSendPluginRegistration = () => ({
|
||||
label: "Telegram",
|
||||
actions: {
|
||||
describeMessageTool: () => ({ actions: ["send"] }),
|
||||
handleAction: (async ({ action, params, cfg, accountId }: ChannelActionParams) => {
|
||||
handleAction: (async ({
|
||||
action,
|
||||
params,
|
||||
cfg,
|
||||
accountId,
|
||||
agentId,
|
||||
senderIsOwner,
|
||||
}: ChannelActionParams) => {
|
||||
return await handleTelegramAction(
|
||||
{ action, to: params.to, accountId: accountId ?? undefined },
|
||||
{
|
||||
action,
|
||||
to: params.to,
|
||||
accountId: accountId ?? undefined,
|
||||
agentId,
|
||||
senderIsOwner,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}) as unknown as NonNullable<ChannelPlugin["actions"]>["handleAction"],
|
||||
@@ -185,9 +198,22 @@ const createTelegramPollPluginRegistration = () => ({
|
||||
label: "Telegram",
|
||||
actions: {
|
||||
describeMessageTool: () => ({ actions: ["poll"] }),
|
||||
handleAction: (async ({ action, params, cfg, accountId }: ChannelActionParams) => {
|
||||
handleAction: (async ({
|
||||
action,
|
||||
params,
|
||||
cfg,
|
||||
accountId,
|
||||
agentId,
|
||||
senderIsOwner,
|
||||
}: ChannelActionParams) => {
|
||||
return await handleTelegramAction(
|
||||
{ action, to: params.to, accountId: accountId ?? undefined },
|
||||
{
|
||||
action,
|
||||
to: params.to,
|
||||
accountId: accountId ?? undefined,
|
||||
agentId,
|
||||
senderIsOwner,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}) as unknown as NonNullable<ChannelPlugin["actions"]>["handleAction"],
|
||||
@@ -294,6 +320,19 @@ describe("messageCommand", () => {
|
||||
}),
|
||||
);
|
||||
expect(sendText.mock.calls[0]?.[0]?.cfg).not.toBe(rawConfig);
|
||||
expect(resolveCommandConfigWithSecrets).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: rawConfig,
|
||||
commandName: "message",
|
||||
}),
|
||||
);
|
||||
const call = resolveCommandConfigWithSecrets.mock.calls[0]?.[0] as {
|
||||
targetIds?: Set<string>;
|
||||
};
|
||||
expect(call.targetIds).toBeInstanceOf(Set);
|
||||
expect([...(call.targetIds ?? [])].every((id) => id.startsWith("channels.telegram."))).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps local-fallback resolved cfg in outbound adapter sends", async () => {
|
||||
@@ -330,6 +369,11 @@ describe("messageCommand", () => {
|
||||
|
||||
it("defaults channel when only one configured", async () => {
|
||||
process.env.TELEGRAM_BOT_TOKEN = "token-abc";
|
||||
testConfig = {
|
||||
agents: {
|
||||
list: [{ id: "alpha" }, { id: "ops", default: true }],
|
||||
},
|
||||
};
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
@@ -346,7 +390,13 @@ describe("messageCommand", () => {
|
||||
deps,
|
||||
runtime,
|
||||
);
|
||||
expect(handleTelegramAction).toHaveBeenCalled();
|
||||
expect(handleTelegramAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: "ops",
|
||||
senderIsOwner: true,
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("defaults channel from the auto-enabled config snapshot when only one channel becomes configured", async () => {
|
||||
@@ -500,6 +550,7 @@ describe("messageCommand", () => {
|
||||
pollQuestion: "Ship it?",
|
||||
pollOption: ["Yes", "No"],
|
||||
pollDurationSeconds: 120,
|
||||
senderIsOwner: false,
|
||||
},
|
||||
deps,
|
||||
runtime,
|
||||
@@ -508,6 +559,7 @@ describe("messageCommand", () => {
|
||||
expect.objectContaining({
|
||||
action: "poll",
|
||||
to: "123456789",
|
||||
senderIsOwner: false,
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { resolveAgentModelPrimaryValue } from "../../../config/model-input.js";
|
||||
import { applyNonInteractiveAuthChoice } from "./auth-choice.js";
|
||||
|
||||
const applyNonInteractivePluginProviderChoice = vi.hoisted(() => vi.fn(async () => undefined));
|
||||
@@ -99,7 +100,9 @@ describe("applyNonInteractiveAuthChoice", () => {
|
||||
provider: "default",
|
||||
id: "CUSTOM_API_KEY",
|
||||
});
|
||||
expect(result?.agents?.defaults?.model?.primary).toBe("custom-models-custom-local/local-large");
|
||||
expect(resolveAgentModelPrimaryValue(result?.agents?.defaults?.model)).toBe(
|
||||
"custom-models-custom-local/local-large",
|
||||
);
|
||||
expect(resolveNonInteractiveApiKey).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "custom-models-custom-local",
|
||||
|
||||
@@ -598,6 +598,8 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
|
||||
requesterSenderE164: input.requesterSenderE164 ?? undefined,
|
||||
mediaAccess: ctx.mediaAccess,
|
||||
accountId: accountId ?? undefined,
|
||||
senderIsOwner: input.senderIsOwner,
|
||||
sessionId: input.sessionId,
|
||||
gateway,
|
||||
toolContext: input.toolContext,
|
||||
deps: input.deps,
|
||||
@@ -639,7 +641,7 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
|
||||
}
|
||||
|
||||
async function handlePollAction(ctx: ResolvedActionContext): Promise<MessageActionRunResult> {
|
||||
const { cfg, params, channel, accountId, dryRun, gateway, input, abortSignal } = ctx;
|
||||
const { cfg, params, channel, accountId, dryRun, gateway, input, agentId, abortSignal } = ctx;
|
||||
throwIfAborted(abortSignal);
|
||||
const action: ChannelMessageActionName = "poll";
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
@@ -672,6 +674,11 @@ async function handlePollAction(ctx: ResolvedActionContext): Promise<MessageActi
|
||||
channel,
|
||||
params,
|
||||
accountId: accountId ?? undefined,
|
||||
agentId,
|
||||
requesterSenderId: input.requesterSenderId ?? undefined,
|
||||
senderIsOwner: input.senderIsOwner,
|
||||
sessionKey: input.sessionKey,
|
||||
sessionId: input.sessionId,
|
||||
gateway,
|
||||
toolContext: input.toolContext,
|
||||
dryRun,
|
||||
|
||||
@@ -40,6 +40,8 @@ export type OutboundSendContext = {
|
||||
mediaAccess?: OutboundMediaAccess;
|
||||
mediaReadFile?: OutboundMediaReadFile;
|
||||
accountId?: string | null;
|
||||
senderIsOwner?: boolean;
|
||||
sessionId?: string;
|
||||
gateway?: OutboundGatewayContext;
|
||||
toolContext?: ChannelThreadingToolContext;
|
||||
deps?: OutboundSendDeps;
|
||||
@@ -100,6 +102,11 @@ async function tryHandleWithPluginAction(params: {
|
||||
mediaLocalRoots: mediaAccess.localRoots,
|
||||
mediaReadFile: mediaAccess.readFile,
|
||||
accountId: params.ctx.accountId ?? undefined,
|
||||
requesterSenderId: params.ctx.requesterSenderId,
|
||||
senderIsOwner: params.ctx.senderIsOwner,
|
||||
sessionKey: params.ctx.sessionKey,
|
||||
sessionId: params.ctx.sessionId,
|
||||
agentId: params.ctx.agentId,
|
||||
gateway: params.ctx.gateway,
|
||||
toolContext: params.ctx.toolContext,
|
||||
dryRun: params.ctx.dryRun,
|
||||
|
||||
Reference in New Issue
Block a user