fix(channels): thread runtime config through sends

This commit is contained in:
Peter Steinberger
2026-04-22 06:14:12 +01:00
parent e1897419de
commit 95331e5cc5
125 changed files with 1461 additions and 804 deletions

View File

@@ -31,16 +31,18 @@ import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
const CHANNEL_ID = "nextcloud-talk" as const;
async function deliverNextcloudTalkReply(params: {
cfg: CoreConfig;
payload: OutboundReplyPayload;
roomToken: string;
accountId: string;
statusSink?: (patch: { lastOutboundAt?: number }) => void;
}): Promise<void> {
const { payload, roomToken, accountId, statusSink } = params;
const { cfg, payload, roomToken, accountId, statusSink } = params;
await deliverFormattedTextWithAttachments({
payload,
send: async ({ text, replyToId }) => {
await sendMessageNextcloudTalk(roomToken, text, {
cfg,
accountId,
replyTo: replyToId,
});
@@ -177,7 +179,10 @@ export async function handleNextcloudTalkInbound(params: {
senderIdLine: `Your Nextcloud user id: ${senderId}`,
meta: { name: senderName || undefined },
sendPairingReply: async (text) => {
await sendMessageNextcloudTalk(roomToken, text, { accountId: account.accountId });
await sendMessageNextcloudTalk(roomToken, text, {
cfg: config,
accountId: account.accountId,
});
statusSink?.({ lastOutboundAt: Date.now() });
},
onReplyError: (err) => {
@@ -291,6 +296,7 @@ export async function handleNextcloudTalkInbound(params: {
core,
deliver: async (payload) => {
await deliverNextcloudTalkReply({
cfg: config,
payload,
roomToken,
accountId: account.accountId,

View File

@@ -2,7 +2,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
createSendCfgThreadingRuntime,
expectProvidedCfgSkipsRuntimeLoad,
expectRuntimeCfgFallback,
} from "../../../test/helpers/plugins/send-config.js";
const hoisted = vi.hoisted(() => ({
@@ -25,6 +24,12 @@ vi.mock("./send.runtime.js", () => {
fetchWithSsrFGuard: hoisted.mockFetchGuard,
generateNextcloudTalkSignature: hoisted.generateNextcloudTalkSignature,
getNextcloudTalkRuntime: () => createSendCfgThreadingRuntime(hoisted),
requireRuntimeConfig: (cfg: unknown, context: string) => {
if (cfg) {
return cfg;
}
throw new Error(`${context} requires a resolved runtime config`);
},
resolveNextcloudTalkAccount: hoisted.resolveNextcloudTalkAccount,
resolveMarkdownTableMode: hoisted.resolveMarkdownTableMode,
ssrfPolicyFromPrivateNetworkOptIn: hoisted.ssrfPolicyFromPrivateNetworkOptIn,
@@ -133,21 +138,16 @@ describe("nextcloud-talk send cfg threading", () => {
});
});
it("falls back to runtime cfg for sendReaction when cfg is omitted", async () => {
const runtimeCfg = { source: "runtime" } as const;
hoisted.loadConfig.mockReturnValueOnce(runtimeCfg);
it("fails hard for sendReaction when cfg is omitted", async () => {
fetchMock.mockResolvedValueOnce(new Response("{}", { status: 200 }));
const result = await sendReactionNextcloudTalk("room:ops", "m-1", "👍", {
accountId: "default",
});
await expect(
sendReactionNextcloudTalk("room:ops", "m-1", "👍", {
accountId: "default",
} as never),
).rejects.toThrow("Nextcloud Talk send requires a resolved runtime config");
expect(result).toEqual({ ok: true });
expectRuntimeCfgFallback({
loadConfig: hoisted.loadConfig,
resolveAccount: hoisted.resolveNextcloudTalkAccount,
cfg: runtimeCfg,
accountId: "default",
});
expect(hoisted.loadConfig).not.toHaveBeenCalled();
expect(hoisted.resolveNextcloudTalkAccount).not.toHaveBeenCalled();
});
});

View File

@@ -1,4 +1,4 @@
export { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
export { requireRuntimeConfig, resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
export { ssrfPolicyFromPrivateNetworkOptIn } from "openclaw/plugin-sdk/ssrf-runtime";
export { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
export { fetchWithSsrFGuard } from "../runtime-api.js";

View File

@@ -4,6 +4,7 @@ import {
fetchWithSsrFGuard,
generateNextcloudTalkSignature,
getNextcloudTalkRuntime,
requireRuntimeConfig,
resolveMarkdownTableMode,
resolveNextcloudTalkAccount,
ssrfPolicyFromPrivateNetworkOptIn,
@@ -11,12 +12,12 @@ import {
import type { CoreConfig, NextcloudTalkSendResult } from "./types.js";
type NextcloudTalkSendOpts = {
cfg: CoreConfig;
baseUrl?: string;
secret?: string;
accountId?: string;
replyTo?: string;
verbose?: boolean;
cfg?: CoreConfig;
};
function resolveCredentials(
@@ -54,7 +55,7 @@ function resolveNextcloudTalkSendContext(opts: NextcloudTalkSendOpts): {
baseUrl: string;
secret: string;
} {
const cfg = (opts.cfg ?? getNextcloudTalkRuntime().config.loadConfig()) as CoreConfig;
const cfg = requireRuntimeConfig(opts.cfg, "Nextcloud Talk send") as CoreConfig;
const account = resolveNextcloudTalkAccount({
cfg,
accountId: opts.accountId,
@@ -83,7 +84,7 @@ function recordNextcloudTalkOutboundActivity(accountId: string): void {
export async function sendMessageNextcloudTalk(
to: string,
text: string,
opts: NextcloudTalkSendOpts = {},
opts: NextcloudTalkSendOpts,
): Promise<NextcloudTalkSendResult> {
const { cfg, account, baseUrl, secret } = resolveNextcloudTalkSendContext(opts);
const roomToken = normalizeRoomToken(to);
@@ -192,7 +193,7 @@ export async function sendReactionNextcloudTalk(
roomToken: string,
messageId: string,
reaction: string,
opts: Omit<NextcloudTalkSendOpts, "replyTo"> = {},
opts: Omit<NextcloudTalkSendOpts, "replyTo">,
): Promise<{ ok: true }> {
const { account, baseUrl, secret } = resolveNextcloudTalkSendContext(opts);
const normalizedToken = normalizeRoomToken(roomToken);