@openclaw/nextcloud-talk 2026.7.2-beta.1 → 2026.7.2-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-CYse6FwT.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-De2OKnzd.js";
2
2
  export { nextcloudTalkPlugin };
@@ -15,27 +15,25 @@ import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecr
15
15
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
16
16
  import { parseStrictNonNegativeInteger, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
17
17
  import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
18
+ import { buildChannelInboundEventContext, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
18
19
  import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
19
20
  import { ssrfPolicyFromPrivateNetworkOptIn, ssrfPolicyFromPrivateNetworkOptIn as ssrfPolicyFromPrivateNetworkOptIn$1 } from "openclaw/plugin-sdk/ssrf-runtime";
20
- import { readFileSync } from "node:fs";
21
21
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
22
22
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
23
23
  import { createHmac, randomBytes } from "node:crypto";
24
- import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
24
+ import { fileExists, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
25
25
  import { clearAccountEntryFields } from "openclaw/plugin-sdk/channel-plugin-common";
26
26
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2 } from "openclaw/plugin-sdk/account-id";
27
- import { DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, ToolPolicySchema, buildChannelConfigSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
28
- import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
27
+ import { DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
28
+ import { formatAllowFromLowercase, resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
29
29
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
30
30
  import { requireChannelOpenAllowFrom, resolveLoggerBackedRuntime, runStoppablePassiveMonitor, safeParseJsonWithSchema } from "openclaw/plugin-sdk/extension-shared";
31
31
  import { z } from "zod";
32
- import fs from "node:fs/promises";
33
32
  import os from "node:os";
34
33
  import path from "node:path";
35
- import { createClaimableDedupe, migratePersistentDedupeLegacyJsonFile } from "openclaw/plugin-sdk/persistent-dedupe";
34
+ import { createChannelReplayGuard, migratePersistentDedupeLegacyJsonFile } from "openclaw/plugin-sdk/persistent-dedupe";
36
35
  import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
37
36
  import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
38
- import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
39
37
  import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback } from "openclaw/plugin-sdk/channel-targets";
40
38
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
41
39
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -43,9 +41,40 @@ import { createServer } from "node:http";
43
41
  import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
44
42
  import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
45
43
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
46
- import { applyAccountNameToChannelSection, createSetupTranslator, createStandardChannelSetupStatus, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
44
+ import { applyAccountNameToChannelSection, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
47
45
  import { createSetupInputPresenceValidator, createSetupTranslator as createSetupTranslator$1, mergeAllowFromEntries, promptParsedAllowFromForAccount, resolveSetupAccountId } from "openclaw/plugin-sdk/setup-runtime";
48
46
  import { formatDocsLink as formatDocsLink$1 } from "openclaw/plugin-sdk/setup-tools";
47
+ //#region extensions/nextcloud-talk/src/api-credentials.ts
48
+ function resolveNextcloudTalkApiCredentialsResult(params) {
49
+ const apiUser = params.apiUser?.trim();
50
+ if (!apiUser) return { status: "missing" };
51
+ const inlinePassword = normalizeResolvedSecretInputString({
52
+ value: params.apiPassword,
53
+ path: "channels.nextcloud-talk.apiPassword"
54
+ });
55
+ if (inlinePassword) return {
56
+ status: "available",
57
+ value: {
58
+ apiUser,
59
+ apiPassword: inlinePassword
60
+ }
61
+ };
62
+ if (!params.apiPasswordFile?.trim()) return { status: "missing" };
63
+ const result = tryReadSecretFileSync(params.apiPasswordFile, "Nextcloud Talk API password", { rejectHardlinks: false }, { configPath: params.configPath ?? "channels.nextcloud-talk.apiPasswordFile" });
64
+ if (result.status === "available") return result.value ? {
65
+ status: "available",
66
+ value: {
67
+ apiUser,
68
+ apiPassword: result.value
69
+ }
70
+ } : { status: "missing" };
71
+ return result;
72
+ }
73
+ function resolveNextcloudTalkApiCredentials(params) {
74
+ const result = resolveNextcloudTalkApiCredentialsResult(params);
75
+ return result.status === "available" ? result.value : void 0;
76
+ }
77
+ //#endregion
49
78
  //#region extensions/nextcloud-talk/src/accounts.ts
50
79
  function isTruthyEnvValue(value) {
51
80
  const normalized = normalizeLowercaseStringOrEmpty(value);
@@ -81,13 +110,21 @@ function resolveNextcloudTalkSecret(cfg, opts) {
81
110
  const envSecret = normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET);
82
111
  if (envSecret && resolvedAccountId === DEFAULT_ACCOUNT_ID) return {
83
112
  secret: envSecret,
84
- source: "env"
113
+ source: "env",
114
+ status: "available"
85
115
  };
86
- if (merged.botSecretFile) {
87
- const fileSecret = tryReadSecretFileSync(merged.botSecretFile, "Nextcloud Talk bot secret file", { rejectSymlink: true });
88
- if (fileSecret) return {
89
- secret: fileSecret,
90
- source: "secretFile"
116
+ const botSecretFile = normalizeOptionalString(merged.botSecretFile);
117
+ if (botSecretFile) {
118
+ const result = tryReadSecretFileSync(botSecretFile, "Nextcloud Talk bot secret file", { rejectSymlink: true }, { configPath: `channels.nextcloud-talk.accounts.${resolvedAccountId}.botSecretFile` });
119
+ return result.status === "available" ? {
120
+ secret: result.value,
121
+ source: "secretFile",
122
+ status: "available"
123
+ } : {
124
+ secret: "",
125
+ source: "secretFile",
126
+ status: "configured_unavailable",
127
+ diagnostic: result.diagnostic
91
128
  };
92
129
  }
93
130
  const inlineSecret = normalizeResolvedSecretInputString({
@@ -96,11 +133,13 @@ function resolveNextcloudTalkSecret(cfg, opts) {
96
133
  });
97
134
  if (inlineSecret) return {
98
135
  secret: inlineSecret,
99
- source: "config"
136
+ source: "config",
137
+ status: "available"
100
138
  };
101
139
  return {
102
140
  secret: "",
103
- source: "none"
141
+ source: "none",
142
+ status: "missing"
104
143
  };
105
144
  }
106
145
  function resolveNextcloudTalkAccount(params) {
@@ -111,6 +150,13 @@ function resolveNextcloudTalkAccount(params) {
111
150
  const accountEnabled = merged.enabled !== false;
112
151
  const enabled = baseEnabled && accountEnabled;
113
152
  const secretResolution = resolveNextcloudTalkSecret(params.cfg, { accountId });
153
+ const apiCredentialResolution = resolveNextcloudTalkApiCredentialsResult({
154
+ apiUser: merged.apiUser,
155
+ apiPassword: merged.apiPassword,
156
+ apiPasswordFile: merged.apiPasswordFile,
157
+ configPath: `channels.nextcloud-talk.accounts.${accountId}.apiPasswordFile`
158
+ });
159
+ const diagnostics = [secretResolution.diagnostic, apiCredentialResolution.status === "configured_unavailable" ? apiCredentialResolution.diagnostic : void 0].filter((diagnostic) => Boolean(diagnostic));
114
160
  const baseUrl = merged.baseUrl?.trim()?.replace(/\/$/, "") ?? "";
115
161
  debugAccounts("resolve", {
116
162
  accountId,
@@ -125,6 +171,9 @@ function resolveNextcloudTalkAccount(params) {
125
171
  baseUrl,
126
172
  secret: secretResolution.secret,
127
173
  secretSource: secretResolution.source,
174
+ tokenStatus: secretResolution.status,
175
+ apiCredentialStatus: apiCredentialResolution.status,
176
+ ...diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {},
128
177
  config: merged
129
178
  };
130
179
  };
@@ -132,7 +181,7 @@ function resolveNextcloudTalkAccount(params) {
132
181
  accountId: resolvedAccountId,
133
182
  normalizeAccountId,
134
183
  resolvePrimary: resolve,
135
- hasCredential: (account) => account.secretSource !== "none",
184
+ hasCredential: (account) => account.tokenStatus !== "missing",
136
185
  resolveDefaultAccountId: () => resolveDefaultNextcloudTalkAccountId(params.cfg)
137
186
  });
138
187
  }
@@ -152,30 +201,6 @@ const nextcloudTalkApprovalAuth = createChannelApprovalAuth({
152
201
  normalizeApprover: normalizeNextcloudTalkApproverId
153
202
  }).approvalAuth;
154
203
  //#endregion
155
- //#region extensions/nextcloud-talk/src/api-credentials.ts
156
- function resolveNextcloudTalkApiCredentials(params) {
157
- const apiUser = params.apiUser?.trim();
158
- if (!apiUser) return;
159
- const inlinePassword = normalizeResolvedSecretInputString({
160
- value: params.apiPassword,
161
- path: "channels.nextcloud-talk.apiPassword"
162
- });
163
- if (inlinePassword) return {
164
- apiUser,
165
- apiPassword: inlinePassword
166
- };
167
- if (!params.apiPasswordFile) return;
168
- try {
169
- const filePassword = readFileSync(params.apiPasswordFile, "utf-8").trim();
170
- return filePassword ? {
171
- apiUser,
172
- apiPassword: filePassword
173
- } : void 0;
174
- } catch {
175
- return;
176
- }
177
- }
178
- //#endregion
179
204
  //#region extensions/nextcloud-talk/src/signature.ts
180
205
  const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
181
206
  const RANDOM_HEADER = "x-nextcloud-talk-random";
@@ -362,18 +387,11 @@ const nextcloudTalkPairingTextAdapter = {
362
387
  };
363
388
  //#endregion
364
389
  //#region extensions/nextcloud-talk/src/config-schema.ts
365
- const NextcloudTalkRoomSchema = z.object({
366
- requireMention: z.boolean().optional(),
367
- tools: ToolPolicySchema,
368
- skills: z.array(z.string()).optional(),
369
- enabled: z.boolean().optional(),
370
- allowFrom: z.array(z.string()).optional(),
371
- systemPrompt: z.string().optional()
372
- }).strict();
390
+ const NextcloudTalkRoomSchema = buildGroupEntrySchema({ allowFrom: z.array(z.string()).optional() }).omit({ toolsBySender: true });
373
391
  const NextcloudTalkNetworkSchema = z.object({
374
392
  /** Dangerous opt-in for self-hosted Nextcloud Talk on trusted private/internal hosts. */
375
393
  dangerouslyAllowPrivateNetwork: z.boolean().optional() }).strict().optional();
376
- const NextcloudTalkAccountSchemaBase = z.object({
394
+ const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
377
395
  name: z.string().optional(),
378
396
  enabled: z.boolean().optional(),
379
397
  markdown: MarkdownConfigSchema,
@@ -395,27 +413,17 @@ const NextcloudTalkAccountSchemaBase = z.object({
395
413
  /** Network policy overrides for self-hosted Nextcloud Talk on trusted private/internal hosts. */
396
414
  network: NextcloudTalkNetworkSchema,
397
415
  ...ReplyRuntimeConfigSchemaShape
398
- }).strict();
399
- const NextcloudTalkAccountSchema = NextcloudTalkAccountSchemaBase.superRefine((value, ctx) => {
400
- requireChannelOpenAllowFrom({
401
- channel: "nextcloud-talk",
402
- policy: value.dmPolicy,
403
- allowFrom: value.allowFrom,
404
- ctx,
405
- requireOpenAllowFrom
406
- });
407
- });
408
- const NextcloudTalkConfigSchema = NextcloudTalkAccountSchemaBase.extend({
409
- accounts: z.record(z.string(), NextcloudTalkAccountSchema.optional()).optional(),
410
- defaultAccount: z.string().optional()
411
- }).superRefine((value, ctx) => {
412
- requireChannelOpenAllowFrom({
413
- channel: "nextcloud-talk",
414
- policy: value.dmPolicy,
415
- allowFrom: value.allowFrom,
416
- ctx,
417
- requireOpenAllowFrom
418
- });
416
+ }).strict(), {
417
+ optionalAccount: true,
418
+ refine: (value, ctx) => {
419
+ requireChannelOpenAllowFrom({
420
+ channel: "nextcloud-talk",
421
+ policy: value.dmPolicy,
422
+ allowFrom: value.allowFrom,
423
+ ctx,
424
+ requireOpenAllowFrom
425
+ });
426
+ }
419
427
  });
420
428
  //#endregion
421
429
  //#region extensions/nextcloud-talk/src/replay-guard.ts
@@ -424,7 +432,7 @@ const NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX = "replay-dedupe";
424
432
  const DEFAULT_REPLAY_TTL_MS = 1440 * 60 * 1e3;
425
433
  const DEFAULT_MEMORY_MAX_SIZE = 1e3;
426
434
  const DEFAULT_STATE_MAX_ENTRIES = 1e4;
427
- function buildReplayKey(params) {
435
+ function buildNextcloudTalkReplayKey(params) {
428
436
  const roomToken = params.roomToken.trim();
429
437
  const messageId = params.messageId.trim();
430
438
  if (!roomToken || !messageId) return null;
@@ -436,55 +444,21 @@ function createNextcloudTalkReplayGuard(options) {
436
444
  ttlMs: options.ttlMs ?? DEFAULT_REPLAY_TTL_MS,
437
445
  memoryMaxSize: options.memoryMaxSize ?? DEFAULT_MEMORY_MAX_SIZE
438
446
  };
439
- const dedupe = createClaimableDedupe(stateDir ? {
440
- ...baseOptions,
441
- pluginId: NEXTCLOUD_TALK_PLUGIN_ID,
442
- namespacePrefix: NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX,
443
- stateMaxEntries: options.stateMaxEntries ?? options.fileMaxEntries ?? DEFAULT_STATE_MAX_ENTRIES,
444
- env: {
445
- ...process.env,
446
- OPENCLAW_STATE_DIR: stateDir
447
- },
448
- onDiskError: options.onDiskError
449
- } : baseOptions);
450
- return {
451
- claimMessage: async ({ accountId, roomToken, messageId }) => {
452
- const replayKey = buildReplayKey({
453
- roomToken,
454
- messageId
455
- });
456
- if (!replayKey) return "invalid";
457
- return (await dedupe.claim(replayKey, { namespace: accountId })).kind;
458
- },
459
- commitMessage: async ({ accountId, roomToken, messageId }) => {
460
- const replayKey = buildReplayKey({
461
- roomToken,
462
- messageId
463
- });
464
- if (!replayKey) return true;
465
- return await dedupe.commit(replayKey, { namespace: accountId });
466
- },
467
- releaseMessage: ({ accountId, roomToken, messageId, error }) => {
468
- const replayKey = buildReplayKey({
469
- roomToken,
470
- messageId
471
- });
472
- if (!replayKey) return;
473
- dedupe.release(replayKey, {
474
- namespace: accountId,
475
- error
476
- });
477
- },
478
- shouldProcessMessage: async ({ accountId, roomToken, messageId }) => {
479
- const replayKey = buildReplayKey({
480
- roomToken,
481
- messageId
482
- });
483
- if (!replayKey) return true;
484
- if ((await dedupe.claim(replayKey, { namespace: accountId })).kind !== "claimed") return false;
485
- return await dedupe.commit(replayKey, { namespace: accountId });
486
- }
487
- };
447
+ return createChannelReplayGuard({
448
+ dedupe: stateDir ? {
449
+ ...baseOptions,
450
+ pluginId: NEXTCLOUD_TALK_PLUGIN_ID,
451
+ namespacePrefix: NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX,
452
+ stateMaxEntries: options.stateMaxEntries ?? options.fileMaxEntries ?? DEFAULT_STATE_MAX_ENTRIES,
453
+ env: {
454
+ ...process.env,
455
+ OPENCLAW_STATE_DIR: stateDir
456
+ },
457
+ onDiskError: options.onDiskError
458
+ } : baseOptions,
459
+ buildReplayKey: buildNextcloudTalkReplayKey,
460
+ namespace: (event) => event.accountId
461
+ });
488
462
  }
489
463
  //#endregion
490
464
  //#region extensions/nextcloud-talk/src/doctor.ts
@@ -495,14 +469,6 @@ function sanitizeLegacyReplaySegment(value) {
495
469
  if (!trimmed) return "default";
496
470
  return trimmed.replace(/[^a-zA-Z0-9_-]/g, "_");
497
471
  }
498
- async function fileExists(filePath) {
499
- try {
500
- await fs.access(filePath);
501
- return true;
502
- } catch {
503
- return false;
504
- }
505
- }
506
472
  async function collectNextcloudTalkBotResponseWarnings(params) {
507
473
  const warnings = [];
508
474
  for (const accountId of listNextcloudTalkAccountIds(params.cfg)) {
@@ -527,7 +493,7 @@ async function repairNextcloudTalkReplayDedupeState(params) {
527
493
  const replayDir = path.join(stateDir, "nextcloud-talk", "replay-dedupe");
528
494
  for (const accountId of listNextcloudTalkAccountIds(params.cfg)) {
529
495
  const legacyPath = path.join(replayDir, `${sanitizeLegacyReplaySegment(accountId)}.json`);
530
- if (!await fileExists(legacyPath)) continue;
496
+ if (!fileExists(legacyPath)) continue;
531
497
  try {
532
498
  const result = await migratePersistentDedupeLegacyJsonFile({
533
499
  filePath: legacyPath,
@@ -574,19 +540,13 @@ function normalizeNextcloudTalkAllowlist(values) {
574
540
  return (values ?? []).map((value) => normalizeNextcloudTalkAllowEntry(String(value))).filter(Boolean);
575
541
  }
576
542
  function resolveNextcloudTalkAllowlistMatch(params) {
577
- const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
578
- if (allowFrom.length === 0) return { allowed: false };
579
- if (allowFrom.includes("*")) return {
580
- allowed: true,
581
- matchKey: "*",
582
- matchSource: "wildcard"
583
- };
584
- const senderId = normalizeNextcloudTalkAllowEntry(params.senderId);
585
- return allowFrom.includes(senderId) ? {
586
- allowed: true,
587
- matchKey: senderId,
588
- matchSource: "id"
589
- } : { allowed: false };
543
+ return resolveAllowlistMatchByCandidates({
544
+ allowList: normalizeNextcloudTalkAllowlist(params.allowFrom),
545
+ candidates: [{
546
+ value: normalizeNextcloudTalkAllowEntry(params.senderId),
547
+ source: "id"
548
+ }]
549
+ });
590
550
  }
591
551
  function resolveNextcloudTalkRoomMatch(params) {
592
552
  const rooms = params.rooms ?? {};
@@ -1138,19 +1098,17 @@ async function handleNextcloudTalkInbound(params) {
1138
1098
  runtime.log?.(`nextcloud-talk: drop room ${roomToken} (no mention)`);
1139
1099
  return;
1140
1100
  }
1141
- const { route, buildEnvelope } = resolveInboundRouteEnvelopeBuilderWithRuntime({
1101
+ const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
1142
1102
  cfg: config,
1143
1103
  channel: CHANNEL_ID,
1144
1104
  accountId: account.accountId,
1145
1105
  peer: {
1146
1106
  kind: isGroup ? "group" : "direct",
1147
1107
  id: isGroup ? roomToken : senderId
1148
- },
1149
- runtime: core.channel,
1150
- sessionStore: config.session?.store
1108
+ }
1151
1109
  });
1152
1110
  const fromLabel = isGroup ? `room:${roomName || roomToken}` : senderName || `user:${senderId}`;
1153
- const { storePath, body } = buildEnvelope({
1111
+ const body = buildEnvelope({
1154
1112
  channel: "Nextcloud Talk",
1155
1113
  from: fromLabel,
1156
1114
  timestamp: message.timestamp,
@@ -1158,40 +1116,58 @@ async function handleNextcloudTalkInbound(params) {
1158
1116
  });
1159
1117
  const groupSystemPrompt = normalizeOptionalString(roomConfig?.systemPrompt);
1160
1118
  const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
1161
- const ctxPayload = core.channel.reply.finalizeInboundContext({
1162
- Body: body,
1163
- BodyForAgent: rawBody,
1164
- RawBody: rawBody,
1165
- CommandBody: rawBody,
1166
- From: isGroup ? `nextcloud-talk:room:${roomToken}` : `nextcloud-talk:${senderId}`,
1167
- To: `nextcloud-talk:${roomToken}`,
1168
- SessionKey: route.sessionKey,
1169
- AccountId: route.accountId,
1170
- ChatType: isGroup ? "group" : "direct",
1171
- ConversationLabel: fromLabel,
1172
- SenderName: senderName || void 0,
1173
- SenderId: senderId,
1174
- GroupSubject: isGroup ? roomName || roomToken : void 0,
1175
- GroupSystemPrompt: isGroup ? groupSystemPrompt : void 0,
1176
- Provider: CHANNEL_ID,
1177
- Surface: CHANNEL_ID,
1178
- WasMentioned: isGroup ? wasMentioned : void 0,
1179
- MessageSid: message.messageId,
1180
- Timestamp: message.timestamp,
1181
- OriginatingChannel: CHANNEL_ID,
1182
- OriginatingTo: `nextcloud-talk:${roomToken}`,
1183
- CommandAuthorized: commandAuthorized
1119
+ const ctxPayload = buildChannelInboundEventContext({
1120
+ channel: CHANNEL_ID,
1121
+ accountId: route.accountId,
1122
+ messageId: message.messageId,
1123
+ timestamp: message.timestamp,
1124
+ from: isGroup ? `nextcloud-talk:room:${roomToken}` : `nextcloud-talk:${senderId}`,
1125
+ sender: {
1126
+ id: senderId,
1127
+ name: senderName || void 0
1128
+ },
1129
+ conversation: {
1130
+ kind: isGroup ? "group" : "direct",
1131
+ id: roomToken,
1132
+ label: fromLabel
1133
+ },
1134
+ route: {
1135
+ agentId: route.agentId,
1136
+ dmScope: route.dmScope,
1137
+ accountId: route.accountId,
1138
+ routeSessionKey: route.sessionKey
1139
+ },
1140
+ reply: {
1141
+ to: `nextcloud-talk:${roomToken}`,
1142
+ originatingTo: `nextcloud-talk:${roomToken}`
1143
+ },
1144
+ message: {
1145
+ body,
1146
+ bodyForAgent: rawBody,
1147
+ rawBody,
1148
+ commandBody: rawBody
1149
+ },
1150
+ access: {
1151
+ commands: { authorized: commandAuthorized },
1152
+ mentions: {
1153
+ canDetectMention: isGroup,
1154
+ wasMentioned: isGroup && wasMentioned
1155
+ }
1156
+ },
1157
+ extra: {
1158
+ GroupSubject: isGroup ? roomName || roomToken : void 0,
1159
+ GroupSystemPrompt: isGroup ? groupSystemPrompt : void 0
1160
+ }
1184
1161
  });
1185
- await core.channel.inbound.dispatchReply({
1162
+ await core.channel.inbound.dispatch({
1186
1163
  cfg: config,
1187
1164
  channel: CHANNEL_ID,
1188
1165
  accountId: account.accountId,
1189
- agentId: route.agentId,
1190
- routeSessionKey: route.sessionKey,
1191
- storePath,
1166
+ route: {
1167
+ agentId: route.agentId,
1168
+ sessionKey: route.sessionKey
1169
+ },
1192
1170
  ctxPayload,
1193
- recordInboundSession: core.channel.session.recordInboundSession,
1194
- dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
1195
1171
  delivery: {
1196
1172
  preparePayload: (payload) => payload.text === void 0 ? payload : {
1197
1173
  ...payload,
@@ -1263,40 +1239,12 @@ const WEBHOOK_ERRORS = {
1263
1239
  payloadTooLarge: "Payload too large",
1264
1240
  internalServerError: "Internal server error"
1265
1241
  };
1266
- var NextcloudTalkRetryableWebhookError = class extends Error {
1267
- constructor(message, options) {
1268
- super(message, options);
1269
- this.name = "NextcloudTalkRetryableWebhookError";
1270
- }
1271
- };
1272
1242
  async function processNextcloudTalkReplayGuardedMessage(params) {
1273
- if (await params.replayGuard.claimMessage({
1243
+ return (await params.replayGuard.processGuarded({
1274
1244
  accountId: params.accountId,
1275
1245
  roomToken: params.message.roomToken,
1276
1246
  messageId: params.message.messageId
1277
- }) !== "claimed") return "duplicate";
1278
- try {
1279
- await params.handleMessage();
1280
- await params.replayGuard.commitMessage({
1281
- accountId: params.accountId,
1282
- roomToken: params.message.roomToken,
1283
- messageId: params.message.messageId
1284
- });
1285
- return "processed";
1286
- } catch (error) {
1287
- if (error instanceof NextcloudTalkRetryableWebhookError) params.replayGuard.releaseMessage({
1288
- accountId: params.accountId,
1289
- roomToken: params.message.roomToken,
1290
- messageId: params.message.messageId,
1291
- error
1292
- });
1293
- else await params.replayGuard.commitMessage({
1294
- accountId: params.accountId,
1295
- roomToken: params.message.roomToken,
1296
- messageId: params.message.messageId
1297
- });
1298
- throw error;
1299
- }
1247
+ }, params.handleMessage, { onError: "commit" })).kind === "processed" ? "processed" : "duplicate";
1300
1248
  }
1301
1249
  function formatError(err) {
1302
1250
  if (err instanceof Error) return err.message;
@@ -1959,8 +1907,10 @@ const nextcloudTalkSetupWizard = {
1959
1907
  [CONFIGURE_API_FLAG]: "1"
1960
1908
  } };
1961
1909
  },
1962
- credentials: [{
1910
+ credentials: [defineTokenCredential({
1963
1911
  inputKey: "token",
1912
+ configKey: "botSecret",
1913
+ configuredFields: ["botSecret", "botSecretFile"],
1964
1914
  providerHint: channel,
1965
1915
  credentialLabel: t("wizard.nextcloudTalk.botSecret"),
1966
1916
  preferredEnvVar: "NEXTCLOUD_TALK_BOT_SECRET",
@@ -1968,61 +1918,56 @@ const nextcloudTalkSetupWizard = {
1968
1918
  keepPrompt: t("wizard.nextcloudTalk.botSecretKeep"),
1969
1919
  inputPrompt: t("wizard.nextcloudTalk.botSecretInput"),
1970
1920
  allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1,
1971
- inspect: ({ cfg, accountId }) => {
1972
- const resolvedAccount = resolveNextcloudTalkAccount({
1973
- cfg,
1974
- accountId
1975
- });
1976
- return {
1977
- accountConfigured: Boolean(resolvedAccount.secret && resolvedAccount.baseUrl),
1978
- hasConfiguredValue: Boolean(hasConfiguredSecretInput(resolvedAccount.config.botSecret) || resolvedAccount.config.botSecretFile),
1979
- resolvedValue: resolvedAccount.secret || void 0,
1980
- envValue: accountId === DEFAULT_ACCOUNT_ID$1 ? normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET) : void 0
1981
- };
1921
+ resolveAccount: ({ cfg, accountId }) => resolveNextcloudTalkAccount({
1922
+ cfg,
1923
+ accountId
1924
+ }),
1925
+ accountConfigured: (account) => Boolean(account.secret && account.baseUrl),
1926
+ hasConfiguredValue: (account) => Boolean(hasConfiguredSecretInput(account.config.botSecret) || account.config.botSecretFile),
1927
+ resolvedValue: (account) => account.secret || void 0,
1928
+ envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1 ? normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET) : void 0,
1929
+ patchAccount: ({ cfg, accountId, patch, clearFields }) => {
1930
+ return setNextcloudTalkAccountConfig(clearNextcloudTalkAccountFields(cfg, accountId, clearFields), accountId, patch);
1982
1931
  },
1983
- applyUseEnv: async (params) => {
1984
- const resolvedAccount = resolveNextcloudTalkAccount({
1985
- cfg: params.cfg,
1986
- accountId: params.accountId
1987
- });
1988
- return setNextcloudTalkAccountConfig(clearNextcloudTalkAccountFields(params.cfg, params.accountId, ["botSecret", "botSecretFile"]), params.accountId, { baseUrl: resolvedAccount.baseUrl });
1932
+ useEnv: {
1933
+ clearFields: ["botSecret", "botSecretFile"],
1934
+ patch: (account) => ({ baseUrl: account.baseUrl })
1989
1935
  },
1990
- applySet: async (params) => setNextcloudTalkAccountConfig(clearNextcloudTalkAccountFields(params.cfg, params.accountId, ["botSecret", "botSecretFile"]), params.accountId, { botSecret: params.value })
1991
- }, {
1936
+ set: { clearFields: ["botSecret", "botSecretFile"] }
1937
+ }), defineTokenCredential({
1992
1938
  inputKey: "password",
1939
+ configKey: "apiPassword",
1940
+ configuredFields: ["apiPassword", "apiPasswordFile"],
1993
1941
  providerHint: "nextcloud-talk-api",
1994
1942
  credentialLabel: t("wizard.nextcloudTalk.apiPassword"),
1995
1943
  preferredEnvVar: "NEXTCLOUD_TALK_API_PASSWORD",
1996
1944
  envPrompt: "",
1997
1945
  keepPrompt: t("wizard.nextcloudTalk.apiPasswordKeep"),
1998
1946
  inputPrompt: t("wizard.nextcloudTalk.apiPasswordInput"),
1999
- inspect: ({ cfg, accountId }) => {
2000
- const resolvedAccount = resolveNextcloudTalkAccount({
2001
- cfg,
2002
- accountId
2003
- });
2004
- const apiUser = resolvedAccount.config.apiUser?.trim();
2005
- const apiPasswordConfigured = Boolean(hasConfiguredSecretInput(resolvedAccount.config.apiPassword) || resolvedAccount.config.apiPasswordFile);
2006
- return {
2007
- accountConfigured: Boolean(apiUser && apiPasswordConfigured),
2008
- hasConfiguredValue: apiPasswordConfigured
2009
- };
2010
- },
1947
+ resolveAccount: ({ cfg, accountId }) => resolveNextcloudTalkAccount({
1948
+ cfg,
1949
+ accountId
1950
+ }),
1951
+ accountConfigured: (account) => Boolean(account.config.apiUser?.trim() && (hasConfiguredSecretInput(account.config.apiPassword) || account.config.apiPasswordFile)),
1952
+ hasConfiguredValue: (account) => Boolean(hasConfiguredSecretInput(account.config.apiPassword) || account.config.apiPasswordFile),
2011
1953
  shouldPrompt: ({ credentialValues }) => credentialValues[CONFIGURE_API_FLAG] === "1",
2012
- applySet: async (params) => setNextcloudTalkAccountConfig(clearNextcloudTalkAccountFields(params.cfg, params.accountId, ["apiPassword", "apiPasswordFile"]), params.accountId, { apiPassword: params.value })
2013
- }],
2014
- textInputs: [{
1954
+ patchAccount: ({ cfg, accountId, patch, clearFields }) => setNextcloudTalkAccountConfig(clearNextcloudTalkAccountFields(cfg, accountId, clearFields), accountId, patch),
1955
+ set: { clearFields: ["apiPassword", "apiPasswordFile"] }
1956
+ })],
1957
+ textInputs: [baseUrlTextInput({
2015
1958
  inputKey: "httpUrl",
1959
+ configKey: "baseUrl",
2016
1960
  message: t("wizard.nextcloudTalk.instanceUrlPrompt"),
2017
- currentValue: ({ cfg, accountId }) => resolveNextcloudTalkAccount({
1961
+ resolveAccount: ({ cfg, accountId }) => resolveNextcloudTalkAccount({
2018
1962
  cfg,
2019
1963
  accountId
2020
- }).baseUrl || void 0,
1964
+ }),
1965
+ currentValue: (account) => account.baseUrl || void 0,
2021
1966
  shouldPrompt: ({ currentValue }) => !currentValue,
2022
- validate: ({ value }) => validateNextcloudTalkBaseUrl(value),
2023
- normalizeValue: ({ value }) => normalizeNextcloudTalkBaseUrl(value),
2024
- applySet: async (params) => setNextcloudTalkAccountConfig(params.cfg, params.accountId, { baseUrl: params.value })
2025
- }, {
1967
+ validate: validateNextcloudTalkBaseUrl,
1968
+ normalize: normalizeNextcloudTalkBaseUrl,
1969
+ patchAccount: ({ cfg, accountId, patch }) => setNextcloudTalkAccountConfig(cfg, accountId, patch)
1970
+ }), {
2026
1971
  inputKey: "userId",
2027
1972
  message: t("wizard.nextcloudTalk.apiUserPrompt"),
2028
1973
  currentValue: ({ cfg, accountId }) => resolveNextcloudTalkAccount({
@@ -2084,12 +2029,14 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2084
2029
  configSchema: buildChannelConfigSchema(NextcloudTalkConfigSchema),
2085
2030
  config: {
2086
2031
  ...nextcloudTalkConfigAdapter,
2087
- isConfigured: (account) => Boolean(account.secret?.trim() && account.baseUrl?.trim()),
2032
+ isConfigured: (account) => Boolean(account.tokenStatus !== "missing" && account.baseUrl?.trim()),
2088
2033
  describeAccount: (account) => describeWebhookAccountSnapshot({
2089
2034
  account,
2090
- configured: Boolean(account.secret?.trim() && account.baseUrl?.trim()),
2035
+ configured: Boolean(account.tokenStatus !== "missing" && account.baseUrl?.trim()),
2091
2036
  extra: {
2092
2037
  secretSource: account.secretSource,
2038
+ tokenStatus: account.tokenStatus,
2039
+ apiCredentialStatus: account.apiCredentialStatus,
2093
2040
  baseUrl: account.baseUrl ? "[set]" : "[missing]"
2094
2041
  }
2095
2042
  })
@@ -2140,9 +2087,11 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
2140
2087
  accountId: account.accountId,
2141
2088
  name: account.name,
2142
2089
  enabled: account.enabled,
2143
- configured: Boolean(account.secret?.trim() && account.baseUrl?.trim()),
2090
+ configured: Boolean(account.tokenStatus !== "missing" && account.baseUrl?.trim()),
2144
2091
  extra: {
2145
2092
  secretSource: account.secretSource,
2093
+ tokenStatus: account.tokenStatus,
2094
+ apiCredentialStatus: account.apiCredentialStatus,
2146
2095
  baseUrl: account.baseUrl ? "[set]" : "[missing]",
2147
2096
  mode: "webhook"
2148
2097
  }
@@ -1,2 +1,2 @@
1
- import { t as nextcloudTalkPlugin } from "./channel-CYse6FwT.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-De2OKnzd.js";
2
2
  export { nextcloudTalkPlugin };
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.7.2-beta.1",
3
+ "version": "2026.7.2-beta.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/nextcloud-talk",
9
- "version": "2026.7.2-beta.1",
9
+ "version": "2026.7.2-beta.3",
10
10
  "dependencies": {
11
11
  "zod": "4.4.3"
12
12
  },
13
13
  "peerDependencies": {
14
- "openclaw": ">=2026.7.2-beta.1"
14
+ "openclaw": ">=2026.7.2-beta.3"
15
15
  },
16
16
  "peerDependenciesMeta": {
17
17
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.7.2-beta.1",
3
+ "version": "2026.7.2-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.1"
11
+ "openclaw": ">=2026.7.2-beta.3"
12
12
  },
13
13
  "peerDependenciesMeta": {
14
14
  "openclaw": {
@@ -40,10 +40,10 @@
40
40
  "minHostVersion": ">=2026.4.10"
41
41
  },
42
42
  "compat": {
43
- "pluginApi": ">=2026.7.2-beta.1"
43
+ "pluginApi": ">=2026.7.2-beta.3"
44
44
  },
45
45
  "build": {
46
- "openclawVersion": "2026.7.2-beta.1"
46
+ "openclawVersion": "2026.7.2-beta.3"
47
47
  },
48
48
  "release": {
49
49
  "publishToClawHub": true,