@openclaw/nextcloud-talk 2026.7.2-beta.5 → 2026.8.1-beta.2

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-D6MTjGj3.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-CojgyJSq.js";
2
2
  export { nextcloudTalkPlugin };
@@ -1,6 +1,6 @@
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";
@@ -8,14 +8,15 @@ import { createAllowlistProviderRouteAllowlistWarningCollector, resolveScopeRequ
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
+ import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
13
14
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
14
15
  import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
15
16
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
16
17
  import { parseStrictNonNegativeInteger, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
17
18
  import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
18
- import { buildChannelInboundEventContext, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
19
+ import { resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
19
20
  import { bindIngressLifecycleToReplyOptions, createAccountStatusSink, createChannelIngressMonitor, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveChannelStreamingBlockEnabled, runPassiveAccountLifecycle } from "openclaw/plugin-sdk/channel-outbound";
20
21
  import { ssrfPolicyFromPrivateNetworkOptIn, ssrfPolicyFromPrivateNetworkOptIn as ssrfPolicyFromPrivateNetworkOptIn$1 } from "openclaw/plugin-sdk/ssrf-runtime";
21
22
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
@@ -33,12 +34,14 @@ import os from "node:os";
33
34
  import path from "node:path";
34
35
  import { migratePersistentDedupeLegacyJsonFile, resolvePersistentDedupePluginStateNamespace } from "openclaw/plugin-sdk/persistent-dedupe";
35
36
  import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
37
+ import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
36
38
  import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
37
39
  import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback } from "openclaw/plugin-sdk/channel-targets";
38
40
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
39
41
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
40
42
  import { createServer } from "node:http";
41
43
  import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
44
+ import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
42
45
  import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
43
46
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
44
47
  import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
@@ -78,10 +81,6 @@ function resolveNextcloudTalkApiCredentials(params) {
78
81
  }
79
82
  //#endregion
80
83
  //#region extensions/nextcloud-talk/src/accounts.ts
81
- function isTruthyEnvValue(value) {
82
- const normalized = normalizeLowercaseStringOrEmpty(value);
83
- return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
84
- }
85
84
  const debugAccounts = (...args) => {
86
85
  if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_NEXTCLOUD_TALK_ACCOUNTS)) console.warn("[nextcloud-talk:accounts]", ...args);
87
86
  };
@@ -397,6 +396,7 @@ dangerouslyAllowPrivateNetwork: z.boolean().optional() }).strict().optional();
397
396
  const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
398
397
  name: z.string().optional(),
399
398
  enabled: z.boolean().optional(),
399
+ configWrites: z.boolean().optional(),
400
400
  markdown: MarkdownConfigSchema,
401
401
  baseUrl: z.string().optional(),
402
402
  botSecret: buildSecretInputSchema().optional(),
@@ -595,10 +595,6 @@ function cacheRoomInfo(key, value) {
595
595
  roomCache.set(key, value);
596
596
  pruneMapToMaxSize(roomCache, ROOM_CACHE_MAX_ENTRIES);
597
597
  }
598
- function coerceRoomType(value) {
599
- if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
600
- return parseStrictPositiveInteger(value);
601
- }
602
598
  function resolveRoomKindFromType(type) {
603
599
  if (!type) return;
604
600
  if (type === 1 || type === 5 || type === 6) return "direct";
@@ -650,7 +646,7 @@ async function resolveNextcloudTalkRoomKind(params) {
650
646
  runtime?.log?.(`nextcloud-talk: room lookup failed (${response.status}) token=${roomToken}`);
651
647
  return;
652
648
  }
653
- const kind = resolveRoomKindFromType(coerceRoomType((await readProviderJsonResponse(response, "Nextcloud Talk room info failed")).ocs?.data?.type));
649
+ const kind = resolveRoomKindFromType(parseStrictPositiveInteger((await readProviderJsonResponse(response, "Nextcloud Talk room info failed")).ocs?.data?.type));
654
650
  cacheRoomInfo(key, {
655
651
  fetchedAt: Date.now(),
656
652
  kind
@@ -989,7 +985,7 @@ async function handleNextcloudTalkInbound(params) {
989
985
  const allowFrom = normalizeStringEntries(account.config.allowFrom);
990
986
  const outerGroupAllowFrom = account.config.groupAllowFrom?.length ? normalizeStringEntries(account.config.groupAllowFrom) : allowFrom;
991
987
  const roomAllowFrom = normalizeStringEntries(roomConfig?.allowFrom);
992
- const resolveAccess = async (wasMentioned) => await resolveStableChannelMessageIngress({
988
+ const resolveAccess = async (wasMentioned, contextBinding) => await resolveStableChannelMessageIngress({
993
989
  channelId: CHANNEL_ID,
994
990
  accountId: account.accountId,
995
991
  identity: {
@@ -1005,6 +1001,7 @@ async function handleNextcloudTalkInbound(params) {
1005
1001
  kind: isGroup ? "group" : "direct",
1006
1002
  id: isGroup ? roomToken : senderId
1007
1003
  },
1004
+ contextBinding,
1008
1005
  route: roomRoutes({
1009
1006
  isGroup,
1010
1007
  groupPolicy,
@@ -1092,11 +1089,6 @@ async function handleNextcloudTalkInbound(params) {
1092
1089
  }
1093
1090
  const mentionRegexes = core.channel.mentions.buildMentionRegexes(config);
1094
1091
  const wasMentioned = mentionRegexes.length ? core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) : false;
1095
- if (isGroup) access = await resolveAccess(wasMentioned);
1096
- if (isGroup && access.activationAccess.shouldSkip) {
1097
- runtime.log?.(`nextcloud-talk: drop room ${roomToken} (no mention)`);
1098
- return;
1099
- }
1100
1092
  const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
1101
1093
  cfg: config,
1102
1094
  channel: CHANNEL_ID,
@@ -1106,6 +1098,16 @@ async function handleNextcloudTalkInbound(params) {
1106
1098
  id: isGroup ? roomToken : senderId
1107
1099
  }
1108
1100
  });
1101
+ access = await resolveAccess(isGroup ? wasMentioned : void 0, {
1102
+ agentId: route.agentId,
1103
+ sessionKey: route.sessionKey,
1104
+ messageId: message.messageId,
1105
+ inboundEventKind: "user_request"
1106
+ });
1107
+ if (access.ingress.admission !== "dispatch") {
1108
+ runtime.log?.(isGroup && access.activationAccess.shouldSkip ? `nextcloud-talk: drop room ${roomToken} (no mention)` : `nextcloud-talk: drop ${isGroup ? "room" : "DM"} ${roomToken} (authorization changed)`);
1109
+ return;
1110
+ }
1109
1111
  const fromLabel = isGroup ? `room:${roomName || roomToken}` : senderName || `user:${senderId}`;
1110
1112
  const body = buildEnvelope({
1111
1113
  channel: "Nextcloud Talk",
@@ -1115,7 +1117,8 @@ async function handleNextcloudTalkInbound(params) {
1115
1117
  });
1116
1118
  const groupSystemPrompt = normalizeOptionalString(roomConfig?.systemPrompt);
1117
1119
  const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
1118
- const ctxPayload = buildChannelInboundEventContext({
1120
+ const ctxPayload = core.channel.inbound.buildContext({
1121
+ channelIngress: access,
1119
1122
  channel: CHANNEL_ID,
1120
1123
  accountId: route.accountId,
1121
1124
  messageId: message.messageId,
@@ -1127,7 +1130,7 @@ async function handleNextcloudTalkInbound(params) {
1127
1130
  },
1128
1131
  conversation: {
1129
1132
  kind: isGroup ? "group" : "direct",
1130
- id: roomToken,
1133
+ id: isGroup ? roomToken : senderId,
1131
1134
  label: fromLabel
1132
1135
  },
1133
1136
  route: {
@@ -1202,9 +1205,6 @@ var NextcloudTalkWebhookPayloadError = class extends Error {
1202
1205
  this.name = "NextcloudTalkWebhookPayloadError";
1203
1206
  }
1204
1207
  };
1205
- function isRecord(value) {
1206
- return typeof value === "object" && value !== null && !Array.isArray(value);
1207
- }
1208
1208
  function parseRawObject(rawEvent) {
1209
1209
  let parsed;
1210
1210
  try {
@@ -1268,6 +1268,8 @@ async function migrateNextcloudTalkLegacyReplayState(params) {
1268
1268
  //#region extensions/nextcloud-talk/src/monitor.ts
1269
1269
  const DEFAULT_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
1270
1270
  const PREAUTH_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
1271
+ const NEXTCLOUD_TALK_WEBHOOK_ACCEPTED_HEADER = "x-openclaw-delivery-accepted";
1272
+ const NEXTCLOUD_TALK_WEBHOOK_ACCEPTED_VALUE = "durable";
1271
1273
  const PREAUTH_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
1272
1274
  const HEALTH_PATH = "/healthz";
1273
1275
  const WEBHOOK_AUTH_RATE_LIMIT_SCOPE = "nextcloud-talk-webhook-auth";
@@ -1279,10 +1281,6 @@ const WEBHOOK_ERRORS = {
1279
1281
  payloadTooLarge: "Payload too large",
1280
1282
  internalServerError: "Internal server error"
1281
1283
  };
1282
- function formatError(err) {
1283
- if (err instanceof Error) return err.message;
1284
- return typeof err === "string" ? err : JSON.stringify(err);
1285
- }
1286
1284
  function writeJsonResponse(res, status, body) {
1287
1285
  if (body) {
1288
1286
  res.writeHead(status, { "Content-Type": "application/json" });
@@ -1376,7 +1374,7 @@ function createNextcloudTalkWebhookServer(opts) {
1376
1374
  clientIp,
1377
1375
  authRateLimiter: webhookAuthRateLimiter
1378
1376
  })) return;
1379
- await onWebhook(body);
1377
+ if (await onWebhook(body) === "accepted") res.setHeader(NEXTCLOUD_TALK_WEBHOOK_ACCEPTED_HEADER, NEXTCLOUD_TALK_WEBHOOK_ACCEPTED_VALUE);
1380
1378
  writeJsonResponse(res, 200);
1381
1379
  } catch (err) {
1382
1380
  if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
@@ -1391,7 +1389,7 @@ function createNextcloudTalkWebhookServer(opts) {
1391
1389
  writeWebhookError(res, 400, WEBHOOK_ERRORS.invalidPayloadFormat);
1392
1390
  return;
1393
1391
  }
1394
- const error = err instanceof Error ? err : new Error(formatError(err));
1392
+ const error = err instanceof Error ? err : new Error(formatErrorMessage(err));
1395
1393
  onError?.(error);
1396
1394
  writeWebhookError(res, 500, WEBHOOK_ERRORS.internalServerError);
1397
1395
  }
@@ -1438,11 +1436,6 @@ function createNextcloudTalkWebhookServer(opts) {
1438
1436
  //#endregion
1439
1437
  //#region extensions/nextcloud-talk/src/webhook-spool.ts
1440
1438
  const NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS = 500;
1441
- const NEXTCLOUD_TALK_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
1442
- const NEXTCLOUD_TALK_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
1443
- const NEXTCLOUD_TALK_INGRESS_COMPLETED_MAX_ENTRIES = 1e4;
1444
- const NEXTCLOUD_TALK_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
1445
- const NEXTCLOUD_TALK_INGRESS_FAILED_MAX_ENTRIES = 1e4;
1446
1439
  const NextcloudTalkWebhookPayloadSchema = z.object({
1447
1440
  type: z.enum([
1448
1441
  "Create",
@@ -1544,15 +1537,12 @@ function createNextcloudTalkWebhookSpool(options) {
1544
1537
  await options.deliver(message, lifecycle);
1545
1538
  },
1546
1539
  pollIntervalMs: options.pollIntervalMs ?? NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS,
1547
- waitForDeliveryIdleBeforeRepump: true,
1548
1540
  retention: {
1549
- pruneIntervalMs: NEXTCLOUD_TALK_INGRESS_PRUNE_INTERVAL_MS,
1550
- completedTtlMs: NEXTCLOUD_TALK_INGRESS_COMPLETED_TTL_MS,
1551
- completedMaxEntries: NEXTCLOUD_TALK_INGRESS_COMPLETED_MAX_ENTRIES,
1552
- failedTtlMs: NEXTCLOUD_TALK_INGRESS_FAILED_TTL_MS,
1553
- failedMaxEntries: NEXTCLOUD_TALK_INGRESS_FAILED_MAX_ENTRIES
1541
+ completedMaxEntries: 1e4,
1542
+ failedMaxEntries: 1e4
1554
1543
  },
1555
1544
  drain: {
1545
+ startLimit: 32,
1556
1546
  resolveNonRetryableFailure,
1557
1547
  ...options.adoptionStallTimeoutMs === void 0 ? {} : { adoptionStallTimeoutMs: options.adoptionStallTimeoutMs },
1558
1548
  onLog: (message) => options.runtime.log?.(`nextcloud-talk ${message}`)
@@ -1683,6 +1673,7 @@ async function monitorNextcloudTalkProvider(opts) {
1683
1673
  await stop();
1684
1674
  return { stop };
1685
1675
  }
1676
+ opts.statusSink?.(channelReadyPatch());
1686
1677
  const publicUrl = account.config.webhookPublicUrl ?? `http://${host === "0.0.0.0" ? "localhost" : host}:${port}${path}`;
1687
1678
  logger.info(`[nextcloud-talk:${account.accountId}] webhook listening on ${publicUrl}`);
1688
1679
  return { stop };
@@ -1785,6 +1776,11 @@ const nextcloudTalkMessageActions = {
1785
1776
  handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
1786
1777
  if (action === "send") throw new Error("Send should be handled by outbound, not actions handler.");
1787
1778
  if (action === "react") {
1779
+ const account = resolveNextcloudTalkAccount({
1780
+ cfg,
1781
+ accountId
1782
+ });
1783
+ if (!isAccountConfigured(account)) throw new Error(`Nextcloud Talk account "${account.accountId}" is disabled or not configured.`);
1788
1784
  const target = readStringParam(params, "to", {
1789
1785
  required: true,
1790
1786
  label: "to (room token)"
@@ -1863,9 +1859,6 @@ function resolveNextcloudTalkOutboundSessionRoute(params) {
1863
1859
  //#region extensions/nextcloud-talk/src/setup-core.ts
1864
1860
  const t$1 = createSetupTranslator$1();
1865
1861
  const channel$1 = "nextcloud-talk";
1866
- function readOptionalString(value) {
1867
- return typeof value === "string" && value.length > 0 ? value : void 0;
1868
- }
1869
1862
  function normalizeNextcloudTalkBaseUrl(value) {
1870
1863
  return value?.trim().replace(/\/+$/, "") ?? "";
1871
1864
  }
@@ -1930,46 +1923,6 @@ const nextcloudTalkDmPolicy = createChannelDmPolicy({
1930
1923
  applyPatch: ({ cfg, account, patch }) => setNextcloudTalkAccountConfig(cfg, account.accountId, patch),
1931
1924
  promptAllowFrom: promptNextcloudTalkAllowFromForAccount
1932
1925
  });
1933
- const nextcloudTalkSetupAdapter = {
1934
- singleAccountKeysToMove: ["rooms"],
1935
- resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
1936
- prepareAccountConfigInput: ({ input }) => {
1937
- const setupInput = input;
1938
- return {
1939
- ...setupInput,
1940
- baseUrl: setupInput.baseUrl ?? readOptionalString(setupInput.url),
1941
- secret: setupInput.secret ?? readOptionalString(setupInput.token) ?? readOptionalString(setupInput.password),
1942
- secretFile: setupInput.secretFile ?? readOptionalString(setupInput.tokenFile)
1943
- };
1944
- },
1945
- applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
1946
- cfg,
1947
- channelKey: channel$1,
1948
- accountId,
1949
- name
1950
- }),
1951
- validateInput: createSetupInputPresenceValidator({
1952
- defaultAccountOnlyEnvError: "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
1953
- validate: ({ input }) => {
1954
- const setupInput = input;
1955
- if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
1956
- if (!setupInput.baseUrl) return "Nextcloud Talk requires --base-url.";
1957
- return null;
1958
- }
1959
- }),
1960
- applyAccountConfig: ({ cfg, accountId, input }) => {
1961
- const setupInput = input;
1962
- return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
1963
- cfg,
1964
- channelKey: channel$1,
1965
- accountId,
1966
- name: setupInput.name
1967
- }), accountId, {
1968
- baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
1969
- ...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
1970
- }, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
1971
- }
1972
- };
1973
1926
  const nextcloudTalkSetupContract = defineChannelSetupContract({
1974
1927
  fields: {
1975
1928
  baseUrl: {
@@ -2031,10 +1984,53 @@ const nextcloudTalkSetupContract = defineChannelSetupContract({
2031
1984
  cli: {
2032
1985
  flags: "--use-env",
2033
1986
  description: "Use Nextcloud Talk environment credentials"
2034
- }
1987
+ },
1988
+ envVars: ["NEXTCLOUD_TALK_BOT_SECRET"]
2035
1989
  }
2036
1990
  },
2037
- legacyAdapter: nextcloudTalkSetupAdapter
1991
+ legacyAdapter: {
1992
+ singleAccountKeysToMove: ["rooms"],
1993
+ resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
1994
+ prepareAccountConfigInput: ({ input }) => {
1995
+ const setupInput = input;
1996
+ return {
1997
+ ...setupInput,
1998
+ baseUrl: setupInput.baseUrl ?? readNonEmptyStringPreservingWhitespace(setupInput.url),
1999
+ secret: setupInput.secret ?? readNonEmptyStringPreservingWhitespace(setupInput.token) ?? readNonEmptyStringPreservingWhitespace(setupInput.password),
2000
+ secretFile: setupInput.secretFile ?? readNonEmptyStringPreservingWhitespace(setupInput.tokenFile)
2001
+ };
2002
+ },
2003
+ applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
2004
+ cfg,
2005
+ channelKey: channel$1,
2006
+ accountId,
2007
+ name
2008
+ }),
2009
+ validateInput: createSetupInputPresenceValidator({
2010
+ defaultAccountOnlyEnvError: "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
2011
+ validate: ({ input }) => {
2012
+ const setupInput = input;
2013
+ if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
2014
+ const normalizedBaseUrl = normalizeNextcloudTalkBaseUrl(setupInput.baseUrl);
2015
+ if (!normalizedBaseUrl) return "Nextcloud Talk requires --base-url.";
2016
+ const baseUrlError = validateNextcloudTalkBaseUrl(normalizedBaseUrl);
2017
+ if (baseUrlError) return baseUrlError;
2018
+ return null;
2019
+ }
2020
+ }),
2021
+ applyAccountConfig: ({ cfg, accountId, input }) => {
2022
+ const setupInput = input;
2023
+ return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
2024
+ cfg,
2025
+ channelKey: channel$1,
2026
+ accountId,
2027
+ name: setupInput.name
2028
+ }), accountId, {
2029
+ baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
2030
+ ...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
2031
+ }, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
2032
+ }
2033
+ }
2038
2034
  });
2039
2035
  //#endregion
2040
2036
  //#region extensions/nextcloud-talk/src/setup-surface.ts
@@ -2238,6 +2234,7 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2238
2234
  "nc"
2239
2235
  ],
2240
2236
  normalizeTarget: normalizeNextcloudTalkMessagingTarget,
2237
+ inferTargetChatType: ({ to }) => normalizeNextcloudTalkMessagingTarget(to) ? "group" : void 0,
2241
2238
  resolveOutboundSessionRoute: (params) => resolveNextcloudTalkOutboundSessionRoute(params),
2242
2239
  targetResolver: {
2243
2240
  looksLikeId: looksLikeNextcloudTalkTargetId,
@@ -2248,7 +2245,6 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2248
2245
  secretTargetRegistryEntries,
2249
2246
  collectRuntimeConfigAssignments
2250
2247
  },
2251
- setup: nextcloudTalkSetupAdapter,
2252
2248
  setupContract: nextcloudTalkSetupContract,
2253
2249
  status: createComputedAccountStatusAdapter({
2254
2250
  defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID$2),
@@ -1,2 +1,2 @@
1
- import { t as nextcloudTalkPlugin } from "./channel-D6MTjGj3.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-CojgyJSq.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.5",
3
+ "version": "2026.8.1-beta.2",
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.5"
11
+ "openclaw": ">=2026.8.1-beta.2"
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.5"
130
+ "pluginApi": ">=2026.8.1-beta.2"
128
131
  },
129
132
  "build": {
130
- "openclawVersion": "2026.7.2-beta.5"
133
+ "openclawVersion": "2026.8.1-beta.2"
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 };