@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-
|
|
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,
|
|
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 {
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
});
|
|
408
|
-
|
|
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
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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 (!
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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 } =
|
|
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
|
|
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 =
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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.
|
|
1162
|
+
await core.channel.inbound.dispatch({
|
|
1186
1163
|
cfg: config,
|
|
1187
1164
|
channel: CHANNEL_ID,
|
|
1188
1165
|
accountId: account.accountId,
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1243
|
+
return (await params.replayGuard.processGuarded({
|
|
1274
1244
|
accountId: params.accountId,
|
|
1275
1245
|
roomToken: params.message.roomToken,
|
|
1276
1246
|
messageId: params.message.messageId
|
|
1277
|
-
}
|
|
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
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
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
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
1961
|
+
resolveAccount: ({ cfg, accountId }) => resolveNextcloudTalkAccount({
|
|
2018
1962
|
cfg,
|
|
2019
1963
|
accountId
|
|
2020
|
-
})
|
|
1964
|
+
}),
|
|
1965
|
+
currentValue: (account) => account.baseUrl || void 0,
|
|
2021
1966
|
shouldPrompt: ({ currentValue }) => !currentValue,
|
|
2022
|
-
validate:
|
|
2023
|
-
|
|
2024
|
-
|
|
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.
|
|
2032
|
+
isConfigured: (account) => Boolean(account.tokenStatus !== "missing" && account.baseUrl?.trim()),
|
|
2088
2033
|
describeAccount: (account) => describeWebhookAccountSnapshot({
|
|
2089
2034
|
account,
|
|
2090
|
-
configured: Boolean(account.
|
|
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.
|
|
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-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-De2OKnzd.js";
|
|
2
2
|
export { nextcloudTalkPlugin };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/nextcloud-talk",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
43
|
+
"pluginApi": ">=2026.7.2-beta.3"
|
|
44
44
|
},
|
|
45
45
|
"build": {
|
|
46
|
-
"openclawVersion": "2026.7.2-beta.
|
|
46
|
+
"openclawVersion": "2026.7.2-beta.3"
|
|
47
47
|
},
|
|
48
48
|
"release": {
|
|
49
49
|
"publishToClawHub": true,
|