@openclaw/nextcloud-talk 2026.2.1 → 2026.2.6

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.1",
3
+ "version": "2026.2.6",
4
4
  "description": "OpenClaw Nextcloud Talk channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -47,6 +47,7 @@ export const NextcloudTalkAccountSchemaBase = z
47
47
  chunkMode: z.enum(["length", "newline"]).optional(),
48
48
  blockStreaming: z.boolean().optional(),
49
49
  blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
50
+ responsePrefix: z.string().optional(),
50
51
  mediaMaxMb: z.number().positive().optional(),
51
52
  })
52
53
  .strict();
package/src/inbound.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ createReplyPrefixOptions,
2
3
  logInboundDrop,
3
4
  resolveControlCommandGate,
4
5
  type OpenClawConfig,
@@ -121,7 +122,6 @@ export async function handleNextcloudTalkInbound(params: {
121
122
  const senderAllowedForCommands = resolveNextcloudTalkAllowlistMatch({
122
123
  allowFrom: isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom,
123
124
  senderId,
124
- senderName,
125
125
  }).allowed;
126
126
  const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config as OpenClawConfig);
127
127
  const commandGate = resolveControlCommandGate({
@@ -143,7 +143,6 @@ export async function handleNextcloudTalkInbound(params: {
143
143
  outerAllowFrom: effectiveGroupAllowFrom,
144
144
  innerAllowFrom: roomAllowFrom,
145
145
  senderId,
146
- senderName,
147
146
  });
148
147
  if (!groupAllow.allowed) {
149
148
  runtime.log?.(`nextcloud-talk: drop group sender ${senderId} (policy=${groupPolicy})`);
@@ -158,7 +157,6 @@ export async function handleNextcloudTalkInbound(params: {
158
157
  const dmAllowed = resolveNextcloudTalkAllowlistMatch({
159
158
  allowFrom: effectiveAllowFrom,
160
159
  senderId,
161
- senderName,
162
160
  }).allowed;
163
161
  if (!dmAllowed) {
164
162
  if (dmPolicy === "pairing") {
@@ -288,10 +286,18 @@ export async function handleNextcloudTalkInbound(params: {
288
286
  },
289
287
  });
290
288
 
289
+ const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
290
+ cfg: config as OpenClawConfig,
291
+ agentId: route.agentId,
292
+ channel: CHANNEL_ID,
293
+ accountId: account.accountId,
294
+ });
295
+
291
296
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
292
297
  ctx: ctxPayload,
293
298
  cfg: config as OpenClawConfig,
294
299
  dispatcherOptions: {
300
+ ...prefixOptions,
295
301
  deliver: async (payload) => {
296
302
  await deliverNextcloudTalkReply({
297
303
  payload: payload as {
@@ -311,6 +317,7 @@ export async function handleNextcloudTalkInbound(params: {
311
317
  },
312
318
  replyOptions: {
313
319
  skillFilter: roomConfig?.skills,
320
+ onModelSelected,
314
321
  disableBlockStreaming:
315
322
  typeof account.config.blockStreaming === "boolean"
316
323
  ? !account.config.blockStreaming
package/src/monitor.ts CHANGED
@@ -54,7 +54,7 @@ function payloadToInboundMessage(
54
54
  roomToken: payload.target.id,
55
55
  roomName: payload.target.name,
56
56
  senderId: payload.actor.id,
57
- senderName: payload.actor.name,
57
+ senderName: payload.actor.name ?? "",
58
58
  text: payload.object.content || payload.object.name || "",
59
59
  mediaType: payload.object.mediaType || "text/plain",
60
60
  timestamp: Date.now(),
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveNextcloudTalkAllowlistMatch } from "./policy.js";
3
+
4
+ describe("nextcloud-talk policy", () => {
5
+ describe("resolveNextcloudTalkAllowlistMatch", () => {
6
+ it("allows wildcard", () => {
7
+ expect(
8
+ resolveNextcloudTalkAllowlistMatch({
9
+ allowFrom: ["*"],
10
+ senderId: "user-id",
11
+ }).allowed,
12
+ ).toBe(true);
13
+ });
14
+
15
+ it("allows sender id match with normalization", () => {
16
+ expect(
17
+ resolveNextcloudTalkAllowlistMatch({
18
+ allowFrom: ["nc:User-Id"],
19
+ senderId: "user-id",
20
+ }),
21
+ ).toEqual({ allowed: true, matchKey: "user-id", matchSource: "id" });
22
+ });
23
+
24
+ it("blocks when sender id does not match", () => {
25
+ expect(
26
+ resolveNextcloudTalkAllowlistMatch({
27
+ allowFrom: ["allowed"],
28
+ senderId: "other",
29
+ }).allowed,
30
+ ).toBe(false);
31
+ });
32
+ });
33
+ });
package/src/policy.ts CHANGED
@@ -29,8 +29,7 @@ export function normalizeNextcloudTalkAllowlist(
29
29
  export function resolveNextcloudTalkAllowlistMatch(params: {
30
30
  allowFrom: Array<string | number> | undefined;
31
31
  senderId: string;
32
- senderName?: string | null;
33
- }): AllowlistMatch<"wildcard" | "id" | "name"> {
32
+ }): AllowlistMatch<"wildcard" | "id"> {
34
33
  const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
35
34
  if (allowFrom.length === 0) {
36
35
  return { allowed: false };
@@ -42,10 +41,6 @@ export function resolveNextcloudTalkAllowlistMatch(params: {
42
41
  if (allowFrom.includes(senderId)) {
43
42
  return { allowed: true, matchKey: senderId, matchSource: "id" };
44
43
  }
45
- const senderName = params.senderName ? normalizeAllowEntry(params.senderName) : "";
46
- if (senderName && allowFrom.includes(senderName)) {
47
- return { allowed: true, matchKey: senderName, matchSource: "name" };
48
- }
49
44
  return { allowed: false };
50
45
  }
51
46
 
@@ -132,7 +127,6 @@ export function resolveNextcloudTalkGroupAllow(params: {
132
127
  outerAllowFrom: Array<string | number> | undefined;
133
128
  innerAllowFrom: Array<string | number> | undefined;
134
129
  senderId: string;
135
- senderName?: string | null;
136
130
  }): { allowed: boolean; outerMatch: AllowlistMatch; innerMatch: AllowlistMatch } {
137
131
  if (params.groupPolicy === "disabled") {
138
132
  return { allowed: false, outerMatch: { allowed: false }, innerMatch: { allowed: false } };
@@ -150,12 +144,10 @@ export function resolveNextcloudTalkGroupAllow(params: {
150
144
  const outerMatch = resolveNextcloudTalkAllowlistMatch({
151
145
  allowFrom: params.outerAllowFrom,
152
146
  senderId: params.senderId,
153
- senderName: params.senderName,
154
147
  });
155
148
  const innerMatch = resolveNextcloudTalkAllowlistMatch({
156
149
  allowFrom: params.innerAllowFrom,
157
150
  senderId: params.senderId,
158
- senderName: params.senderName,
159
151
  });
160
152
  const allowed = resolveNestedAllowlistDecision({
161
153
  outerConfigured: outerAllow.length > 0 || innerAllow.length > 0,
package/src/send.ts CHANGED
@@ -93,8 +93,12 @@ export async function sendMessageNextcloudTalk(
93
93
  }
94
94
  const bodyStr = JSON.stringify(body);
95
95
 
96
+ // Nextcloud Talk verifies signature against the extracted message text,
97
+ // not the full JSON body. See ChecksumVerificationService.php:
98
+ // hash_hmac('sha256', $random . $data, $secret)
99
+ // where $data is the "message" parameter, not the raw request body.
96
100
  const { random, signature } = generateNextcloudTalkSignature({
97
- body: bodyStr,
101
+ body: message,
98
102
  secret,
99
103
  });
100
104
 
@@ -183,8 +187,9 @@ export async function sendReactionNextcloudTalk(
183
187
  const normalizedToken = normalizeRoomToken(roomToken);
184
188
 
185
189
  const body = JSON.stringify({ reaction });
190
+ // Sign only the reaction string, not the full JSON body
186
191
  const { random, signature } = generateNextcloudTalkSignature({
187
- body,
192
+ body: reaction,
188
193
  secret,
189
194
  });
190
195
 
package/src/types.ts CHANGED
@@ -68,6 +68,8 @@ export type NextcloudTalkAccountConfig = {
68
68
  blockStreaming?: boolean;
69
69
  /** Merge streamed block replies before sending. */
70
70
  blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
71
+ /** Outbound response prefix override for this channel/account. */
72
+ responsePrefix?: string;
71
73
  /** Media upload max size in MB. */
72
74
  mediaMaxMb?: number;
73
75
  };