openclaw-openagent 1.0.2 → 1.0.4

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.
@@ -71,7 +71,7 @@ export function createDownloadFileTool() {
71
71
  return fail('OASN transport not ready');
72
72
  }
73
73
  // ② Resolve download URL
74
- const accessBase = rt.config?.accessApiBase;
74
+ const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
75
75
  const downloadUrl = resolveOasnAccessUrl(accessBase, contentUrl);
76
76
  logger.info(`[download-tool] Downloading artifact: url=${downloadUrl} saveTo=${savePath}`);
77
77
  // ③ Download
@@ -290,7 +290,7 @@ export function createCallRemoteAgentTool() {
290
290
  // 在 visible_result.webui_continuation 里返回可继续交互的入口。
291
291
  if (result.webuiContinuation?.launchUrl) {
292
292
  const rawLink = result.webuiContinuation.launchUrl;
293
- const accessBase = rt.config?.accessApiBase;
293
+ const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
294
294
  const link = resolveOasnAccessUrl(accessBase, rawLink);
295
295
  const label = result.webuiContinuation.displayName || 'Open WebUI';
296
296
  content = content.trim()
@@ -2,7 +2,7 @@
2
2
  * OpenAgent UI Override — Auto-generated, do not edit directly!
3
3
  *
4
4
  * Source: src/plugin-ui/modules/
5
- * Built: 2026-06-25T03:47:26.820Z
5
+ * Built: 2026-06-25T07:10:51.968Z
6
6
  *
7
7
  * To modify, edit the source modules and run:
8
8
  * npm run build:override
@@ -2891,7 +2891,16 @@ liveTextarea.dispatchEvent(new Event('change', { bubbles: true }));
2891
2891
  // tryInject() 只做 DOM 注入(@ 按钮 + CSS class),不绑定任何事件。
2892
2892
 
2893
2893
  // ── textarea 选择器(全局复用)──
2894
- const _CL_TEXTAREA_SELECTOR = 'textarea[placeholder*="Message"], textarea[placeholder*="Enter to send"]';
2894
+ // 多策略回退:v3 "Message..." / v4 "发消息..." / 结构化选择器
2895
+ const _CL_TEXTAREA_SELECTOR = [
2896
+ 'textarea[placeholder*="发消息"]',
2897
+ 'textarea[placeholder*="Message"]',
2898
+ 'textarea[placeholder*="Enter to send"]',
2899
+ '.agent-chat__composer-combobox textarea',
2900
+ '.chat-composer textarea',
2901
+ '.chat-input textarea',
2902
+ '[class*="composer"] textarea:not([placeholder*="Search"])',
2903
+ ].join(', ');
2895
2904
 
2896
2905
  function _getTextarea() {
2897
2906
  return document.querySelector(_CL_TEXTAREA_SELECTOR);
@@ -2929,7 +2938,8 @@ function _isLikelyNativeSendButton(target, textarea) {
2929
2938
  if (!button || !textarea) return false;
2930
2939
  if (button.classList && button.classList.contains('openagent-at-btn')) return false;
2931
2940
 
2932
- const inputContainer = textarea.closest('.chat-input')
2941
+ const inputContainer = textarea.closest('.agent-chat__composer-combobox')
2942
+ || textarea.closest('.chat-input')
2933
2943
  || textarea.closest('[class*="chat-composer"]')
2934
2944
  || textarea.closest('[class*="chat"]')
2935
2945
  || textarea.parentElement;
@@ -3086,7 +3096,8 @@ function tryInject() {
3086
3096
  }
3087
3097
 
3088
3098
  // ── 给输入区域加 position: relative 包裹层 ──
3089
- const inputContainer = textarea.closest('.chat-input')
3099
+ const inputContainer = textarea.closest('.agent-chat__composer-combobox')
3100
+ || textarea.closest('.chat-input')
3090
3101
  || textarea.closest('[class*="chat-composer"]')
3091
3102
  || textarea.closest('[class*="chat"]')
3092
3103
  || textareaParent?.parentElement
@@ -264,47 +264,61 @@ const toolcardRender = {
264
264
  const toolMessageAnchor = 'toolmsg:${';
265
265
  let patchedToolMessageWrapper = false;
266
266
  if (content.includes(toolMessageAnchor)) {
267
+ let toolMessageOk = true;
267
268
  const toolMessageFn = findFunctionByAnchor(content, toolMessageAnchor, 8000);
268
269
  if (!toolMessageFn) {
269
270
  logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper function not found');
270
- return false;
271
+ toolMessageOk = false;
271
272
  }
272
- if (!toolMessageFn.body.includes('chat-tool-msg-collapse--manual')
273
- || !toolMessageFn.body.includes('chat-tool-msg-body')) {
273
+ if (toolMessageOk && (!toolMessageFn.body.includes('chat-tool-msg-collapse--manual')
274
+ || !toolMessageFn.body.includes('chat-tool-msg-body'))) {
274
275
  logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper shape mismatch');
275
- return false;
276
+ toolMessageOk = false;
276
277
  }
277
- const toolMessageReturnTagMatch = toolMessageFn.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
278
- if (!toolMessageReturnTagMatch) {
279
- logger.warn('[ui-ext:regex] toolcard-render: tool message Lit html tag not found');
280
- return false;
281
- }
282
- const toolMessageHtmlVar = toolMessageReturnTagMatch[1];
283
- 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*)\?/);
284
- if (!singleCardMatch) {
285
- logger.warn('[ui-ext:regex] toolcard-render: tool message single-card guard not found');
286
- return false;
287
- }
288
- const cardsVar = singleCardMatch[2];
289
- const mediaVar = singleCardMatch[4];
290
- const chatBubbleClassMatch = toolMessageFn.body.match(/[,;]([a-zA-Z_$]\w*)=\[`chat-bubble`,/);
291
- if (!chatBubbleClassMatch) {
292
- logger.warn('[ui-ext:regex] toolcard-render: tool message chat-bubble class var not found');
293
- return false;
278
+ let toolMessageHtmlVar = '';
279
+ if (toolMessageOk) {
280
+ const toolMessageReturnTagMatch = toolMessageFn.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
281
+ if (!toolMessageReturnTagMatch) {
282
+ logger.warn('[ui-ext:regex] toolcard-render: tool message Lit html tag not found');
283
+ toolMessageOk = false;
284
+ }
285
+ else {
286
+ toolMessageHtmlVar = toolMessageReturnTagMatch[1];
287
+ }
294
288
  }
295
- const chatBubbleClassVar = chatBubbleClassMatch[1];
296
- const toolMessageParams = toolMessageFn.params.split(',').map((s) => s.trim());
297
- const toolMessageSidebarArg = toolMessageParams[3] || 'undefined';
298
- const toolMessageReturnIdx = toolMessageFn.body.indexOf(`return ${toolMessageHtmlVar}\``);
299
- if (toolMessageReturnIdx < 0) {
300
- logger.warn('[ui-ext:regex] toolcard-render: tool message return anchor not found');
301
- return false;
289
+ if (toolMessageOk) {
290
+ 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');
293
+ toolMessageOk = false;
294
+ }
295
+ 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');
301
+ toolMessageOk = false;
302
+ }
303
+ 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
+ }
319
+ }
320
+ }
302
321
  }
303
- 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>\`;`;
304
- const toolMessageInsertAt = toolMessageFn.bodyStart + toolMessageReturnIdx;
305
- content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
306
- patchedToolMessageWrapper = true;
307
- logger.info(`[ui-ext:regex] toolcard-render: patched tool message wrapper ${toolMessageFn.funcName}(${toolMessageFn.params.substring(0, 20)}) html=${toolMessageHtmlVar}`);
308
322
  }
309
323
  writeFileSync(bundlePath, content, 'utf-8');
310
324
  logger.info(`[ui-ext:regex] toolcard-render: patched ${funcName}(${params.substring(0, 20)}) html=${litHtmlVar}${patchedWrapper ? ' + wrapper' : ''}${patchedToolMessageWrapper ? ' + tool-message' : ''}`);
@@ -411,7 +425,7 @@ const toolcallidPropagate = {
411
425
  logger.debug(`[ui-ext:regex] toolcallid-propagate: mVar=${mVar} cards=${cardsVar} callLoop=${callLoopVar} resultLoop=${resultLoopVar}`);
412
426
  // 计算注入内容
413
427
  const marker = makeMarker(this.id);
414
- const tcidCapture = `${marker}var __tcid=(typeof ${mVar}.toolCallId==='string'&&${mVar}.toolCallId)||(typeof ${mVar}.tool_call_id==='string'&&${mVar}.tool_call_id)||'';`;
428
+ const tcidCapture = `${marker}var __tcid=(typeof ${msgParam}.toolCallId==='string'&&${msgParam}.toolCallId)||(typeof ${msgParam}.tool_call_id==='string'&&${msgParam}.tool_call_id)||'';`;
415
429
  const inlinePatchCall = callLoopVar
416
430
  ? `,toolCallId:__tcid||${callLoopVar}.id||${callLoopVar}.toolCallId||${callLoopVar}.tool_call_id||''`
417
431
  : `,toolCallId:__tcid`;
@@ -423,16 +437,16 @@ const toolcallidPropagate = {
423
437
  const arrInitPattern = `,${cardsVar}=[];`;
424
438
  let arrInitIdx = funcBody.indexOf(arrInitPattern);
425
439
  if (arrInitIdx < 0) {
426
- // v4.11+: 可能是 ,i=[](后接分号或逗号之前的位置不同)
440
+ // v4.11+: 可能是 ,i=[](后接逗号,如 a=[],o=...),不能直接插 var
427
441
  const altPattern = `,${cardsVar}=[]`;
428
442
  arrInitIdx = funcBody.indexOf(altPattern);
429
443
  if (arrInitIdx < 0) {
430
444
  logger.warn('[ui-ext:regex] toolcallid-propagate: cards init not found');
431
445
  return false;
432
446
  }
433
- // 找到 ] 后面的分隔符位置
434
- arrInitIdx += altPattern.length;
435
- // captureInsertOffset ; 或 , 之后
447
+ // altPattern 后面是逗号(逗号表达式),var 声明无法插入此处
448
+ // 改为插入到函数体开头,利用 var 提升特性
449
+ arrInitIdx = 0;
436
450
  }
437
451
  else {
438
452
  arrInitIdx += arrInitPattern.length;
@@ -630,7 +644,7 @@ const remoteAgentMessageShortCircuit = {
630
644
  // ── Extension 7: mention-hooks ───────────────────────────────────────────
631
645
  const mentionHooks = {
632
646
  id: 'mention-hooks',
633
- version: 2,
647
+ version: 3,
634
648
  sharedFile: 'index-bundle',
635
649
  apply(controlUiDir) {
636
650
  const bundlePath = resolveBundlePath(controlUiDir);
@@ -643,9 +657,9 @@ const mentionHooks = {
643
657
  if (!backupFile(bundlePath))
644
658
  return false;
645
659
  // 注入点 A: handleKeyDown
646
- // 匹配 return;if(key===Enter&&!shiftKey){if(isComposing 结构
647
- // 兼容 2026.4.x: return;if (无 }) 和旧版 ;return}if (有 })
648
- const keydownRe = /;return\}?if\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.key===`Enter`&&!\1\.shiftKey\)\{if\(\1\.isComposing/;
660
+ // 锚定 ;return}if(X.key===`Enter`&&!X.shiftKey){
661
+ // 不再依赖内层 isComposing:2026.6.10 已把 isComposing 检查拆到独立 handler
662
+ const keydownRe = /;return\}if\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.key===`Enter`&&!\1\.shiftKey\)\{/;
649
663
  const keydownMatch = content.match(keydownRe);
650
664
  if (!keydownMatch) {
651
665
  logger.warn('[ui-ext:regex] mention-hooks: keydown anchor not found');
@@ -656,31 +670,40 @@ const mentionHooks = {
656
670
  const markerA = makeMarker(this.id);
657
671
  const hookA = `${markerA}if(window.__clMention?.onKeyDown(${eventParam}))return;`;
658
672
  content = content.substring(0, keydownInsertIdx) + hookA + content.substring(keydownInsertIdx);
659
- // 注入点 B: handleInput — 支持两种形态
660
- // Shape A (old): .onDraftChange(target.value)}}
661
- // Shape B (4.11/latest): .onDraftChange(target.value) (arrow function 内,无 }})
673
+ // 注入点 B: handleInput — 支持三种形态
674
+ // Shape A (old): .onDraftChange(target.value)}}
675
+ // 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 字面
662
678
  const inputReA = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)\}\}/;
663
679
  const inputReB = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)/;
680
+ 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\)\}/;
664
681
  let inputMatch = content.match(inputReA);
665
682
  let inputShape = 'A';
683
+ if (!inputMatch) {
684
+ inputMatch = content.match(inputReC);
685
+ inputShape = 'C';
686
+ }
666
687
  if (!inputMatch) {
667
688
  inputMatch = content.match(inputReB);
668
689
  inputShape = 'B';
669
690
  }
670
691
  if (!inputMatch) {
671
- logger.warn('[ui-ext:regex] mention-hooks: input anchor not found (neither Shape A nor B)');
692
+ logger.warn('[ui-ext:regex] mention-hooks: input anchor not found (Shape A/B/C all missed)');
672
693
  return false;
673
694
  }
674
695
  const targetVar = inputMatch[1];
675
696
  const inputAnchorIdx = content.indexOf(inputMatch[0]);
676
- // 计算 onDraftChange(x.value) 调用结束位置
697
+ // Shape A: }} 之前;Shape C: 末尾 } 之前;Shape B: ) 之后
677
698
  const draftCallEnd = inputShape === 'A'
678
- ? inputAnchorIdx + inputMatch[0].length - 2 // 在 }} 之前
679
- : inputAnchorIdx + inputMatch[0].length; // 在 ) 之后
699
+ ? inputAnchorIdx + inputMatch[0].length - 2
700
+ : inputShape === 'C'
701
+ ? inputAnchorIdx + inputMatch[0].length - 1
702
+ : inputAnchorIdx + inputMatch[0].length;
680
703
  const hookB = `;window.__clMention?.onInput?.(${targetVar})`;
681
704
  content = content.substring(0, draftCallEnd) + hookB + content.substring(draftCallEnd);
682
705
  writeFileSync(bundlePath, content, 'utf-8');
683
- logger.info(`[ui-ext:regex] mention-hooks: injected (event=${eventParam}, target=${targetVar})`);
706
+ logger.info(`[ui-ext:regex] mention-hooks: injected (shape=${inputShape}, event=${eventParam}, target=${targetVar})`);
684
707
  return true;
685
708
  },
686
709
  revert(controlUiDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-openagent",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "description": "OpenAgent channel plugin for OpenClaw",
6
6
  "license": "MIT",
@@ -75,7 +75,7 @@
75
75
  "@types/node": "^25.5.0",
76
76
  "@types/sql.js": "^1.4.11",
77
77
  "@types/yazl": "^3.3.1",
78
- "openclaw": "^2026.6.9",
78
+ "openclaw": "^2026.6.10",
79
79
  "tsx": "^4.21.0",
80
80
  "typescript": "^6.0.2"
81
81
  }
@@ -93,7 +93,7 @@ export function createDownloadFileTool(): ToolDescriptor {
93
93
  }
94
94
 
95
95
  // ② Resolve download URL
96
- const accessBase = rt.config?.accessApiBase;
96
+ const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
97
97
  const downloadUrl = resolveOasnAccessUrl(accessBase, contentUrl);
98
98
 
99
99
  logger.info(
@@ -349,7 +349,7 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
349
349
  // 在 visible_result.webui_continuation 里返回可继续交互的入口。
350
350
  if (result.webuiContinuation?.launchUrl) {
351
351
  const rawLink = result.webuiContinuation.launchUrl;
352
- const accessBase = rt.config?.accessApiBase;
352
+ const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
353
353
  const link = resolveOasnAccessUrl(accessBase, rawLink);
354
354
  const label = result.webuiContinuation.displayName || 'Open WebUI';
355
355
  content = content.trim()
@@ -2,7 +2,7 @@
2
2
  * OpenAgent UI Override — Auto-generated, do not edit directly!
3
3
  *
4
4
  * Source: src/plugin-ui/modules/
5
- * Built: 2026-06-25T03:47:26.820Z
5
+ * Built: 2026-06-25T07:10:51.968Z
6
6
  *
7
7
  * To modify, edit the source modules and run:
8
8
  * npm run build:override
@@ -2891,7 +2891,16 @@ liveTextarea.dispatchEvent(new Event('change', { bubbles: true }));
2891
2891
  // tryInject() 只做 DOM 注入(@ 按钮 + CSS class),不绑定任何事件。
2892
2892
 
2893
2893
  // ── textarea 选择器(全局复用)──
2894
- const _CL_TEXTAREA_SELECTOR = 'textarea[placeholder*="Message"], textarea[placeholder*="Enter to send"]';
2894
+ // 多策略回退:v3 "Message..." / v4 "发消息..." / 结构化选择器
2895
+ const _CL_TEXTAREA_SELECTOR = [
2896
+ 'textarea[placeholder*="发消息"]',
2897
+ 'textarea[placeholder*="Message"]',
2898
+ 'textarea[placeholder*="Enter to send"]',
2899
+ '.agent-chat__composer-combobox textarea',
2900
+ '.chat-composer textarea',
2901
+ '.chat-input textarea',
2902
+ '[class*="composer"] textarea:not([placeholder*="Search"])',
2903
+ ].join(', ');
2895
2904
 
2896
2905
  function _getTextarea() {
2897
2906
  return document.querySelector(_CL_TEXTAREA_SELECTOR);
@@ -2929,7 +2938,8 @@ function _isLikelyNativeSendButton(target, textarea) {
2929
2938
  if (!button || !textarea) return false;
2930
2939
  if (button.classList && button.classList.contains('openagent-at-btn')) return false;
2931
2940
 
2932
- const inputContainer = textarea.closest('.chat-input')
2941
+ const inputContainer = textarea.closest('.agent-chat__composer-combobox')
2942
+ || textarea.closest('.chat-input')
2933
2943
  || textarea.closest('[class*="chat-composer"]')
2934
2944
  || textarea.closest('[class*="chat"]')
2935
2945
  || textarea.parentElement;
@@ -3086,7 +3096,8 @@ function tryInject() {
3086
3096
  }
3087
3097
 
3088
3098
  // ── 给输入区域加 position: relative 包裹层 ──
3089
- const inputContainer = textarea.closest('.chat-input')
3099
+ const inputContainer = textarea.closest('.agent-chat__composer-combobox')
3100
+ || textarea.closest('.chat-input')
3090
3101
  || textarea.closest('[class*="chat-composer"]')
3091
3102
  || textarea.closest('[class*="chat"]')
3092
3103
  || textareaParent?.parentElement
@@ -10,7 +10,16 @@
10
10
  // tryInject() 只做 DOM 注入(@ 按钮 + CSS class),不绑定任何事件。
11
11
 
12
12
  // ── textarea 选择器(全局复用)──
13
- const _CL_TEXTAREA_SELECTOR = 'textarea[placeholder*="Message"], textarea[placeholder*="Enter to send"]';
13
+ // 多策略回退:v3 "Message..." / v4 "发消息..." / 结构化选择器
14
+ const _CL_TEXTAREA_SELECTOR = [
15
+ 'textarea[placeholder*="发消息"]',
16
+ 'textarea[placeholder*="Message"]',
17
+ 'textarea[placeholder*="Enter to send"]',
18
+ '.agent-chat__composer-combobox textarea',
19
+ '.chat-composer textarea',
20
+ '.chat-input textarea',
21
+ '[class*="composer"] textarea:not([placeholder*="Search"])',
22
+ ].join(', ');
14
23
 
15
24
  function _getTextarea() {
16
25
  return document.querySelector(_CL_TEXTAREA_SELECTOR);
@@ -48,7 +57,8 @@ function _isLikelyNativeSendButton(target, textarea) {
48
57
  if (!button || !textarea) return false;
49
58
  if (button.classList && button.classList.contains('openagent-at-btn')) return false;
50
59
 
51
- const inputContainer = textarea.closest('.chat-input')
60
+ const inputContainer = textarea.closest('.agent-chat__composer-combobox')
61
+ || textarea.closest('.chat-input')
52
62
  || textarea.closest('[class*="chat-composer"]')
53
63
  || textarea.closest('[class*="chat"]')
54
64
  || textarea.parentElement;
@@ -205,7 +215,8 @@ function tryInject() {
205
215
  }
206
216
 
207
217
  // ── 给输入区域加 position: relative 包裹层 ──
208
- const inputContainer = textarea.closest('.chat-input')
218
+ const inputContainer = textarea.closest('.agent-chat__composer-combobox')
219
+ || textarea.closest('.chat-input')
209
220
  || textarea.closest('[class*="chat-composer"]')
210
221
  || textarea.closest('[class*="chat"]')
211
222
  || textareaParent?.parentElement
@@ -0,0 +1,86 @@
1
+ // ── OpenAgent Theme Adapter ──
2
+ //
3
+ // 运行时检测 OpenClaw Control UI 主题系统(v4.x vscode tokens / v5.x glassmorphism),
4
+ // 注入 --oa-* CSS 变量供 styles.js 中的 var() fallback 消费。
5
+ //
6
+ // 必须早于 styles.js 加载(由 build.cjs MANIFEST 顺序保证)。
7
+
8
+ (function _clInitThemeAdapter() {
9
+ if (window.__openagentThemeAdapterInstalled) return;
10
+ window.__openagentThemeAdapterInstalled = true;
11
+
12
+ /**
13
+ * 检测当前 OC 主题系统类型。
14
+ * @returns {{ hasGlass: boolean, hasVscode: boolean, isDark: boolean }}
15
+ */
16
+ function detectThemeSystem() {
17
+ var cs = getComputedStyle(document.documentElement);
18
+ var hasGlass = !!(cs.getPropertyValue('--glass-surface').trim());
19
+ var hasKn = !!(cs.getPropertyValue('--kn-bg-primary').trim());
20
+ var hasVscode = !!(cs.getPropertyValue('--vscode-editor-background').trim());
21
+
22
+ // 多策略暗色检测:v4 data-theme-mode / v5 data-theme / prefers-color-scheme
23
+ var themeMode = document.documentElement.getAttribute('data-theme-mode') // v4.x
24
+ || document.documentElement.getAttribute('data-theme') // v5.x
25
+ || '';
26
+ var isDark = themeMode.indexOf('dark') >= 0
27
+ || window.matchMedia('(prefers-color-scheme: dark)').matches;
28
+
29
+ return {
30
+ hasGlass: hasGlass || hasKn,
31
+ hasVscode: hasVscode,
32
+ isDark: isDark
33
+ };
34
+ }
35
+
36
+ /**
37
+ * 注入 --oa-* CSS 变量到 :root。
38
+ * styles.js 中的 var(--oa-xxx, fallback) 会优先取这些值。
39
+ */
40
+ function setupThemeVariables() {
41
+ var system = detectThemeSystem();
42
+ var root = document.documentElement;
43
+
44
+ if (system.hasGlass) {
45
+ // v5.x Glassmorphism: 使用 --glass-* / --kn-* token 族
46
+ root.style.setProperty('--oa-panel-bg', 'var(--glass-surface, var(--card))');
47
+ root.style.setProperty('--oa-panel-text', 'var(--kn-text-primary, var(--text, #e0e0e0))');
48
+ root.style.setProperty('--oa-card-bg', 'var(--glass-card, var(--card, #f5f5f5))');
49
+ root.style.setProperty('--oa-card-hover-bg', 'var(--glass-card-hover, var(--card-hover))');
50
+ root.style.setProperty('--oa-border', 'var(--glass-border, var(--border, #e5e5e5))');
51
+ root.style.setProperty('--oa-shadow-color', 'rgba(0, 0, 0, 0.08)');
52
+ root.style.setProperty('--oa-divider', 'var(--glass-divider, var(--border, #eee))');
53
+ root.style.setProperty('--oa-muted-text', 'var(--kn-text-secondary, var(--text-secondary, #999))');
54
+ } else if (system.hasVscode) {
55
+ // v4.x VSCode tokens: 使用 --vscode-* 变量
56
+ root.style.setProperty('--oa-panel-bg', 'var(--vscode-editor-background, #fff)');
57
+ root.style.setProperty('--oa-panel-text', 'var(--vscode-editor-foreground, #2F2F33)');
58
+ root.style.setProperty('--oa-card-bg', 'var(--vscode-sideBar-background, #f5f5f5)');
59
+ root.style.setProperty('--oa-card-hover-bg', 'var(--vscode-list-hoverBackground, #f0f0f0)');
60
+ root.style.setProperty('--oa-border', 'var(--vscode-panel-border, #e5e5e5)');
61
+ root.style.setProperty('--oa-shadow-color', 'rgba(0, 0, 0, 0.10)');
62
+ root.style.setProperty('--oa-divider', 'var(--vscode-sideBar-border, #eee)');
63
+ root.style.setProperty('--oa-muted-text', 'var(--vscode-descriptionForeground, #999)');
64
+ }
65
+ // 无特征 → 不注入变量,styles.js 中的 var() 会 fallback 到硬编码值
66
+
67
+ // 注入暗色 class 标记供 styles.js 选择器使用
68
+ if (system.isDark) {
69
+ root.style.setProperty('--oa-is-dark', '1');
70
+ } else {
71
+ root.style.setProperty('--oa-is-dark', '0');
72
+ }
73
+ }
74
+
75
+ // 立即执行 + 监听主题变更(v5.x 可能运行时切换主题)
76
+ setupThemeVariables();
77
+
78
+ // 监听 data-theme / data-theme-mode 属性变更
79
+ var themeObserver = new MutationObserver(function () {
80
+ setupThemeVariables();
81
+ });
82
+ themeObserver.observe(document.documentElement, {
83
+ attributes: true,
84
+ attributeFilter: ['data-theme', 'data-theme-mode', 'class']
85
+ });
86
+ })();
@@ -288,52 +288,64 @@ const toolcardRender: UIExtension = {
288
288
  const toolMessageAnchor = 'toolmsg:${';
289
289
  let patchedToolMessageWrapper = false;
290
290
  if (content.includes(toolMessageAnchor)) {
291
+ let toolMessageOk = true;
291
292
  const toolMessageFn = findFunctionByAnchor(content, toolMessageAnchor, 8000);
292
293
  if (!toolMessageFn) {
293
294
  logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper function not found');
294
- return false;
295
+ toolMessageOk = false;
295
296
  }
296
297
 
297
- if (!toolMessageFn.body.includes('chat-tool-msg-collapse--manual')
298
- || !toolMessageFn.body.includes('chat-tool-msg-body')) {
298
+ if (toolMessageOk && (!toolMessageFn!.body.includes('chat-tool-msg-collapse--manual')
299
+ || !toolMessageFn!.body.includes('chat-tool-msg-body'))) {
299
300
  logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper shape mismatch');
300
- return false;
301
+ toolMessageOk = false;
301
302
  }
302
303
 
303
- const toolMessageReturnTagMatch = toolMessageFn.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
304
- if (!toolMessageReturnTagMatch) {
305
- logger.warn('[ui-ext:regex] toolcard-render: tool message Lit html tag not found');
306
- return false;
304
+ let toolMessageHtmlVar = '';
305
+ if (toolMessageOk) {
306
+ const toolMessageReturnTagMatch = toolMessageFn!.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
307
+ if (!toolMessageReturnTagMatch) {
308
+ logger.warn('[ui-ext:regex] toolcard-render: tool message Lit html tag not found');
309
+ toolMessageOk = false;
310
+ } else {
311
+ toolMessageHtmlVar = toolMessageReturnTagMatch[1];
312
+ }
307
313
  }
308
- const toolMessageHtmlVar = toolMessageReturnTagMatch[1];
309
314
 
310
- 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*)\?/);
311
- if (!singleCardMatch) {
312
- logger.warn('[ui-ext:regex] toolcard-render: tool message single-card guard not found');
313
- return false;
314
- }
315
- const cardsVar = singleCardMatch[2];
316
- const mediaVar = singleCardMatch[4];
317
- const chatBubbleClassMatch = toolMessageFn.body.match(/[,;]([a-zA-Z_$]\w*)=\[`chat-bubble`,/);
318
- if (!chatBubbleClassMatch) {
319
- logger.warn('[ui-ext:regex] toolcard-render: tool message chat-bubble class var not found');
320
- return false;
321
- }
322
- const chatBubbleClassVar = chatBubbleClassMatch[1];
323
- const toolMessageParams = toolMessageFn.params.split(',').map((s: string) => s.trim());
324
- const toolMessageSidebarArg = toolMessageParams[3] || 'undefined';
325
- const toolMessageReturnIdx = toolMessageFn.body.indexOf(`return ${toolMessageHtmlVar}\``);
326
- if (toolMessageReturnIdx < 0) {
327
- logger.warn('[ui-ext:regex] toolcard-render: tool message return anchor not found');
328
- return false;
315
+ if (toolMessageOk) {
316
+ 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*)\?/);
317
+ if (!singleCardMatch) {
318
+ logger.warn('[ui-ext:regex] toolcard-render: tool message single-card guard not found');
319
+ toolMessageOk = false;
320
+ }
321
+ if (toolMessageOk) {
322
+ const cardsVar = singleCardMatch![2];
323
+ const mediaVar = singleCardMatch![4];
324
+ const chatBubbleClassMatch = toolMessageFn!.body.match(/[,;]([a-zA-Z_$]\w*)=\[`chat-bubble`,/);
325
+ if (!chatBubbleClassMatch) {
326
+ logger.warn('[ui-ext:regex] toolcard-render: tool message chat-bubble class var not found');
327
+ toolMessageOk = false;
328
+ }
329
+ if (toolMessageOk) {
330
+ const chatBubbleClassVar = chatBubbleClassMatch![1];
331
+ const toolMessageParams = toolMessageFn!.params.split(',').map((s: string) => s.trim());
332
+ const toolMessageSidebarArg = toolMessageParams[3] || 'undefined';
333
+ const toolMessageReturnIdx = toolMessageFn!.body.indexOf(`return ${toolMessageHtmlVar}\``);
334
+ if (toolMessageReturnIdx < 0) {
335
+ logger.warn('[ui-ext:regex] toolcard-render: tool message return anchor not found');
336
+ toolMessageOk = false;
337
+ }
338
+ if (toolMessageOk) {
339
+ const toolMessageHook =
340
+ `${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>\`;`;
341
+ const toolMessageInsertAt = toolMessageFn!.bodyStart + toolMessageReturnIdx;
342
+ content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
343
+ patchedToolMessageWrapper = true;
344
+ logger.info(`[ui-ext:regex] toolcard-render: patched tool message wrapper ${toolMessageFn!.funcName}(${toolMessageFn!.params.substring(0, 20)}) html=${toolMessageHtmlVar}`);
345
+ }
346
+ }
347
+ }
329
348
  }
330
-
331
- const toolMessageHook =
332
- `${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>\`;`;
333
- const toolMessageInsertAt = toolMessageFn.bodyStart + toolMessageReturnIdx;
334
- content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
335
- patchedToolMessageWrapper = true;
336
- logger.info(`[ui-ext:regex] toolcard-render: patched tool message wrapper ${toolMessageFn.funcName}(${toolMessageFn.params.substring(0, 20)}) html=${toolMessageHtmlVar}`);
337
349
  }
338
350
 
339
351
  writeFileSync(bundlePath, content, 'utf-8');
@@ -440,7 +452,7 @@ const toolcallidPropagate: UIExtension = {
440
452
 
441
453
  // 计算注入内容
442
454
  const marker = makeMarker(this.id);
443
- const tcidCapture = `${marker}var __tcid=(typeof ${mVar}.toolCallId==='string'&&${mVar}.toolCallId)||(typeof ${mVar}.tool_call_id==='string'&&${mVar}.tool_call_id)||'';`;
455
+ const tcidCapture = `${marker}var __tcid=(typeof ${msgParam}.toolCallId==='string'&&${msgParam}.toolCallId)||(typeof ${msgParam}.tool_call_id==='string'&&${msgParam}.tool_call_id)||'';`;
444
456
 
445
457
  const inlinePatchCall = callLoopVar
446
458
  ? `,toolCallId:__tcid||${callLoopVar}.id||${callLoopVar}.toolCallId||${callLoopVar}.tool_call_id||''`
@@ -454,16 +466,16 @@ const toolcallidPropagate: UIExtension = {
454
466
  const arrInitPattern = `,${cardsVar}=[];`;
455
467
  let arrInitIdx = funcBody.indexOf(arrInitPattern);
456
468
  if (arrInitIdx < 0) {
457
- // v4.11+: 可能是 ,i=[](后接分号或逗号之前的位置不同)
469
+ // v4.11+: 可能是 ,i=[](后接逗号,如 a=[],o=...),不能直接插 var
458
470
  const altPattern = `,${cardsVar}=[]`;
459
471
  arrInitIdx = funcBody.indexOf(altPattern);
460
472
  if (arrInitIdx < 0) {
461
473
  logger.warn('[ui-ext:regex] toolcallid-propagate: cards init not found');
462
474
  return false;
463
475
  }
464
- // 找到 ] 后面的分隔符位置
465
- arrInitIdx += altPattern.length;
466
- // captureInsertOffset ; 或 , 之后
476
+ // altPattern 后面是逗号(逗号表达式),var 声明无法插入此处
477
+ // 改为插入到函数体开头,利用 var 提升特性
478
+ arrInitIdx = 0;
467
479
  } else {
468
480
  arrInitIdx += arrInitPattern.length;
469
481
  }
@@ -672,7 +684,7 @@ const remoteAgentMessageShortCircuit: UIExtension = {
672
684
 
673
685
  const mentionHooks: UIExtension = {
674
686
  id: 'mention-hooks',
675
- version: 2,
687
+ version: 3,
676
688
  sharedFile: 'index-bundle',
677
689
 
678
690
  apply(controlUiDir: string): boolean {
@@ -683,9 +695,9 @@ const mentionHooks: UIExtension = {
683
695
  if (!backupFile(bundlePath)) return false;
684
696
 
685
697
  // 注入点 A: handleKeyDown
686
- // 匹配 return;if(key===Enter&&!shiftKey){if(isComposing 结构
687
- // 兼容 2026.4.x: return;if (无 }) 和旧版 ;return}if (有 })
688
- const keydownRe = /;return\}?if\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.key===`Enter`&&!\1\.shiftKey\)\{if\(\1\.isComposing/;
698
+ // 锚定 ;return}if(X.key===`Enter`&&!X.shiftKey){
699
+ // 不再依赖内层 isComposing:2026.6.10 已把 isComposing 检查拆到独立 handler
700
+ const keydownRe = /;return\}if\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.key===`Enter`&&!\1\.shiftKey\)\{/;
689
701
  const keydownMatch = content.match(keydownRe);
690
702
  if (!keydownMatch) {
691
703
  logger.warn('[ui-ext:regex] mention-hooks: keydown anchor not found');
@@ -697,34 +709,44 @@ const mentionHooks: UIExtension = {
697
709
  const hookA = `${markerA}if(window.__clMention?.onKeyDown(${eventParam}))return;`;
698
710
  content = content.substring(0, keydownInsertIdx) + hookA + content.substring(keydownInsertIdx);
699
711
 
700
- // 注入点 B: handleInput — 支持两种形态
701
- // Shape A (old): .onDraftChange(target.value)}}
702
- // Shape B (4.11/latest): .onDraftChange(target.value) (arrow function 内,无 }})
712
+ // 注入点 B: handleInput — 支持三种形态
713
+ // Shape A (old): .onDraftChange(target.value)}}
714
+ // Shape B (4.11~4.x): .onDraftChange(target.value) (arrow function )
715
+ // Shape C (2026.6.x): be=t=>{let n=t.target;pL(e,n.value)} — 引入 pL helper,
716
+ // arrow body 末尾不再出现 onDraftChange 字面
703
717
  const inputReA = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)\}\}/;
704
718
  const inputReB = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)/;
719
+ 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\)\}/;
720
+
705
721
  let inputMatch = content.match(inputReA);
706
- let inputShape: 'A' | 'B' = 'A';
722
+ let inputShape: 'A' | 'B' | 'C' = 'A';
707
723
 
724
+ if (!inputMatch) {
725
+ inputMatch = content.match(inputReC);
726
+ inputShape = 'C';
727
+ }
708
728
  if (!inputMatch) {
709
729
  inputMatch = content.match(inputReB);
710
730
  inputShape = 'B';
711
731
  }
712
732
 
713
733
  if (!inputMatch) {
714
- logger.warn('[ui-ext:regex] mention-hooks: input anchor not found (neither Shape A nor B)');
734
+ logger.warn('[ui-ext:regex] mention-hooks: input anchor not found (Shape A/B/C all missed)');
715
735
  return false;
716
736
  }
717
737
  const targetVar = inputMatch[1];
718
738
  const inputAnchorIdx = content.indexOf(inputMatch[0]);
719
- // 计算 onDraftChange(x.value) 调用结束位置
739
+ // Shape A: }} 之前;Shape C: 末尾 } 之前;Shape B: ) 之后
720
740
  const draftCallEnd = inputShape === 'A'
721
- ? inputAnchorIdx + inputMatch[0].length - 2 // 在 }} 之前
722
- : inputAnchorIdx + inputMatch[0].length; // 在 ) 之后
741
+ ? inputAnchorIdx + inputMatch[0].length - 2
742
+ : inputShape === 'C'
743
+ ? inputAnchorIdx + inputMatch[0].length - 1
744
+ : inputAnchorIdx + inputMatch[0].length;
723
745
  const hookB = `;window.__clMention?.onInput?.(${targetVar})`;
724
746
  content = content.substring(0, draftCallEnd) + hookB + content.substring(draftCallEnd);
725
747
 
726
748
  writeFileSync(bundlePath, content, 'utf-8');
727
- logger.info(`[ui-ext:regex] mention-hooks: injected (event=${eventParam}, target=${targetVar})`);
749
+ logger.info(`[ui-ext:regex] mention-hooks: injected (shape=${inputShape}, event=${eventParam}, target=${targetVar})`);
728
750
  return true;
729
751
  },
730
752