improve(nextcloud-talk): avoid room credential reads on sends (#128344)

* perf(nextcloud-talk): skip room credentials on sends

* fix(nextcloud-talk): use account inspection hook

---------

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Peter Steinberger
2026-08-23 13:07:45 -07:00
committed by GitHub
parent bd7fb1d597
commit 323a0efdd0
3 changed files with 66 additions and 16 deletions
+27 -16
View File
@@ -120,20 +120,6 @@ export function resolveNextcloudTalkAccount(params: {
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const secretResolution = resolveNextcloudTalkSecret(accountId, merged);
const apiCredentialResolution = resolveNextcloudTalkApiCredentialsResult({
apiUser: merged.apiUser,
apiPassword: merged.apiPassword,
apiPasswordFile: merged.apiPasswordFile,
configPath: `channels.nextcloud-talk.accounts.${accountId}.apiPasswordFile`,
});
const diagnostics = [
secretResolution.diagnostic,
apiCredentialResolution.status === "configured_unavailable"
? apiCredentialResolution.diagnostic
: undefined,
].filter((diagnostic): diagnostic is NextcloudTalkCredentialUnavailableDiagnostic =>
Boolean(diagnostic),
);
const baseUrl = merged.baseUrl?.trim()?.replace(/\/$/, "") ?? "";
debugAccounts("resolve", {
@@ -151,8 +137,9 @@ export function resolveNextcloudTalkAccount(params: {
secret: secretResolution.secret,
secretSource: secretResolution.source,
tokenStatus: secretResolution.status,
apiCredentialStatus: apiCredentialResolution.status,
...(diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {}),
...(secretResolution.diagnostic
? { credentialDiagnostics: [secretResolution.diagnostic] }
: {}),
config: merged,
} satisfies ResolvedNextcloudTalkAccount;
};
@@ -165,3 +152,27 @@ export function resolveNextcloudTalkAccount(params: {
resolveDefaultAccountId: () => resolveDefaultNextcloudTalkAccountId(params.cfg),
});
}
export function inspectNextcloudTalkAccount(params: {
cfg: CoreConfig;
accountId?: string | null;
}): ResolvedNextcloudTalkAccount {
const account = resolveNextcloudTalkAccount(params);
const apiCredentialResolution = resolveNextcloudTalkApiCredentialsResult({
apiUser: account.config.apiUser,
apiPassword: account.config.apiPassword,
apiPasswordFile: account.config.apiPasswordFile,
configPath: `channels.nextcloud-talk.accounts.${account.accountId}.apiPasswordFile`,
});
const credentialDiagnostics = [
...(account.credentialDiagnostics ?? []),
...(apiCredentialResolution.status === "configured_unavailable"
? [apiCredentialResolution.diagnostic]
: []),
];
return {
...account,
apiCredentialStatus: apiCredentialResolution.status,
...(credentialDiagnostics.length > 0 ? { credentialDiagnostics } : {}),
};
}
@@ -8,6 +8,7 @@ import {
import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
inspectNextcloudTalkAccount,
listNextcloudTalkAccountIds,
resolveDefaultNextcloudTalkAccountId,
resolveNextcloudTalkAccount,
@@ -23,6 +24,7 @@ export const nextcloudTalkConfigAdapter = createScopedChannelConfigAdapter<
sectionKey: "nextcloud-talk",
listAccountIds: listNextcloudTalkAccountIds,
resolveAccount: adaptScopedAccountAccessor(resolveNextcloudTalkAccount),
inspectAccount: adaptScopedAccountAccessor(inspectNextcloudTalkAccount),
defaultAccountId: resolveDefaultNextcloudTalkAccountId,
clearBaseFields: ["botSecret", "botSecretFile", "baseUrl", "name"],
resolveAllowFrom: (account) => account.config.allowFrom,
@@ -1,6 +1,10 @@
// Nextcloud Talk tests cover channel.status plugin behavior.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { nextcloudTalkPlugin } from "./channel.js";
import type { CoreConfig } from "./types.js";
describe("nextcloud-talk channel status", () => {
it("classifies room tokens as groups", () => {
@@ -32,4 +36,37 @@ describe("nextcloud-talk channel status", () => {
},
]);
});
it("keeps API credential inspection off runtime resolution", async () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nextcloud-talk-status-"));
const apiPasswordFile = path.join(directory, "api-password");
const cfg = {
channels: {
"nextcloud-talk": {
baseUrl: "https://cloud.example.com",
botSecret: "bot-secret",
apiUser: "bot",
apiPasswordFile,
},
},
} satisfies CoreConfig;
try {
const account = nextcloudTalkPlugin.config.resolveAccount(cfg, "default");
expect(account.apiCredentialStatus).toBeUndefined();
expect(account.credentialDiagnostics).toBeUndefined();
fs.writeFileSync(apiPasswordFile, "api-password\n", "utf8");
const inspected = (await nextcloudTalkPlugin.config.inspectAccount?.(
cfg,
"default",
)) as typeof account;
expect(nextcloudTalkPlugin.config.describeAccount?.(inspected, cfg)).toMatchObject({
configured: true,
apiCredentialStatus: "available",
});
} finally {
fs.rmSync(directory, { recursive: true, force: true });
}
});
});