openclaw-openagent 1.0.6 → 1.0.8

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.
@@ -235,27 +235,28 @@ const toolcardRender = {
235
235
  if (hasManualCollapseWrapper) {
236
236
  const wrapperFn = findFunctionByAnchor(content, wrapperAnchor);
237
237
  if (!wrapperFn) {
238
- logger.warn('[ui-ext:regex] toolcard-render: manual collapse wrapper function not found');
239
- return false;
238
+ logger.warn('[ui-ext:regex] toolcard-render: manual collapse wrapper function not found, skipping wrapper patch');
240
239
  }
241
- if (!wrapperFn.body.includes('chat-tool-msg-body') || !wrapperFn.body.includes('onToggleExpanded')) {
242
- logger.warn('[ui-ext:regex] toolcard-render: manual collapse wrapper shape mismatch');
243
- return false;
240
+ else if (!wrapperFn.body.includes('chat-tool-msg-body') || !wrapperFn.body.includes('onToggleExpanded')) {
241
+ logger.warn('[ui-ext:regex] toolcard-render: manual collapse wrapper shape mismatch, skipping wrapper patch');
244
242
  }
245
- const wrapperReturnTagMatch = wrapperFn.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
246
- if (!wrapperReturnTagMatch) {
247
- logger.warn('[ui-ext:regex] toolcard-render: manual collapse Lit html tag not found');
248
- return false;
243
+ else {
244
+ const wrapperReturnTagMatch = wrapperFn.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
245
+ if (!wrapperReturnTagMatch) {
246
+ logger.warn('[ui-ext:regex] toolcard-render: manual collapse Lit html tag not found, skipping wrapper patch');
247
+ }
248
+ else {
249
+ const wrapperHtmlVar = wrapperReturnTagMatch[1];
250
+ const wrapperParams = wrapperFn.params.split(',').map((s) => s.trim());
251
+ const wrapperCardParam = wrapperParams[0] || 'e';
252
+ const wrapperOptionsParam = wrapperParams[1] || '';
253
+ const wrapperSidebarArg = wrapperOptionsParam ? `${wrapperOptionsParam}&&${wrapperOptionsParam}.onOpenSidebar` : 'undefined';
254
+ const wrapperHook = `${marker}if(window.__openagentRenderToolCard){let __cl=window.__openagentRenderToolCard(${wrapperCardParam},${wrapperSidebarArg});if(__cl)return ${wrapperHtmlVar}\`\${__cl}\`}`;
255
+ content = content.substring(0, wrapperFn.bodyStart) + wrapperHook + content.substring(wrapperFn.bodyStart);
256
+ patchedWrapper = true;
257
+ logger.info(`[ui-ext:regex] toolcard-render: patched manual wrapper ${wrapperFn.funcName}(${wrapperFn.params.substring(0, 20)}) html=${wrapperHtmlVar}`);
258
+ }
249
259
  }
250
- const wrapperHtmlVar = wrapperReturnTagMatch[1];
251
- const wrapperParams = wrapperFn.params.split(',').map((s) => s.trim());
252
- const wrapperCardParam = wrapperParams[0] || 'e';
253
- const wrapperOptionsParam = wrapperParams[1] || '';
254
- const wrapperSidebarArg = wrapperOptionsParam ? `${wrapperOptionsParam}&&${wrapperOptionsParam}.onOpenSidebar` : 'undefined';
255
- const wrapperHook = `${marker}if(window.__openagentRenderToolCard){let __cl=window.__openagentRenderToolCard(${wrapperCardParam},${wrapperSidebarArg});if(__cl)return ${wrapperHtmlVar}\`\${__cl}\`}`;
256
- content = content.substring(0, wrapperFn.bodyStart) + wrapperHook + content.substring(wrapperFn.bodyStart);
257
- patchedWrapper = true;
258
- logger.info(`[ui-ext:regex] toolcard-render: patched manual wrapper ${wrapperFn.funcName}(${wrapperFn.params.substring(0, 20)}) html=${wrapperHtmlVar}`);
259
260
  }
260
261
  // 4.11+ also renders tool-result messages through the grouped message
261
262
  // renderer itself instead of nb(). That path only calls the inner ToolCard
@@ -267,12 +268,12 @@ const toolcardRender = {
267
268
  let toolMessageOk = true;
268
269
  const toolMessageFn = findFunctionByAnchor(content, toolMessageAnchor, 8000);
269
270
  if (!toolMessageFn) {
270
- logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper function not found');
271
+ logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper function not found, skipping');
271
272
  toolMessageOk = false;
272
273
  }
273
274
  if (toolMessageOk && (!toolMessageFn.body.includes('chat-tool-msg-collapse--manual')
274
275
  || !toolMessageFn.body.includes('chat-tool-msg-body'))) {
275
- logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper shape mismatch');
276
+ logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper shape mismatch, skipping');
276
277
  toolMessageOk = false;
277
278
  }
278
279
  let toolMessageHtmlVar = '';
@@ -287,35 +288,56 @@ const toolcardRender = {
287
288
  }
288
289
  }
289
290
  if (toolMessageOk) {
291
+ // 双模式匹配单卡 guard:
292
+ // Shape A (2026.4.x): ,Z=X.length===1?X[0]:null,Y=Z&&!A&&!B?
293
+ // Shape B (2026.5.x): ,N=u.length===1?u[0]:null,...ae=P?`Tool error`:ne&&!C&&!h?ne:...
290
294
  const singleCardMatch = toolMessageFn.body.match(/(?:let\s+|,)([a-zA-Z_$]\w*)=([a-zA-Z_$]\w*)\.length===1\?\2\[0\]:null,\w+=\1&&!([a-zA-Z_$]\w*)&&!([a-zA-Z_$]\w*)\?/);
291
- if (!singleCardMatch) {
292
- logger.warn('[ui-ext:regex] toolcard-render: tool message single-card guard not found');
295
+ let cardsVar = '';
296
+ let mediaVar = '';
297
+ if (singleCardMatch) {
298
+ // Shape A: old bundle
299
+ cardsVar = singleCardMatch[2];
300
+ mediaVar = singleCardMatch[4];
301
+ }
302
+ else {
303
+ // Shape B: 2026.5.x bundle
304
+ const shapeB = toolMessageFn.body.match(/,(?:let\s+)?([a-zA-Z_$]\w*)=([a-zA-Z_$]\w*)\.length===1\?\2\[0\]:null/);
305
+ if (!shapeB) {
306
+ logger.warn('[ui-ext:regex] toolcard-render: tool message single-card guard not found');
307
+ toolMessageOk = false;
308
+ }
309
+ else {
310
+ cardsVar = shapeB[2];
311
+ // 从 ae=P?`Tool error`:ne&&!C&&!h 中提取排除变量 C, h
312
+ const aeMatch = toolMessageFn.body.match(/,\w+=\w+\?`Tool error`:(\w+)&&!(\w+)&&!(\w+)\?/);
313
+ if (aeMatch) {
314
+ mediaVar = `(${aeMatch[2]}||${aeMatch[3]})`;
315
+ }
316
+ else {
317
+ mediaVar = 'false';
318
+ }
319
+ }
320
+ }
321
+ const chatBubbleClassMatch = toolMessageFn.body.match(/[,;]([a-zA-Z_$]\w*)=\[`chat-bubble`,/);
322
+ if (!chatBubbleClassMatch) {
323
+ logger.warn('[ui-ext:regex] toolcard-render: tool message chat-bubble class var not found');
293
324
  toolMessageOk = false;
294
325
  }
295
326
  if (toolMessageOk) {
296
- const cardsVar = singleCardMatch[2];
297
- const mediaVar = singleCardMatch[4];
298
- const chatBubbleClassMatch = toolMessageFn.body.match(/[,;]([a-zA-Z_$]\w*)=\[`chat-bubble`,/);
299
- if (!chatBubbleClassMatch) {
300
- logger.warn('[ui-ext:regex] toolcard-render: tool message chat-bubble class var not found');
327
+ const chatBubbleClassVar = chatBubbleClassMatch[1];
328
+ const toolMessageParams = toolMessageFn.params.split(',').map((s) => s.trim());
329
+ const toolMessageSidebarArg = toolMessageParams[3] || 'undefined';
330
+ const toolMessageReturnIdx = toolMessageFn.body.indexOf(`return ${toolMessageHtmlVar}\``);
331
+ if (toolMessageReturnIdx < 0) {
332
+ logger.warn('[ui-ext:regex] toolcard-render: tool message return anchor not found');
301
333
  toolMessageOk = false;
302
334
  }
303
335
  if (toolMessageOk) {
304
- const chatBubbleClassVar = chatBubbleClassMatch[1];
305
- const toolMessageParams = toolMessageFn.params.split(',').map((s) => s.trim());
306
- const toolMessageSidebarArg = toolMessageParams[3] || 'undefined';
307
- const toolMessageReturnIdx = toolMessageFn.body.indexOf(`return ${toolMessageHtmlVar}\``);
308
- if (toolMessageReturnIdx < 0) {
309
- logger.warn('[ui-ext:regex] toolcard-render: tool message return anchor not found');
310
- toolMessageOk = false;
311
- }
312
- if (toolMessageOk) {
313
- const toolMessageHook = `${marker}let __openagentToolMsgCard=null;if(!${mediaVar}&&window.__openagentRenderToolCard){for(let __openagentI=${cardsVar}.length-1;__openagentI>=0&&!__openagentToolMsgCard;__openagentI--){let __openagentCandidate=${cardsVar}[__openagentI];if(__openagentCandidate&&(__openagentCandidate.outputText||__openagentCandidate.text))__openagentToolMsgCard=window.__openagentRenderToolCard(__openagentCandidate,${toolMessageSidebarArg})}for(let __openagentI=0;__openagentI<${cardsVar}.length&&!__openagentToolMsgCard;__openagentI++){__openagentToolMsgCard=window.__openagentRenderToolCard(${cardsVar}[__openagentI],${toolMessageSidebarArg})}}if(__openagentToolMsgCard)return ${toolMessageHtmlVar}\`<div class="\${${chatBubbleClassVar}}">\${__openagentToolMsgCard}</div>\`;`;
314
- const toolMessageInsertAt = toolMessageFn.bodyStart + toolMessageReturnIdx;
315
- content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
316
- patchedToolMessageWrapper = true;
317
- logger.info(`[ui-ext:regex] toolcard-render: patched tool message wrapper ${toolMessageFn.funcName}(${toolMessageFn.params.substring(0, 20)}) html=${toolMessageHtmlVar}`);
318
- }
336
+ const toolMessageHook = `${marker}let __openagentToolMsgCard=null;console.log('[openagent] toolMsg hook: cards='+${cardsVar}.length+' mediaVar='+${mediaVar});if(!${mediaVar}&&window.__openagentRenderToolCard){for(let __openagentI=${cardsVar}.length-1;__openagentI>=0&&!__openagentToolMsgCard;__openagentI--){let __openagentCandidate=${cardsVar}[__openagentI];if(__openagentCandidate&&(__openagentCandidate.outputText||__openagentCandidate.text))__openagentToolMsgCard=window.__openagentRenderToolCard(__openagentCandidate,${toolMessageSidebarArg})}for(let __openagentI=0;__openagentI<${cardsVar}.length&&!__openagentToolMsgCard;__openagentI++){__openagentToolMsgCard=window.__openagentRenderToolCard(${cardsVar}[__openagentI],${toolMessageSidebarArg})}}if(__openagentToolMsgCard)return ${toolMessageHtmlVar}\`<div class="\${${chatBubbleClassVar}}">\${__openagentToolMsgCard}</div>\`;`;
337
+ const toolMessageInsertAt = toolMessageFn.bodyStart + toolMessageReturnIdx;
338
+ content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
339
+ patchedToolMessageWrapper = true;
340
+ logger.info(`[ui-ext:regex] toolcard-render: patched tool message wrapper ${toolMessageFn.funcName}(${toolMessageFn.params.substring(0, 20)}) html=${toolMessageHtmlVar}`);
319
341
  }
320
342
  }
321
343
  }
@@ -613,11 +635,36 @@ const remoteAgentMessageShortCircuit = {
613
635
  logger.info(`[ui-ext:regex] remote-agent-msg: patched (strategy C, sc=${scVar})`);
614
636
  return true;
615
637
  }
638
+ // Strategy D (v5.17+ fallback): search for any if-condition referencing
639
+ // hasToolCards alongside a role-like variable — v5.17+ may replace the
640
+ // ternary/compound patterns of A/B/C with a different expression shape.
641
+ const strategyDRe = new RegExp(`if\\(!` + `([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*&&\\s*` +
642
+ hasToolCardsVar + `\\s*&&\\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*\\)`);
643
+ const strategyDMatch = content.match(strategyDRe);
644
+ if (strategyDMatch) {
645
+ const markdownVarD = strategyDMatch[1];
646
+ const isToolResultVarD = strategyDMatch[2];
647
+ const dMatchFull = strategyDMatch[0];
648
+ const dIdx = strategyDMatch.index;
649
+ const patched = `if(` + marker +
650
+ `(!` + markdownVarD + `||window.__openagentOwnsToolCards&&window.__openagentOwnsToolCards(` +
651
+ toolCardsVar + `))&&` + hasToolCardsVar + `&&` + isToolResultVarD + `)`;
652
+ content = content.substring(0, dIdx) + patched + content.substring(dIdx + dMatchFull.length);
653
+ writeFileSync(bundlePath, content, 'utf-8');
654
+ logger.info(`[ui-ext:regex] remote-agent-msg: patched (strategy D, md=${markdownVarD})`);
655
+ return true;
656
+ }
616
657
  // 策略B (v4.11~v4.1x fallback): compound = hasToolCards && (showToolCalls ?? !0)
617
658
  const compoundRe = new RegExp(`(\\w+)=${hasToolCardsVar}&&\\(\\w+\\.showToolCalls\\?\\?!0\\)`);
618
659
  const compoundMatch = content.match(compoundRe);
619
660
  if (!compoundMatch) {
620
- logger.warn('[ui-ext:regex] remote-agent-msg: all strategies (A/C/B) failed');
661
+ // All strategies (A/C/D/B) failed — dump context for diagnostics
662
+ const anchorHintIdx = content.indexOf(hasToolCardsVar);
663
+ const ctxStart = anchorHintIdx > 200 ? anchorHintIdx - 200 : 0;
664
+ const ctxEnd = Math.min(anchorHintIdx + 200, content.length);
665
+ logger.warn(`[ui-ext:regex] remote-agent-msg: all strategies (A/C/D/B) failed for toolCards=${toolCardsVar}. ` +
666
+ `Context around hasToolCardsVar (at ${anchorHintIdx}): ` +
667
+ content.substring(ctxStart, ctxEnd));
621
668
  return false;
622
669
  }
623
670
  const compoundVar = compoundMatch[1];
@@ -670,14 +717,15 @@ const mentionHooks = {
670
717
  const markerA = makeMarker(this.id);
671
718
  const hookA = `${markerA}if(window.__clMention?.onKeyDown(${eventParam}))return;`;
672
719
  content = content.substring(0, keydownInsertIdx) + hookA + content.substring(keydownInsertIdx);
673
- // 注入点 B: handleInput — 支持三种形态
720
+ // 注入点 B: handleInput — 四种形态兼容
674
721
  // Shape A (old): .onDraftChange(target.value)}}
675
722
  // Shape B (4.11~4.x): .onDraftChange(target.value) (arrow function 内)
676
- // Shape C (2026.6.x): be=t=>{let n=t.target;pL(e,n.value)} — 引入 pL helper
677
- // arrow body 末尾不再出现 onDraftChange 字面
723
+ // Shape C (2026.6.x): be=t=>{let n=t.target;pL(e,n.value)} — 引入 pL helper
724
+ // Shape D (generic): .onDraftChange(任意.target.value) 不加结尾限制
678
725
  const inputReA = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)\}\}/;
679
726
  const inputReB = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)/;
680
727
  const inputReC = /let ([a-zA-Z_$][a-zA-Z0-9_$]*)=[a-zA-Z_$][a-zA-Z0-9_$]*\.target;[a-zA-Z_$][a-zA-Z0-9_$]*\([a-zA-Z_$][a-zA-Z0-9_$]*,\1\.value\)\}/;
728
+ const inputReD = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.target\.value\)/;
681
729
  let inputMatch = content.match(inputReA);
682
730
  let inputShape = 'A';
683
731
  if (!inputMatch) {
@@ -689,12 +737,22 @@ const mentionHooks = {
689
737
  inputShape = 'B';
690
738
  }
691
739
  if (!inputMatch) {
692
- logger.warn('[ui-ext:regex] mention-hooks: input anchor not found (Shape A/B/C all missed)');
740
+ inputMatch = content.match(inputReD);
741
+ inputShape = 'D';
742
+ }
743
+ if (!inputMatch) {
744
+ // All shapes missed — dump context for diagnostics
745
+ const draftHintIdx = content.indexOf('.onDraftChange');
746
+ const ctxStart = draftHintIdx > 200 ? draftHintIdx - 200 : 0;
747
+ const ctxEnd = Math.min(draftHintIdx + 200, content.length);
748
+ logger.warn(`[ui-ext:regex] mention-hooks: input anchor not found (Shape A/B/C/D all missed). ` +
749
+ `Context around onDraftChange (at ${draftHintIdx}): ` +
750
+ content.substring(ctxStart, ctxEnd));
693
751
  return false;
694
752
  }
695
753
  const targetVar = inputMatch[1];
696
754
  const inputAnchorIdx = content.indexOf(inputMatch[0]);
697
- // Shape A: }} 之前;Shape C: 末尾 } 之前;Shape B: ) 之后
755
+ // Shape A: }} 之前;Shape C: 末尾 } 之前;Shape B/D: ) 之后
698
756
  const draftCallEnd = inputShape === 'A'
699
757
  ? inputAnchorIdx + inputMatch[0].length - 2
700
758
  : inputShape === 'C'
@@ -5,3 +5,11 @@
5
5
  * Used by remote-agent-tool (WebUI continuation) and download-file-tool (artifact download).
6
6
  */
7
7
  export declare function resolveOasnAccessUrl(accessApiBase: string | undefined, url: string): string;
8
+ /**
9
+ * Resolve a WebUI continuation URL for user-facing browser clicks.
10
+ *
11
+ * WebUI auth is Owner-cookie based, so the clickable URL must use the same
12
+ * public origin as the claim/login flow. API-key calls can use an internal
13
+ * Access base, but browser links cannot reuse cookies across IP/domain origins.
14
+ */
15
+ export declare function resolveOasnWebuiUrl(accessApiBase: string | undefined, claimUrl: string | undefined, url: string): string;
@@ -18,3 +18,43 @@ export function resolveOasnAccessUrl(accessApiBase, url) {
18
18
  return url;
19
19
  }
20
20
  }
21
+ /**
22
+ * Resolve a WebUI continuation URL for user-facing browser clicks.
23
+ *
24
+ * WebUI auth is Owner-cookie based, so the clickable URL must use the same
25
+ * public origin as the claim/login flow. API-key calls can use an internal
26
+ * Access base, but browser links cannot reuse cookies across IP/domain origins.
27
+ */
28
+ export function resolveOasnWebuiUrl(accessApiBase, claimUrl, url) {
29
+ if (!url)
30
+ return url;
31
+ const claimOrigin = originOf(claimUrl);
32
+ if (!claimOrigin || !isWebuiUrl(url, accessApiBase || claimOrigin)) {
33
+ return resolveOasnAccessUrl(accessApiBase, url);
34
+ }
35
+ try {
36
+ const absolute = new URL(url, accessApiBase || claimOrigin);
37
+ return new URL(`${absolute.pathname}${absolute.search}${absolute.hash}`, claimOrigin).toString();
38
+ }
39
+ catch {
40
+ return resolveOasnAccessUrl(accessApiBase, url);
41
+ }
42
+ }
43
+ function originOf(value) {
44
+ if (!value)
45
+ return undefined;
46
+ try {
47
+ return new URL(value).origin;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ function isWebuiUrl(url, base) {
54
+ try {
55
+ return new URL(url, base).pathname.startsWith('/webui/');
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-openagent",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
5
  "description": "OpenAgent channel plugin for OpenClaw",
6
6
  "license": "MIT",
@@ -29,7 +29,7 @@ import { sendC2CCustomMessage, waitForC2CReply } from '../tim/c2c.js';
29
29
  import type { OasnTransport, TaskHandle, TaskRequest } from '../transport/types.js';
30
30
  import fs from 'node:fs';
31
31
  import nodePath from 'node:path';
32
- import { resolveOasnAccessUrl } from '../util/url-resolver.js';
32
+ import { resolveOasnAccessUrl, resolveOasnWebuiUrl } from '../util/url-resolver.js';
33
33
 
34
34
  // ── Helpers ──
35
35
 
@@ -242,7 +242,7 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
242
242
  // listUnsettledInvocations() 续轮询,避免重复扣费
243
243
  const requestId = generateRequestId();
244
244
  const TIMEOUT_MS = 15 * 60 * 1000; // 15 分钟:远程 agent 可能跑长工具
245
- const OASN_INVOCATION_TIMEOUT_MS = 60_000; // compute02 REL 当前允许的 ServiceAgent 执行预算
245
+ const OASN_INVOCATION_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 小时
246
246
 
247
247
  logger.info(
248
248
  `[remote-tool] Calling remote agent: agentId=${agentId} requestId=${requestId} ` +
@@ -350,7 +350,8 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
350
350
  const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
351
351
  if (result.webuiContinuation?.launchUrl) {
352
352
  const rawLink = result.webuiContinuation.launchUrl;
353
- const link = resolveOasnAccessUrl(accessBase, rawLink);
353
+ const claimUrl = rt.config?.claimUrl;
354
+ const link = resolveOasnWebuiUrl(accessBase, claimUrl, rawLink);
354
355
  const label = result.webuiContinuation.displayName || 'Open WebUI';
355
356
  content = content.trim()
356
357
  ? `${content}\n\n${label}: ${link}`
package/src/compat.ts CHANGED
@@ -15,6 +15,9 @@ export const PLUGIN_VERSION = '1.0.0';
15
15
 
16
16
  export const SUPPORTED_HOST_MIN = '2026.3.22';
17
17
 
18
+ /** Plugin has been verified up to this OpenClaw host version */
19
+ export const MAX_TESTED_VERSION = '2026.6.30';
20
+
18
21
  export interface OpenClawVersion {
19
22
  year: number;
20
23
  month: number;
@@ -71,6 +74,15 @@ export function assertHostCompatibility(hostVersion: string | undefined): void {
71
74
  }
72
75
  if (isHostVersionSupported(hostVersion)) {
73
76
  logger.info(`[compat] Host OpenClaw ${hostVersion} >= ${SUPPORTED_HOST_MIN}, OK.`);
77
+ // Warn but don't block for versions beyond tested range
78
+ const host = parseOpenClawVersion(hostVersion);
79
+ const max = parseOpenClawVersion(MAX_TESTED_VERSION);
80
+ if (host && max && compareVersions(host, max) > 0) {
81
+ logger.warn(
82
+ `[compat] Host OpenClaw ${hostVersion} is beyond max tested version ${MAX_TESTED_VERSION}. ` +
83
+ `The plugin may encounter compatibility issues.`,
84
+ );
85
+ }
74
86
  return;
75
87
  }
76
88
  throw new Error(