@openclaw/synology-chat 2026.7.1 → 2026.7.2-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,3 +1,3 @@
1
- import { n as setSynologyRuntime, t as synologyChatPlugin } from "./channel-C6bhvNZi.js";
1
+ import { n as setSynologyRuntime, t as synologyChatPlugin } from "./channel-Hiv3SX8l.js";
2
2
  import { t as collectSynologyChatSecurityAuditFindings } from "./security-audit-DIsaxIaB.js";
3
3
  export { collectSynologyChatSecurityAuditFindings, setSynologyRuntime, synologyChatPlugin };
@@ -1,4 +1,4 @@
1
- import { i as resolveAccount, n as synologyChatSetupWizard, r as listAccountIds, t as synologyChatSetupAdapter } from "./setup-surface-pMbKFqU5.js";
1
+ import { i as resolveAccount, n as synologyChatSetupWizard, r as listAccountIds, t as synologyChatSetupAdapter } from "./setup-surface-CJtfU_Gw.js";
2
2
  import { t as collectSynologyChatSecurityAuditFindings } from "./security-audit-DIsaxIaB.js";
3
3
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
4
4
  import { createHybridChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -8,16 +8,18 @@ import { composeWarningCollectors, createConditionalWarningCollector, projectAcc
8
8
  import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
9
9
  import { finiteSecondsToTimerSafeMilliseconds, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
10
10
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
11
12
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/account-resolution";
12
- import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
13
+ import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
13
14
  import * as http from "node:http";
14
15
  import * as https from "node:https";
15
16
  import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
17
+ import { readByteStreamWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
16
18
  import { sleep } from "openclaw/plugin-sdk/runtime-env";
17
19
  import { formatErrorMessage, resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
18
20
  import { z } from "zod";
19
21
  import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
20
- import { beginWebhookRequestPipelineOrReject, createFixedWindowRateLimiter, createWebhookInFlightLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, registerPluginHttpRoute, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
22
+ import { beginWebhookRequestPipelineOrReject, createFixedWindowRateLimiter, createWebhookInFlightLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, registerPluginHttpRoute, requestBodyErrorToText, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
21
23
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
22
24
  import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
23
25
  import * as querystring from "node:querystring";
@@ -29,16 +31,13 @@ function normalizeSynologyChatApproverId(value) {
29
31
  const trimmed = String(value).trim();
30
32
  return /^\d+$/.test(trimmed) ? trimmed : void 0;
31
33
  }
32
- const synologyChatApprovalAuth = createResolvedApproverActionAuthAdapter({
34
+ const synologyChatApprovalAuth = createChannelApprovalAuth({
33
35
  channelLabel: "Synology Chat",
34
- resolveApprovers: ({ cfg, accountId }) => {
35
- return resolveApprovalApprovers({
36
- allowFrom: resolveAccount(cfg ?? {}, accountId).allowedUserIds,
37
- normalizeApprover: normalizeSynologyChatApproverId
38
- });
36
+ resolveInputs: ({ cfg, accountId }) => {
37
+ return { allowFrom: resolveAccount(cfg ?? {}, accountId).allowedUserIds };
39
38
  },
40
- normalizeSenderId: (value) => normalizeSynologyChatApproverId(value)
41
- });
39
+ normalizeApprover: normalizeSynologyChatApproverId
40
+ }).approvalAuth;
42
41
  //#endregion
43
42
  //#region extensions/synology-chat/src/client.ts
44
43
  /**
@@ -46,6 +45,8 @@ const synologyChatApprovalAuth = createResolvedApproverActionAuthAdapter({
46
45
  * Sends messages TO Synology Chat via the incoming webhook URL.
47
46
  */
48
47
  const MIN_SEND_INTERVAL_MS = 500;
48
+ /** user_list JSON can be larger than inbound webhook pre-auth payloads. */
49
+ const USER_LIST_RESPONSE_MAX_BYTES = 1 * 1024 * 1024;
49
50
  let lastSendTime = 0;
50
51
  let sendQueue = Promise.resolve();
51
52
  const ChatUserSchema = z.object({
@@ -129,29 +130,36 @@ async function fetchChatUsers(incomingUrl, allowInsecureSsl = false, log) {
129
130
  const transport = parsedUrl.protocol === "https:" ? https : http;
130
131
  const requestOptions = parsedUrl.protocol === "https:" ? { rejectUnauthorized: !allowInsecureSsl } : {};
131
132
  const req = transport.get(listUrl, requestOptions, (res) => {
132
- let data = "";
133
- res.on("data", (c) => {
134
- data += c.toString();
135
- });
136
- res.on("end", () => {
137
- const result = safeParseJsonWithSchema(ChatUserListResponseSchema, data);
138
- if (!result) {
139
- log?.warn("fetchChatUsers: failed to parse user_list response");
140
- finish(cached?.users ?? []);
141
- return;
142
- }
143
- if (result.success) {
144
- const users = result.data?.users ?? [];
145
- chatUserCache.set(listUrl, {
146
- users,
147
- cachedAt: now
133
+ (async () => {
134
+ try {
135
+ const data = await readByteStreamWithLimit(res, {
136
+ maxBytes: USER_LIST_RESPONSE_MAX_BYTES,
137
+ onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`user_list response exceeded ${maxBytes} bytes`)
148
138
  });
149
- finish(users);
150
- return;
139
+ if (settled) return;
140
+ const result = safeParseJsonWithSchema(ChatUserListResponseSchema, data.toString("utf8"));
141
+ if (!result) {
142
+ log?.warn("fetchChatUsers: failed to parse user_list response");
143
+ finish(cached?.users ?? []);
144
+ return;
145
+ }
146
+ if (result.success) {
147
+ const users = result.data?.users ?? [];
148
+ chatUserCache.set(listUrl, {
149
+ users,
150
+ cachedAt: now
151
+ });
152
+ finish(users);
153
+ return;
154
+ }
155
+ log?.warn(`fetchChatUsers: API returned success=${result.success}, using cached data`);
156
+ finish(cached?.users ?? []);
157
+ } catch (err) {
158
+ if (settled) return;
159
+ log?.warn(`fetchChatUsers: ${formatErrorMessage(err)}, using cached data`);
160
+ finish(cached?.users ?? []);
151
161
  }
152
- log?.warn(`fetchChatUsers: API returned success=${result.success}, using cached data`);
153
- finish(cached?.users ?? []);
154
- });
162
+ })();
155
163
  }).on("error", (err) => {
156
164
  log?.warn(`fetchChatUsers: HTTP error — ${err instanceof Error ? err.message : err}`);
157
165
  finish(cached?.users ?? []);
@@ -228,13 +236,10 @@ function doPost(url, body, allowInsecureSsl = false) {
228
236
  timeout: 3e4,
229
237
  rejectUnauthorized: !allowInsecureSsl
230
238
  }, (res) => {
231
- let data = "";
232
- res.on("data", (chunk) => {
233
- data += chunk.toString();
234
- });
235
239
  res.on("end", () => {
236
240
  resolve(res.statusCode === 200);
237
241
  });
242
+ res.resume();
238
243
  });
239
244
  req.on("error", reject);
240
245
  req.on("timeout", () => {
@@ -554,8 +559,8 @@ function getInvalidTokenRateLimiter(account) {
554
559
  }
555
560
  return rl;
556
561
  }
557
- function getSynologyWebhookInvalidTokenRateLimitKey(req) {
558
- return req.socket?.remoteAddress ?? "unknown";
562
+ function getSynologyWebhookInvalidTokenRateLimitKey(params) {
563
+ return resolveRequestClientIp(params.req, params.trustedProxies, params.allowRealIpFallback === true) ?? params.req.socket?.remoteAddress ?? "unknown";
559
564
  }
560
565
  function getSynologyWebhookInFlightKey(account) {
561
566
  return account.accountId;
@@ -736,7 +741,11 @@ async function parseWebhookPayloadRequest(params) {
736
741
  };
737
742
  }
738
743
  async function authorizeSynologyWebhook(params) {
739
- const invalidTokenRateLimitKey = getSynologyWebhookInvalidTokenRateLimitKey(params.req);
744
+ const invalidTokenRateLimitKey = getSynologyWebhookInvalidTokenRateLimitKey({
745
+ req: params.req,
746
+ trustedProxies: params.trustedProxies,
747
+ allowRealIpFallback: params.allowRealIpFallback
748
+ });
740
749
  if (params.invalidTokenRateLimiter.isLocked(invalidTokenRateLimitKey)) {
741
750
  params.log?.warn(`Rate limit exceeded for remote IP: ${invalidTokenRateLimitKey}`);
742
751
  return {
@@ -815,6 +824,8 @@ async function parseAndAuthorizeSynologyWebhook(params) {
815
824
  payload: parsed.payload,
816
825
  invalidTokenRateLimiter: params.invalidTokenRateLimiter,
817
826
  rateLimiter: params.rateLimiter,
827
+ trustedProxies: params.trustedProxies,
828
+ allowRealIpFallback: params.allowRealIpFallback,
818
829
  log: params.log
819
830
  });
820
831
  if (!authorized.ok) {
@@ -902,6 +913,8 @@ function createWebhookHandler(deps) {
902
913
  account,
903
914
  invalidTokenRateLimiter,
904
915
  rateLimiter,
916
+ trustedProxies: deps.trustedProxies,
917
+ allowRealIpFallback: deps.allowRealIpFallback,
905
918
  log,
906
919
  bodyTimeoutMs: deps.bodyTimeoutMs
907
920
  });
@@ -988,7 +1001,7 @@ function validateSynologyGatewayAccountStartup(params) {
988
1001
  return { ok: true };
989
1002
  }
990
1003
  function registerSynologyWebhookRoute(params) {
991
- const { account, log } = params;
1004
+ const { cfg, account, log } = params;
992
1005
  const routeKey = getRouteKey(account);
993
1006
  const prevUnregister = activeRouteUnregisters.get(routeKey);
994
1007
  if (prevUnregister) {
@@ -998,6 +1011,8 @@ function registerSynologyWebhookRoute(params) {
998
1011
  }
999
1012
  const handler = createWebhookHandler({
1000
1013
  account,
1014
+ trustedProxies: cfg.gateway?.trustedProxies,
1015
+ allowRealIpFallback: cfg.gateway?.allowRealIpFallback === true,
1001
1016
  deliver: async (msg) => await dispatchSynologyChatInboundEvent({
1002
1017
  account,
1003
1018
  msg,
@@ -1198,6 +1213,7 @@ function createSynologyChatPlugin() {
1198
1213
  }).ok) return waitUntilAbort(abortSignal);
1199
1214
  log?.info?.(`Starting Synology Chat channel (account: ${accountId}, path: ${account.webhookPath})`);
1200
1215
  const unregister = registerSynologyWebhookRoute({
1216
+ cfg,
1201
1217
  account,
1202
1218
  accountId,
1203
1219
  log
@@ -1255,6 +1271,7 @@ function createSynologyChatPlugin() {
1255
1271
  outbound: {
1256
1272
  deliveryMode: "gateway",
1257
1273
  textChunkLimit: 2e3,
1274
+ sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text),
1258
1275
  sendText: sendSynologyChatText,
1259
1276
  sendMedia: async (ctx) => {
1260
1277
  if (!ctx.mediaUrl) throw new Error("Synology Chat media send requires mediaUrl");
@@ -1,2 +1,2 @@
1
- import { t as synologyChatPlugin } from "./channel-C6bhvNZi.js";
1
+ import { t as synologyChatPlugin } from "./channel-Hiv3SX8l.js";
2
2
  export { synologyChatPlugin };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as synologyChatSetupWizard, t as synologyChatSetupAdapter } from "./setup-surface-pMbKFqU5.js";
1
+ import { n as synologyChatSetupWizard, t as synologyChatSetupAdapter } from "./setup-surface-CJtfU_Gw.js";
2
2
  export { synologyChatSetupAdapter, synologyChatSetupWizard };
@@ -1,8 +1,8 @@
1
1
  import { parseStrictInteger } from "openclaw/plugin-sdk/number-runtime";
2
2
  import { normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
3
- import { DEFAULT_ACCOUNT_ID, listCombinedAccountIds, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
3
+ import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, listCombinedAccountIds, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
4
4
  import { resolveDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
5
- import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, createAllowFromSection, createSetupTranslator, createStandardChannelSetupStatus, formatDocsLink, mergeAllowFromEntries, normalizeAccountId, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
5
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, createAllowFromSection, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, mergeAllowFromEntries, normalizeAccountId, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
6
6
  //#region extensions/synology-chat/src/accounts.ts
7
7
  /**
8
8
  * Account resolution: reads config from channels.synology-chat,
@@ -13,7 +13,7 @@ function getChannelConfig$1(cfg) {
13
13
  return cfg?.channels?.["synology-chat"];
14
14
  }
15
15
  function resolveImplicitAccountId(channelCfg) {
16
- return channelCfg.token || process.env.SYNOLOGY_CHAT_TOKEN ? DEFAULT_ACCOUNT_ID : void 0;
16
+ return hasConfiguredAccountValue(channelCfg.token) || hasConfiguredAccountValue(process.env.SYNOLOGY_CHAT_TOKEN) ? DEFAULT_ACCOUNT_ID : void 0;
17
17
  }
18
18
  function getRawAccountConfig$1(channelCfg, accountId) {
19
19
  if (accountId === DEFAULT_ACCOUNT_ID) return channelCfg;
@@ -238,8 +238,9 @@ const synologyChatSetupWizard = {
238
238
  title: t("wizard.synologyChat.setupTitle"),
239
239
  lines: SYNOLOGY_SETUP_HELP_LINES
240
240
  },
241
- credentials: [{
241
+ credentials: [defineTokenCredential({
242
242
  inputKey: "token",
243
+ configKey: "token",
243
244
  providerHint: channel,
244
245
  credentialLabel: "outgoing webhook token",
245
246
  preferredEnvVar: "SYNOLOGY_CHAT_TOKEN",
@@ -249,30 +250,25 @@ const synologyChatSetupWizard = {
249
250
  keepPrompt: t("wizard.synologyChat.tokenKeep"),
250
251
  inputPrompt: t("wizard.synologyChat.tokenInput"),
251
252
  allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1,
252
- inspect: ({ cfg, accountId }) => {
253
- const account = resolveAccount(cfg, accountId);
254
- const raw = getRawAccountConfig(cfg, accountId);
255
- return {
256
- accountConfigured: isSynologyChatConfigured(cfg, accountId),
257
- hasConfiguredValue: Boolean(normalizeOptionalString(raw.token)),
258
- resolvedValue: normalizeOptionalString(account.token),
259
- envValue: accountId === DEFAULT_ACCOUNT_ID$1 ? normalizeOptionalString(process.env.SYNOLOGY_CHAT_TOKEN) : void 0
260
- };
261
- },
262
- applyUseEnv: async ({ cfg, accountId }) => patchSynologyChatAccountConfig({
263
- cfg,
264
- accountId,
265
- enabled: true,
266
- clearFields: ["token"],
267
- patch: {}
253
+ resolveAccount: ({ cfg, accountId }) => ({
254
+ config: getRawAccountConfig(cfg, accountId),
255
+ resolved: resolveAccount(cfg, accountId),
256
+ configured: isSynologyChatConfigured(cfg, accountId)
268
257
  }),
269
- applySet: async ({ cfg, accountId, resolvedValue }) => patchSynologyChatAccountConfig({
258
+ accountConfigured: (account) => account.configured,
259
+ hasConfiguredValue: (account) => Boolean(normalizeOptionalString(account.config.token)),
260
+ resolvedValue: (account) => normalizeOptionalString(account.resolved.token),
261
+ envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1 ? normalizeOptionalString(process.env.SYNOLOGY_CHAT_TOKEN) : void 0,
262
+ patchAccount: ({ cfg, accountId, patch, clearFields }) => patchSynologyChatAccountConfig({
270
263
  cfg,
271
264
  accountId,
272
265
  enabled: true,
273
- patch: { token: resolvedValue }
274
- })
275
- }],
266
+ clearFields,
267
+ patch
268
+ }),
269
+ useEnv: { clearFields: ["token"] },
270
+ set: { value: "resolved" }
271
+ })],
276
272
  textInputs: [{
277
273
  inputKey: "url",
278
274
  message: t("wizard.synologyChat.incomingWebhookUrlPrompt"),
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/synology-chat",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/synology-chat",
9
- "version": "2026.7.1",
9
+ "version": "2026.7.2-beta.2",
10
10
  "dependencies": {
11
11
  "zod": "4.4.3"
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/synology-chat",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "description": "Synology Chat channel plugin for OpenClaw channels and direct messages.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,10 +27,10 @@
27
27
  "minHostVersion": ">=2026.4.10"
28
28
  },
29
29
  "compat": {
30
- "pluginApi": ">=2026.7.1"
30
+ "pluginApi": ">=2026.7.2-beta.2"
31
31
  },
32
32
  "build": {
33
- "openclawVersion": "2026.7.1"
33
+ "openclawVersion": "2026.7.2-beta.2"
34
34
  },
35
35
  "release": {
36
36
  "publishToClawHub": true,
@@ -51,7 +51,7 @@
51
51
  "README.md"
52
52
  ],
53
53
  "peerDependencies": {
54
- "openclaw": ">=2026.7.1"
54
+ "openclaw": ">=2026.7.2-beta.2"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
57
  "openclaw": {