@openclaw/nextcloud-talk 2026.2.21 → 2026.2.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.2.21",
3
+ "version": "2026.2.23",
4
4
  "description": "OpenClaw Nextcloud Talk channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
package/src/channel.ts CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  deleteAccountFromConfigSection,
6
6
  formatPairingApproveHint,
7
7
  normalizeAccountId,
8
+ resolveAllowlistProviderRuntimeGroupPolicy,
9
+ resolveDefaultGroupPolicy,
8
10
  setAccountEnabledInConfigSection,
9
11
  type ChannelPlugin,
10
12
  type OpenClawConfig,
@@ -128,8 +130,13 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
128
130
  };
129
131
  },
130
132
  collectWarnings: ({ account, cfg }) => {
131
- const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
132
- const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
133
+ const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
134
+ const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
135
+ providerConfigPresent:
136
+ (cfg.channels as Record<string, unknown> | undefined)?.["nextcloud-talk"] !== undefined,
137
+ groupPolicy: account.config.groupPolicy,
138
+ defaultGroupPolicy,
139
+ });
133
140
  if (groupPolicy !== "open") {
134
141
  return [];
135
142
  }
@@ -4,6 +4,7 @@ import {
4
4
  DmPolicySchema,
5
5
  GroupPolicySchema,
6
6
  MarkdownConfigSchema,
7
+ ReplyRuntimeConfigSchemaShape,
7
8
  ToolPolicySchema,
8
9
  requireOpenAllowFrom,
9
10
  } from "openclaw/plugin-sdk";
@@ -40,15 +41,7 @@ export const NextcloudTalkAccountSchemaBase = z
40
41
  groupAllowFrom: z.array(z.string()).optional(),
41
42
  groupPolicy: GroupPolicySchema.optional().default("allowlist"),
42
43
  rooms: z.record(z.string(), NextcloudTalkRoomSchema.optional()).optional(),
43
- historyLimit: z.number().int().min(0).optional(),
44
- dmHistoryLimit: z.number().int().min(0).optional(),
45
- dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
46
- textChunkLimit: z.number().int().positive().optional(),
47
- chunkMode: z.enum(["length", "newline"]).optional(),
48
- blockStreaming: z.boolean().optional(),
49
- blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
50
- responsePrefix: z.string().optional(),
51
- mediaMaxMb: z.number().positive().optional(),
44
+ ...ReplyRuntimeConfigSchemaShape,
52
45
  })
53
46
  .strict();
54
47
 
package/src/inbound.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import {
2
+ GROUP_POLICY_BLOCKED_LABEL,
3
+ createNormalizedOutboundDeliverer,
2
4
  createReplyPrefixOptions,
5
+ formatTextWithAttachmentLinks,
3
6
  logInboundDrop,
4
7
  resolveControlCommandGate,
8
+ resolveOutboundMediaUrls,
9
+ resolveAllowlistProviderRuntimeGroupPolicy,
10
+ resolveDefaultGroupPolicy,
11
+ warnMissingProviderGroupPolicyFallbackOnce,
12
+ type OutboundReplyPayload,
5
13
  type OpenClawConfig,
6
14
  type RuntimeEnv,
7
15
  } from "openclaw/plugin-sdk";
@@ -22,32 +30,17 @@ import type { CoreConfig, GroupPolicy, NextcloudTalkInboundMessage } from "./typ
22
30
  const CHANNEL_ID = "nextcloud-talk" as const;
23
31
 
24
32
  async function deliverNextcloudTalkReply(params: {
25
- payload: { text?: string; mediaUrls?: string[]; mediaUrl?: string; replyToId?: string };
33
+ payload: OutboundReplyPayload;
26
34
  roomToken: string;
27
35
  accountId: string;
28
36
  statusSink?: (patch: { lastOutboundAt?: number }) => void;
29
37
  }): Promise<void> {
30
38
  const { payload, roomToken, accountId, statusSink } = params;
31
- const text = payload.text ?? "";
32
- const mediaList = payload.mediaUrls?.length
33
- ? payload.mediaUrls
34
- : payload.mediaUrl
35
- ? [payload.mediaUrl]
36
- : [];
37
-
38
- if (!text.trim() && mediaList.length === 0) {
39
+ const combined = formatTextWithAttachmentLinks(payload.text, resolveOutboundMediaUrls(payload));
40
+ if (!combined) {
39
41
  return;
40
42
  }
41
43
 
42
- const mediaBlock = mediaList.length
43
- ? mediaList.map((url) => `Attachment: ${url}`).join("\n")
44
- : "";
45
- const combined = text.trim()
46
- ? mediaBlock
47
- ? `${text.trim()}\n\n${mediaBlock}`
48
- : text.trim()
49
- : mediaBlock;
50
-
51
44
  await sendMessageNextcloudTalk(roomToken, combined, {
52
45
  accountId,
53
46
  replyTo: payload.replyToId,
@@ -84,16 +77,29 @@ export async function handleNextcloudTalkInbound(params: {
84
77
  statusSink?.({ lastInboundAt: message.timestamp });
85
78
 
86
79
  const dmPolicy = account.config.dmPolicy ?? "pairing";
87
- const defaultGroupPolicy = (config.channels as Record<string, unknown> | undefined)?.defaults as
88
- | { groupPolicy?: string }
89
- | undefined;
90
- const groupPolicy = (account.config.groupPolicy ??
91
- defaultGroupPolicy?.groupPolicy ??
92
- "allowlist") as GroupPolicy;
80
+ const defaultGroupPolicy = resolveDefaultGroupPolicy(config as OpenClawConfig);
81
+ const { groupPolicy, providerMissingFallbackApplied } =
82
+ resolveAllowlistProviderRuntimeGroupPolicy({
83
+ providerConfigPresent:
84
+ ((config.channels as Record<string, unknown> | undefined)?.["nextcloud-talk"] ??
85
+ undefined) !== undefined,
86
+ groupPolicy: account.config.groupPolicy as GroupPolicy | undefined,
87
+ defaultGroupPolicy,
88
+ });
89
+ warnMissingProviderGroupPolicyFallbackOnce({
90
+ providerMissingFallbackApplied,
91
+ providerKey: "nextcloud-talk",
92
+ accountId: account.accountId,
93
+ blockedLabel: GROUP_POLICY_BLOCKED_LABEL.room,
94
+ log: (message) => runtime.log?.(message),
95
+ });
93
96
 
94
97
  const configAllowFrom = normalizeNextcloudTalkAllowlist(account.config.allowFrom);
95
98
  const configGroupAllowFrom = normalizeNextcloudTalkAllowlist(account.config.groupAllowFrom);
96
- const storeAllowFrom = await core.channel.pairing.readAllowFromStore(CHANNEL_ID).catch(() => []);
99
+ const storeAllowFrom =
100
+ dmPolicy === "allowlist"
101
+ ? []
102
+ : await core.channel.pairing.readAllowFromStore(CHANNEL_ID).catch(() => []);
97
103
  const storeAllowList = normalizeNextcloudTalkAllowlist(storeAllowFrom);
98
104
 
99
105
  const roomMatch = resolveNextcloudTalkRoomMatch({
@@ -301,25 +307,21 @@ export async function handleNextcloudTalkInbound(params: {
301
307
  channel: CHANNEL_ID,
302
308
  accountId: account.accountId,
303
309
  });
310
+ const deliverReply = createNormalizedOutboundDeliverer(async (payload) => {
311
+ await deliverNextcloudTalkReply({
312
+ payload,
313
+ roomToken,
314
+ accountId: account.accountId,
315
+ statusSink,
316
+ });
317
+ });
304
318
 
305
319
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
306
320
  ctx: ctxPayload,
307
321
  cfg: config as OpenClawConfig,
308
322
  dispatcherOptions: {
309
323
  ...prefixOptions,
310
- deliver: async (payload) => {
311
- await deliverNextcloudTalkReply({
312
- payload: payload as {
313
- text?: string;
314
- mediaUrls?: string[];
315
- mediaUrl?: string;
316
- replyToId?: string;
317
- },
318
- roomToken,
319
- accountId: account.accountId,
320
- statusSink,
321
- });
322
- },
324
+ deliver: deliverReply,
323
325
  onError: (err, info) => {
324
326
  runtime.error?.(`nextcloud-talk ${info.kind} reply failed: ${String(err)}`);
325
327
  },
package/src/monitor.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
2
  import {
3
+ createLoggerBackedRuntime,
3
4
  type RuntimeEnv,
4
5
  isRequestBodyLimitError,
5
6
  readRequestBodyWithLimit,
@@ -212,13 +213,12 @@ export async function monitorNextcloudTalkProvider(
212
213
  cfg,
213
214
  accountId: opts.accountId,
214
215
  });
215
- const runtime: RuntimeEnv = opts.runtime ?? {
216
- log: (...args: unknown[]) => core.logging.getChildLogger().info(args.map(String).join(" ")),
217
- error: (...args: unknown[]) => core.logging.getChildLogger().error(args.map(String).join(" ")),
218
- exit: () => {
219
- throw new Error("Runtime exit not available");
220
- },
221
- };
216
+ const runtime: RuntimeEnv =
217
+ opts.runtime ??
218
+ createLoggerBackedRuntime({
219
+ logger: core.logging.getChildLogger(),
220
+ exitError: () => new Error("Runtime exit not available"),
221
+ });
222
222
 
223
223
  if (!account.secret) {
224
224
  throw new Error(`Nextcloud Talk bot secret not configured for account "${account.accountId}"`);