@openclaw/nextcloud-talk 2026.7.2-beta.7 → 2026.8.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as nextcloudTalkPlugin } from "./channel-B9yquEb1.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-gYP-jQPr.js";
2
2
  export { nextcloudTalkPlugin };
@@ -1,22 +1,22 @@
1
1
  import { a as fetchWithSsrFGuard, c as resolveDefaultGroupPolicy, i as deliverFormattedTextWithAttachments, l as warnMissingProviderGroupPolicyFallbackOnce, o as logInboundDrop, r as createChannelPairingController, s as resolveAllowlistProviderRuntimeGroupPolicy, t as GROUP_POLICY_BLOCKED_LABEL, u as getNextcloudTalkRuntime } from "./runtime-api-BKsxTw4J.js";
2
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-fNhK1wag.js";
3
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DGgvfVqm.js";
2
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-Bda_xc6V.js";
3
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Bvm0PmP3.js";
4
4
  import { createAccountListHelpers, describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
5
  import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
6
6
  import { createLoggedPairingApprovalNotifier, createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
7
- import { createAllowlistProviderRouteAllowlistWarningCollector, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
7
+ import { createAllowlistProviderRouteAllowlistWarningCollector, createConditionalWarningCollector, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
8
8
  import { buildWebhookChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
9
9
  import { FormatCapabilityProfile, convertMarkdownTables, renderMarkdownWithMarkers, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
10
10
  import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
11
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
12
12
  import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, normalizeAccountId, resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
13
13
  import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
14
14
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
15
- import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
15
+ import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
16
16
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
17
17
  import { parseStrictNonNegativeInteger, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
18
18
  import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
19
- import { buildChannelInboundEventContext, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
19
+ import { resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
20
20
  import { bindIngressLifecycleToReplyOptions, createAccountStatusSink, createChannelIngressMonitor, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveChannelStreamingBlockEnabled, runPassiveAccountLifecycle } from "openclaw/plugin-sdk/channel-outbound";
21
21
  import { ssrfPolicyFromPrivateNetworkOptIn, ssrfPolicyFromPrivateNetworkOptIn as ssrfPolicyFromPrivateNetworkOptIn$1 } from "openclaw/plugin-sdk/ssrf-runtime";
22
22
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
@@ -34,12 +34,13 @@ import os from "node:os";
34
34
  import path from "node:path";
35
35
  import { migratePersistentDedupeLegacyJsonFile, resolvePersistentDedupePluginStateNamespace } from "openclaw/plugin-sdk/persistent-dedupe";
36
36
  import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
37
+ import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
37
38
  import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
38
39
  import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback } from "openclaw/plugin-sdk/channel-targets";
39
40
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
40
41
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
41
42
  import { createServer } from "node:http";
42
- import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
43
+ import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
43
44
  import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
44
45
  import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
45
46
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
@@ -96,18 +97,26 @@ function listNextcloudTalkAccountIds(cfg) {
96
97
  debugAccounts("listNextcloudTalkAccountIds", ids);
97
98
  return ids;
98
99
  }
99
- function resolveNextcloudTalkSecret(cfg, opts) {
100
- const resolvedAccountId = opts.accountId ?? resolveDefaultNextcloudTalkAccountId(cfg);
101
- const merged = mergeNextcloudTalkAccountConfig(cfg, resolvedAccountId);
100
+ function resolveNextcloudTalkSecret(accountId, merged) {
101
+ const configuredSecret = resolveSecretInputString({
102
+ value: merged.botSecret,
103
+ path: `channels.nextcloud-talk.accounts.${accountId}.botSecret`,
104
+ mode: "inspect"
105
+ });
106
+ if (configuredSecret.status === "configured_unavailable") return {
107
+ secret: "",
108
+ source: "config",
109
+ status: "configured_unavailable"
110
+ };
102
111
  const envSecret = normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET);
103
- if (envSecret && resolvedAccountId === DEFAULT_ACCOUNT_ID) return {
112
+ if (envSecret && accountId === DEFAULT_ACCOUNT_ID) return {
104
113
  secret: envSecret,
105
114
  source: "env",
106
115
  status: "available"
107
116
  };
108
117
  const botSecretFile = normalizeOptionalString(merged.botSecretFile);
109
118
  if (botSecretFile) {
110
- const result = tryReadSecretFileSync(botSecretFile, "Nextcloud Talk bot secret file", { rejectSymlink: true }, { configPath: `channels.nextcloud-talk.accounts.${resolvedAccountId}.botSecretFile` });
119
+ const result = tryReadSecretFileSync(botSecretFile, "Nextcloud Talk bot secret file", { rejectSymlink: true }, { configPath: `channels.nextcloud-talk.accounts.${accountId}.botSecretFile` });
111
120
  return result.status === "available" ? {
112
121
  secret: result.value,
113
122
  source: "secretFile",
@@ -119,12 +128,8 @@ function resolveNextcloudTalkSecret(cfg, opts) {
119
128
  diagnostic: result.diagnostic
120
129
  };
121
130
  }
122
- const inlineSecret = normalizeResolvedSecretInputString({
123
- value: merged.botSecret,
124
- path: `channels.nextcloud-talk.accounts.${resolvedAccountId}.botSecret`
125
- });
126
- if (inlineSecret) return {
127
- secret: inlineSecret,
131
+ if (configuredSecret.status === "available") return {
132
+ secret: configuredSecret.value,
128
133
  source: "config",
129
134
  status: "available"
130
135
  };
@@ -141,7 +146,7 @@ function resolveNextcloudTalkAccount(params) {
141
146
  const merged = mergeNextcloudTalkAccountConfig(params.cfg, accountId);
142
147
  const accountEnabled = merged.enabled !== false;
143
148
  const enabled = baseEnabled && accountEnabled;
144
- const secretResolution = resolveNextcloudTalkSecret(params.cfg, { accountId });
149
+ const secretResolution = resolveNextcloudTalkSecret(accountId, merged);
145
150
  const apiCredentialResolution = resolveNextcloudTalkApiCredentialsResult({
146
151
  apiUser: merged.apiUser,
147
152
  apiPassword: merged.apiPassword,
@@ -395,6 +400,7 @@ dangerouslyAllowPrivateNetwork: z.boolean().optional() }).strict().optional();
395
400
  const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
396
401
  name: z.string().optional(),
397
402
  enabled: z.boolean().optional(),
403
+ configWrites: z.boolean().optional(),
398
404
  markdown: MarkdownConfigSchema,
399
405
  baseUrl: z.string().optional(),
400
406
  botSecret: buildSecretInputSchema().optional(),
@@ -593,10 +599,6 @@ function cacheRoomInfo(key, value) {
593
599
  roomCache.set(key, value);
594
600
  pruneMapToMaxSize(roomCache, ROOM_CACHE_MAX_ENTRIES);
595
601
  }
596
- function coerceRoomType(value) {
597
- if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
598
- return parseStrictPositiveInteger(value);
599
- }
600
602
  function resolveRoomKindFromType(type) {
601
603
  if (!type) return;
602
604
  if (type === 1 || type === 5 || type === 6) return "direct";
@@ -648,7 +650,7 @@ async function resolveNextcloudTalkRoomKind(params) {
648
650
  runtime?.log?.(`nextcloud-talk: room lookup failed (${response.status}) token=${roomToken}`);
649
651
  return;
650
652
  }
651
- const kind = resolveRoomKindFromType(coerceRoomType((await readProviderJsonResponse(response, "Nextcloud Talk room info failed")).ocs?.data?.type));
653
+ const kind = resolveRoomKindFromType(parseStrictPositiveInteger((await readProviderJsonResponse(response, "Nextcloud Talk room info failed")).ocs?.data?.type));
652
654
  cacheRoomInfo(key, {
653
655
  fetchedAt: Date.now(),
654
656
  kind
@@ -987,7 +989,7 @@ async function handleNextcloudTalkInbound(params) {
987
989
  const allowFrom = normalizeStringEntries(account.config.allowFrom);
988
990
  const outerGroupAllowFrom = account.config.groupAllowFrom?.length ? normalizeStringEntries(account.config.groupAllowFrom) : allowFrom;
989
991
  const roomAllowFrom = normalizeStringEntries(roomConfig?.allowFrom);
990
- const resolveAccess = async (wasMentioned) => await resolveStableChannelMessageIngress({
992
+ const resolveAccess = async (wasMentioned, contextBinding) => await resolveStableChannelMessageIngress({
991
993
  channelId: CHANNEL_ID,
992
994
  accountId: account.accountId,
993
995
  identity: {
@@ -1003,6 +1005,7 @@ async function handleNextcloudTalkInbound(params) {
1003
1005
  kind: isGroup ? "group" : "direct",
1004
1006
  id: isGroup ? roomToken : senderId
1005
1007
  },
1008
+ contextBinding,
1006
1009
  route: roomRoutes({
1007
1010
  isGroup,
1008
1011
  groupPolicy,
@@ -1090,11 +1093,6 @@ async function handleNextcloudTalkInbound(params) {
1090
1093
  }
1091
1094
  const mentionRegexes = core.channel.mentions.buildMentionRegexes(config);
1092
1095
  const wasMentioned = mentionRegexes.length ? core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) : false;
1093
- if (isGroup) access = await resolveAccess(wasMentioned);
1094
- if (isGroup && access.activationAccess.shouldSkip) {
1095
- runtime.log?.(`nextcloud-talk: drop room ${roomToken} (no mention)`);
1096
- return;
1097
- }
1098
1096
  const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
1099
1097
  cfg: config,
1100
1098
  channel: CHANNEL_ID,
@@ -1104,6 +1102,16 @@ async function handleNextcloudTalkInbound(params) {
1104
1102
  id: isGroup ? roomToken : senderId
1105
1103
  }
1106
1104
  });
1105
+ access = await resolveAccess(isGroup ? wasMentioned : void 0, {
1106
+ agentId: route.agentId,
1107
+ sessionKey: route.sessionKey,
1108
+ messageId: message.messageId,
1109
+ inboundEventKind: "user_request"
1110
+ });
1111
+ if (access.ingress.admission !== "dispatch") {
1112
+ runtime.log?.(isGroup && access.activationAccess.shouldSkip ? `nextcloud-talk: drop room ${roomToken} (no mention)` : `nextcloud-talk: drop ${isGroup ? "room" : "DM"} ${roomToken} (authorization changed)`);
1113
+ return;
1114
+ }
1107
1115
  const fromLabel = isGroup ? `room:${roomName || roomToken}` : senderName || `user:${senderId}`;
1108
1116
  const body = buildEnvelope({
1109
1117
  channel: "Nextcloud Talk",
@@ -1113,7 +1121,8 @@ async function handleNextcloudTalkInbound(params) {
1113
1121
  });
1114
1122
  const groupSystemPrompt = normalizeOptionalString(roomConfig?.systemPrompt);
1115
1123
  const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
1116
- const ctxPayload = buildChannelInboundEventContext({
1124
+ const ctxPayload = core.channel.inbound.buildContext({
1125
+ channelIngress: access,
1117
1126
  channel: CHANNEL_ID,
1118
1127
  accountId: route.accountId,
1119
1128
  messageId: message.messageId,
@@ -1125,7 +1134,7 @@ async function handleNextcloudTalkInbound(params) {
1125
1134
  },
1126
1135
  conversation: {
1127
1136
  kind: isGroup ? "group" : "direct",
1128
- id: roomToken,
1137
+ id: isGroup ? roomToken : senderId,
1129
1138
  label: fromLabel
1130
1139
  },
1131
1140
  route: {
@@ -1276,10 +1285,6 @@ const WEBHOOK_ERRORS = {
1276
1285
  payloadTooLarge: "Payload too large",
1277
1286
  internalServerError: "Internal server error"
1278
1287
  };
1279
- function formatError(err) {
1280
- if (err instanceof Error) return err.message;
1281
- return typeof err === "string" ? err : JSON.stringify(err);
1282
- }
1283
1288
  function writeJsonResponse(res, status, body) {
1284
1289
  if (body) {
1285
1290
  res.writeHead(status, { "Content-Type": "application/json" });
@@ -1351,7 +1356,7 @@ function createNextcloudTalkWebhookServer(opts) {
1351
1356
  res.end();
1352
1357
  return;
1353
1358
  }
1354
- const clientIp = req.socket.remoteAddress ?? "unknown";
1359
+ const clientIp = resolveRequestClientIp(req, opts.trustedProxies, opts.allowRealIpFallback) ?? req.socket.remoteAddress ?? "unknown";
1355
1360
  if (!webhookAuthRateLimiter.check(clientIp, WEBHOOK_AUTH_RATE_LIMIT_SCOPE).allowed) {
1356
1361
  res.writeHead(429);
1357
1362
  res.end("Too Many Requests");
@@ -1388,7 +1393,7 @@ function createNextcloudTalkWebhookServer(opts) {
1388
1393
  writeWebhookError(res, 400, WEBHOOK_ERRORS.invalidPayloadFormat);
1389
1394
  return;
1390
1395
  }
1391
- const error = err instanceof Error ? err : new Error(formatError(err));
1396
+ const error = err instanceof Error ? err : new Error(formatErrorMessage(err));
1392
1397
  onError?.(error);
1393
1398
  writeWebhookError(res, 500, WEBHOOK_ERRORS.internalServerError);
1394
1399
  }
@@ -1536,12 +1541,12 @@ function createNextcloudTalkWebhookSpool(options) {
1536
1541
  await options.deliver(message, lifecycle);
1537
1542
  },
1538
1543
  pollIntervalMs: options.pollIntervalMs ?? NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS,
1539
- waitForDeliveryIdleBeforeRepump: true,
1540
1544
  retention: {
1541
1545
  completedMaxEntries: 1e4,
1542
1546
  failedMaxEntries: 1e4
1543
1547
  },
1544
1548
  drain: {
1549
+ startLimit: 32,
1545
1550
  resolveNonRetryableFailure,
1546
1551
  ...options.adoptionStallTimeoutMs === void 0 ? {} : { adoptionStallTimeoutMs: options.adoptionStallTimeoutMs },
1547
1552
  onLog: (message) => options.runtime.log?.(`nextcloud-talk ${message}`)
@@ -1646,6 +1651,8 @@ async function monitorNextcloudTalkProvider(opts) {
1646
1651
  onError: (error) => {
1647
1652
  logger.error(`[nextcloud-talk:${account.accountId}] webhook error: ${error.message}`);
1648
1653
  },
1654
+ trustedProxies: cfg.gateway?.trustedProxies,
1655
+ allowRealIpFallback: cfg.gateway?.allowRealIpFallback,
1649
1656
  abortSignal: opts.abortSignal
1650
1657
  });
1651
1658
  let stopPromise;
@@ -1672,6 +1679,7 @@ async function monitorNextcloudTalkProvider(opts) {
1672
1679
  await stop();
1673
1680
  return { stop };
1674
1681
  }
1682
+ opts.statusSink?.(channelReadyPatch());
1675
1683
  const publicUrl = account.config.webhookPublicUrl ?? `http://${host === "0.0.0.0" ? "localhost" : host}:${port}${path}`;
1676
1684
  logger.info(`[nextcloud-talk:${account.accountId}] webhook listening on ${publicUrl}`);
1677
1685
  return { stop };
@@ -1857,9 +1865,6 @@ function resolveNextcloudTalkOutboundSessionRoute(params) {
1857
1865
  //#region extensions/nextcloud-talk/src/setup-core.ts
1858
1866
  const t$1 = createSetupTranslator$1();
1859
1867
  const channel$1 = "nextcloud-talk";
1860
- function readOptionalString(value) {
1861
- return typeof value === "string" && value.length > 0 ? value : void 0;
1862
- }
1863
1868
  function normalizeNextcloudTalkBaseUrl(value) {
1864
1869
  return value?.trim().replace(/\/+$/, "") ?? "";
1865
1870
  }
@@ -1924,46 +1929,6 @@ const nextcloudTalkDmPolicy = createChannelDmPolicy({
1924
1929
  applyPatch: ({ cfg, account, patch }) => setNextcloudTalkAccountConfig(cfg, account.accountId, patch),
1925
1930
  promptAllowFrom: promptNextcloudTalkAllowFromForAccount
1926
1931
  });
1927
- const nextcloudTalkSetupAdapter = {
1928
- singleAccountKeysToMove: ["rooms"],
1929
- resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
1930
- prepareAccountConfigInput: ({ input }) => {
1931
- const setupInput = input;
1932
- return {
1933
- ...setupInput,
1934
- baseUrl: setupInput.baseUrl ?? readOptionalString(setupInput.url),
1935
- secret: setupInput.secret ?? readOptionalString(setupInput.token) ?? readOptionalString(setupInput.password),
1936
- secretFile: setupInput.secretFile ?? readOptionalString(setupInput.tokenFile)
1937
- };
1938
- },
1939
- applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
1940
- cfg,
1941
- channelKey: channel$1,
1942
- accountId,
1943
- name
1944
- }),
1945
- validateInput: createSetupInputPresenceValidator({
1946
- defaultAccountOnlyEnvError: "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
1947
- validate: ({ input }) => {
1948
- const setupInput = input;
1949
- if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
1950
- if (!setupInput.baseUrl) return "Nextcloud Talk requires --base-url.";
1951
- return null;
1952
- }
1953
- }),
1954
- applyAccountConfig: ({ cfg, accountId, input }) => {
1955
- const setupInput = input;
1956
- return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
1957
- cfg,
1958
- channelKey: channel$1,
1959
- accountId,
1960
- name: setupInput.name
1961
- }), accountId, {
1962
- baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
1963
- ...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
1964
- }, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
1965
- }
1966
- };
1967
1932
  const nextcloudTalkSetupContract = defineChannelSetupContract({
1968
1933
  fields: {
1969
1934
  baseUrl: {
@@ -2025,10 +1990,53 @@ const nextcloudTalkSetupContract = defineChannelSetupContract({
2025
1990
  cli: {
2026
1991
  flags: "--use-env",
2027
1992
  description: "Use Nextcloud Talk environment credentials"
2028
- }
1993
+ },
1994
+ envVars: ["NEXTCLOUD_TALK_BOT_SECRET"]
2029
1995
  }
2030
1996
  },
2031
- legacyAdapter: nextcloudTalkSetupAdapter
1997
+ legacyAdapter: {
1998
+ singleAccountKeysToMove: ["rooms"],
1999
+ resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
2000
+ prepareAccountConfigInput: ({ input }) => {
2001
+ const setupInput = input;
2002
+ return {
2003
+ ...setupInput,
2004
+ baseUrl: setupInput.baseUrl ?? readNonEmptyStringPreservingWhitespace(setupInput.url),
2005
+ secret: setupInput.secret ?? readNonEmptyStringPreservingWhitespace(setupInput.token) ?? readNonEmptyStringPreservingWhitespace(setupInput.password),
2006
+ secretFile: setupInput.secretFile ?? readNonEmptyStringPreservingWhitespace(setupInput.tokenFile)
2007
+ };
2008
+ },
2009
+ applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
2010
+ cfg,
2011
+ channelKey: channel$1,
2012
+ accountId,
2013
+ name
2014
+ }),
2015
+ validateInput: createSetupInputPresenceValidator({
2016
+ defaultAccountOnlyEnvError: "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
2017
+ validate: ({ input }) => {
2018
+ const setupInput = input;
2019
+ if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
2020
+ const normalizedBaseUrl = normalizeNextcloudTalkBaseUrl(setupInput.baseUrl);
2021
+ if (!normalizedBaseUrl) return "Nextcloud Talk requires --base-url.";
2022
+ const baseUrlError = validateNextcloudTalkBaseUrl(normalizedBaseUrl);
2023
+ if (baseUrlError) return baseUrlError;
2024
+ return null;
2025
+ }
2026
+ }),
2027
+ applyAccountConfig: ({ cfg, accountId, input }) => {
2028
+ const setupInput = input;
2029
+ return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
2030
+ cfg,
2031
+ channelKey: channel$1,
2032
+ accountId,
2033
+ name: setupInput.name
2034
+ }), accountId, {
2035
+ baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
2036
+ ...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
2037
+ }, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
2038
+ }
2039
+ }
2032
2040
  });
2033
2041
  //#endregion
2034
2042
  //#region extensions/nextcloud-talk/src/setup-surface.ts
@@ -2190,6 +2198,12 @@ const collectNextcloudTalkSecurityWarnings = createAllowlistProviderRouteAllowli
2190
2198
  groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom"
2191
2199
  }
2192
2200
  });
2201
+ const collectNextcloudTalkOpenGroupFindings = createConditionalWarningCollector.findings({
2202
+ collectWarnings: collectNextcloudTalkSecurityWarnings,
2203
+ checkId: "channels.nextcloud-talk.groups.open",
2204
+ severity: "critical",
2205
+ title: "Nextcloud Talk security warning"
2206
+ });
2193
2207
  const nextcloudTalkPlugin = createChatChannelPlugin({
2194
2208
  base: {
2195
2209
  id: "nextcloud-talk",
@@ -2232,6 +2246,7 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2232
2246
  "nc"
2233
2247
  ],
2234
2248
  normalizeTarget: normalizeNextcloudTalkMessagingTarget,
2249
+ inferTargetChatType: ({ to }) => normalizeNextcloudTalkMessagingTarget(to) ? "group" : void 0,
2235
2250
  resolveOutboundSessionRoute: (params) => resolveNextcloudTalkOutboundSessionRoute(params),
2236
2251
  targetResolver: {
2237
2252
  looksLikeId: looksLikeNextcloudTalkTargetId,
@@ -2242,7 +2257,6 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2242
2257
  secretTargetRegistryEntries,
2243
2258
  collectRuntimeConfigAssignments
2244
2259
  },
2245
- setup: nextcloudTalkSetupAdapter,
2246
2260
  setupContract: nextcloudTalkSetupContract,
2247
2261
  status: createComputedAccountStatusAdapter({
2248
2262
  defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID$2),
@@ -2286,7 +2300,7 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2286
2300
  } },
2287
2301
  security: {
2288
2302
  ...nextcloudTalkSecurityAdapter,
2289
- collectWarnings: collectNextcloudTalkSecurityWarnings
2303
+ collectWarnings: collectNextcloudTalkOpenGroupFindings
2290
2304
  },
2291
2305
  outbound: {
2292
2306
  base: {
@@ -1,2 +1,2 @@
1
- import { t as nextcloudTalkPlugin } from "./channel-B9yquEb1.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-gYP-jQPr.js";
2
2
  export { nextcloudTalkPlugin };
@@ -1,5 +1,4 @@
1
- import { createLegacyPrivateNetworkDoctorContract } from "openclaw/plugin-sdk/ssrf-runtime";
2
- import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor";
1
+ import { createLegacyPrivateNetworkDoctorContract, defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
3
2
  //#region extensions/nextcloud-talk/src/doctor-contract.ts
4
3
  const networkContract = createLegacyPrivateNetworkDoctorContract({ channelKey: "nextcloud-talk" });
5
4
  const streamingAliasMigration = defineChannelAliasMigration({
@@ -1,2 +1,2 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-fNhK1wag.js";
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-Bda_xc6V.js";
2
2
  export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -0,0 +1,15 @@
1
+ import { createSimpleChannelSecretContract } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
2
+ //#region extensions/nextcloud-talk/src/secret-contract.ts
3
+ const channelSecrets = createSimpleChannelSecretContract({
4
+ channelKey: "nextcloud-talk",
5
+ label: "Nextcloud Talk",
6
+ accountFields: ["apiPassword", "botSecret"],
7
+ channelFields: ["apiPassword", "botSecret"],
8
+ mode: {
9
+ kind: "surface-inheritance",
10
+ collectionFields: ["botSecret", "apiPassword"]
11
+ }
12
+ });
13
+ const { secretTargetRegistryEntries, collectRuntimeConfigAssignments } = channelSecrets;
14
+ //#endregion
15
+ export { collectRuntimeConfigAssignments as n, secretTargetRegistryEntries as r, channelSecrets as t };
@@ -1,2 +1,2 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-DGgvfVqm.js";
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-Bvm0PmP3.js";
2
2
  export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "id": "nextcloud-talk",
3
+ "doctorContract": {
4
+ "configRepair": true
5
+ },
3
6
  "name": "Nextcloud Talk",
4
7
  "description": "OpenClaw Nextcloud Talk channel plugin for conversations.",
5
8
  "icon": "https://cdn.simpleicons.org/nextcloud",
@@ -26,6 +29,9 @@
26
29
  "enabled": {
27
30
  "type": "boolean"
28
31
  },
32
+ "configWrites": {
33
+ "type": "boolean"
34
+ },
29
35
  "markdown": {
30
36
  "type": "object",
31
37
  "properties": {
@@ -74,6 +80,29 @@
74
80
  ],
75
81
  "additionalProperties": false
76
82
  },
83
+ {
84
+ "type": "object",
85
+ "properties": {
86
+ "source": {
87
+ "type": "string",
88
+ "const": "store"
89
+ },
90
+ "provider": {
91
+ "type": "string",
92
+ "pattern": "^[a-z][a-z0-9_-]{0,63}$"
93
+ },
94
+ "id": {
95
+ "type": "string",
96
+ "pattern": "^[A-Z][A-Z0-9_]{0,127}$"
97
+ }
98
+ },
99
+ "required": [
100
+ "source",
101
+ "provider",
102
+ "id"
103
+ ],
104
+ "additionalProperties": false
105
+ },
77
106
  {
78
107
  "type": "object",
79
108
  "properties": {
@@ -158,6 +187,29 @@
158
187
  ],
159
188
  "additionalProperties": false
160
189
  },
190
+ {
191
+ "type": "object",
192
+ "properties": {
193
+ "source": {
194
+ "type": "string",
195
+ "const": "store"
196
+ },
197
+ "provider": {
198
+ "type": "string",
199
+ "pattern": "^[a-z][a-z0-9_-]{0,63}$"
200
+ },
201
+ "id": {
202
+ "type": "string",
203
+ "pattern": "^[A-Z][A-Z0-9_]{0,127}$"
204
+ }
205
+ },
206
+ "required": [
207
+ "source",
208
+ "provider",
209
+ "id"
210
+ ],
211
+ "additionalProperties": false
212
+ },
161
213
  {
162
214
  "type": "object",
163
215
  "properties": {
@@ -424,6 +476,9 @@
424
476
  "enabled": {
425
477
  "type": "boolean"
426
478
  },
479
+ "configWrites": {
480
+ "type": "boolean"
481
+ },
427
482
  "markdown": {
428
483
  "type": "object",
429
484
  "properties": {
@@ -472,6 +527,29 @@
472
527
  ],
473
528
  "additionalProperties": false
474
529
  },
530
+ {
531
+ "type": "object",
532
+ "properties": {
533
+ "source": {
534
+ "type": "string",
535
+ "const": "store"
536
+ },
537
+ "provider": {
538
+ "type": "string",
539
+ "pattern": "^[a-z][a-z0-9_-]{0,63}$"
540
+ },
541
+ "id": {
542
+ "type": "string",
543
+ "pattern": "^[A-Z][A-Z0-9_]{0,127}$"
544
+ }
545
+ },
546
+ "required": [
547
+ "source",
548
+ "provider",
549
+ "id"
550
+ ],
551
+ "additionalProperties": false
552
+ },
475
553
  {
476
554
  "type": "object",
477
555
  "properties": {
@@ -556,6 +634,29 @@
556
634
  ],
557
635
  "additionalProperties": false
558
636
  },
637
+ {
638
+ "type": "object",
639
+ "properties": {
640
+ "source": {
641
+ "type": "string",
642
+ "const": "store"
643
+ },
644
+ "provider": {
645
+ "type": "string",
646
+ "pattern": "^[a-z][a-z0-9_-]{0,63}$"
647
+ },
648
+ "id": {
649
+ "type": "string",
650
+ "pattern": "^[A-Z][A-Z0-9_]{0,127}$"
651
+ }
652
+ },
653
+ "required": [
654
+ "source",
655
+ "provider",
656
+ "id"
657
+ ],
658
+ "additionalProperties": false
659
+ },
559
660
  {
560
661
  "type": "object",
561
662
  "properties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.7.2-beta.7",
3
+ "version": "2026.8.1-beta.3",
4
4
  "description": "OpenClaw Nextcloud Talk channel plugin for conversations.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "type": "module",
10
10
  "peerDependencies": {
11
- "openclaw": ">=2026.7.2-beta.7"
11
+ "openclaw": ">=2026.8.1-beta.3"
12
12
  },
13
13
  "peerDependenciesMeta": {
14
14
  "openclaw": {
@@ -113,7 +113,10 @@
113
113
  "cli": {
114
114
  "flags": "--use-env",
115
115
  "description": "Use Nextcloud Talk environment credentials"
116
- }
116
+ },
117
+ "envVars": [
118
+ "NEXTCLOUD_TALK_BOT_SECRET"
119
+ ]
117
120
  }
118
121
  ]
119
122
  }
@@ -124,10 +127,10 @@
124
127
  "minHostVersion": ">=2026.4.10"
125
128
  },
126
129
  "compat": {
127
- "pluginApi": ">=2026.7.2-beta.7"
130
+ "pluginApi": ">=2026.8.1-beta.3"
128
131
  },
129
132
  "build": {
130
- "openclawVersion": "2026.7.2-beta.7"
133
+ "openclawVersion": "2026.8.1-beta.3"
131
134
  },
132
135
  "release": {
133
136
  "publishToClawHub": true,
@@ -1,45 +0,0 @@
1
- import { collectConditionalChannelFieldAssignments, createChannelSecretTargetRegistryEntries, getChannelSurface, hasOwnProperty } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
2
- //#region extensions/nextcloud-talk/src/secret-contract.ts
3
- const secretTargetRegistryEntries = createChannelSecretTargetRegistryEntries({
4
- channelKey: "nextcloud-talk",
5
- account: ["apiPassword", "botSecret"],
6
- channel: ["apiPassword", "botSecret"]
7
- });
8
- function collectRuntimeConfigAssignments(params) {
9
- const resolved = getChannelSurface(params.config, "nextcloud-talk");
10
- if (!resolved) return;
11
- const { channel: nextcloudTalk, surface } = resolved;
12
- const inheritsField = (field) => ({ account, enabled }) => enabled && !hasOwnProperty(account, field);
13
- collectConditionalChannelFieldAssignments({
14
- channelKey: "nextcloud-talk",
15
- field: "botSecret",
16
- channel: nextcloudTalk,
17
- surface,
18
- defaults: params.defaults,
19
- context: params.context,
20
- topLevelActiveWithoutAccounts: true,
21
- topLevelInheritedAccountActive: inheritsField("botSecret"),
22
- accountActive: ({ enabled }) => enabled,
23
- topInactiveReason: "no enabled Nextcloud Talk surface inherits this top-level botSecret.",
24
- accountInactiveReason: "Nextcloud Talk account is disabled."
25
- });
26
- collectConditionalChannelFieldAssignments({
27
- channelKey: "nextcloud-talk",
28
- field: "apiPassword",
29
- channel: nextcloudTalk,
30
- surface,
31
- defaults: params.defaults,
32
- context: params.context,
33
- topLevelActiveWithoutAccounts: true,
34
- topLevelInheritedAccountActive: inheritsField("apiPassword"),
35
- accountActive: ({ enabled }) => enabled,
36
- topInactiveReason: "no enabled Nextcloud Talk surface inherits this top-level apiPassword.",
37
- accountInactiveReason: "Nextcloud Talk account is disabled."
38
- });
39
- }
40
- const channelSecrets = {
41
- secretTargetRegistryEntries,
42
- collectRuntimeConfigAssignments
43
- };
44
- //#endregion
45
- export { collectRuntimeConfigAssignments as n, secretTargetRegistryEntries as r, channelSecrets as t };