openclaw-openagent 1.0.12 → 1.0.14

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.
Files changed (132) hide show
  1. package/dist/index.js +2 -0
  2. package/dist/src/config/config-schema.d.ts +9 -0
  3. package/dist/src/config/config-schema.js +6 -0
  4. package/dist/src/messaging/executor.d.ts +2 -3
  5. package/dist/src/messaging/executor.js +4 -31
  6. package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
  7. package/dist/src/proxy/auth-proxy.js +5 -0
  8. package/dist/src/runtime/update-checker.d.ts +18 -0
  9. package/dist/src/runtime/update-checker.js +216 -0
  10. package/index.ts +3 -0
  11. package/package.json +1 -3
  12. package/src/app/channel-tools.ts +0 -249
  13. package/src/app/discovery-tools.ts +0 -273
  14. package/src/app/download-file-tool.ts +0 -133
  15. package/src/app/hooks.ts +0 -144
  16. package/src/app/index.ts +0 -84
  17. package/src/app/messaging-tools.ts +0 -79
  18. package/src/app/ops-tools.ts +0 -155
  19. package/src/app/remote-agent-tool.ts +0 -762
  20. package/src/app/types.ts +0 -68
  21. package/src/app/upload-file-tool.ts +0 -411
  22. package/src/app/verbose-preflight.ts +0 -190
  23. package/src/auth/config.ts +0 -266
  24. package/src/auth/credential-manager.ts +0 -146
  25. package/src/auth/index.ts +0 -24
  26. package/src/auth/verify.ts +0 -99
  27. package/src/channel.ts +0 -566
  28. package/src/compat.ts +0 -94
  29. package/src/config/config-schema.ts +0 -39
  30. package/src/messaging/aggregator.ts +0 -120
  31. package/src/messaging/collector.ts +0 -89
  32. package/src/messaging/executor.ts +0 -72
  33. package/src/messaging/inbound.ts +0 -150
  34. package/src/messaging/index.ts +0 -11
  35. package/src/messaging/mention-protocol.ts +0 -94
  36. package/src/messaging/process-c2c-request.ts +0 -564
  37. package/src/messaging/process-message.ts +0 -373
  38. package/src/messaging/scheduler.ts +0 -55
  39. package/src/messaging/types.ts +0 -38
  40. package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
  41. package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
  42. package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
  43. package/src/plugin-ui/adapters/oc-unknown.js +0 -48
  44. package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
  45. package/src/plugin-ui/assets/bg.png +0 -0
  46. package/src/plugin-ui/assets/icon.png +0 -0
  47. package/src/plugin-ui/assets/magic.svg +0 -5
  48. package/src/plugin-ui/assets/openagent-override.js +0 -11156
  49. package/src/plugin-ui/build.cjs +0 -463
  50. package/src/plugin-ui/index.ts +0 -18
  51. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
  52. package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
  53. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
  54. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
  55. package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
  56. package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
  57. package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
  58. package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
  59. package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
  60. package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
  61. package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
  62. package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
  63. package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
  64. package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
  65. package/src/plugin-ui/modules/loader/shared-state.js +0 -838
  66. package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
  67. package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
  68. package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
  69. package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
  70. package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
  71. package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
  72. package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
  73. package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
  74. package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
  75. package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
  76. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
  77. package/src/plugin-ui/postinstall-deploy.cjs +0 -52
  78. package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
  79. package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
  80. package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
  81. package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
  82. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
  83. package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
  84. package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
  85. package/src/proxy/auth-proxy.ts +0 -409
  86. package/src/runtime/account.ts +0 -549
  87. package/src/runtime/index.ts +0 -7
  88. package/src/runtime/plugin-runtime.ts +0 -94
  89. package/src/runtime/registry.ts +0 -71
  90. package/src/sdk/CLASS_MAP.md +0 -143
  91. package/src/sdk/index.d.ts +0 -126
  92. package/src/sdk/index.js +0 -23990
  93. package/src/sdk/modules/cloud-search-module.js +0 -1117
  94. package/src/sdk/modules/follow-module.js +0 -1069
  95. package/src/sdk/modules/group-module.js +0 -7397
  96. package/src/sdk/modules/relationship-module.js +0 -2269
  97. package/src/sdk/modules/signaling-module.js +0 -1468
  98. package/src/sdk/modules/tim-upload-plugin.js +0 -730
  99. package/src/sdk/node-env/http-request.js +0 -90
  100. package/src/sdk/node-env/index.js +0 -57
  101. package/src/sdk/node-env/storage.js +0 -114
  102. package/src/sdk/package.json +0 -10
  103. package/src/sdk/tsconfig.json +0 -16
  104. package/src/state/pending-invocation-store.ts +0 -43
  105. package/src/state/store.ts +0 -756
  106. package/src/tim/c2c.ts +0 -451
  107. package/src/tim/channels.ts +0 -364
  108. package/src/tim/client.ts +0 -330
  109. package/src/tim/index.ts +0 -18
  110. package/src/tim/messages.ts +0 -166
  111. package/src/tim/sdk-logger-init.ts +0 -50
  112. package/src/tools.ts +0 -10
  113. package/src/transport/factory.ts +0 -95
  114. package/src/transport/oasn/index.ts +0 -17
  115. package/src/transport/oasn/oasn-agent-card.ts +0 -111
  116. package/src/transport/oasn/oasn-discovery.ts +0 -108
  117. package/src/transport/oasn/oasn-files.ts +0 -210
  118. package/src/transport/oasn/oasn-http.ts +0 -529
  119. package/src/transport/oasn/oasn-invocation.ts +0 -604
  120. package/src/transport/oasn/oasn-normalize.ts +0 -133
  121. package/src/transport/oasn/oasn-register.ts +0 -33
  122. package/src/transport/oasn/oasn-transport.ts +0 -318
  123. package/src/transport/oasn/oasn-types.ts +0 -404
  124. package/src/transport/tim/index.ts +0 -8
  125. package/src/transport/tim/tim-transport.ts +0 -515
  126. package/src/transport/types.ts +0 -538
  127. package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
  128. package/src/types/openclaw.d.ts +0 -97
  129. package/src/types/tencentcloud-chat.d.ts +0 -15
  130. package/src/util/http.ts +0 -113
  131. package/src/util/logger.ts +0 -131
  132. package/src/util/url-resolver.ts +0 -77
@@ -1,944 +0,0 @@
1
- /**
2
- * UIExtension 注册表 — 正则模式链方案(无需 source map)
3
- *
4
- * 用正则锚点定位注入位置,无需 source map。
5
- *
6
- * 验证来源:full-inject.mjs — 21 版本 × 6 扩展点,acorn 语法 100% 合法。
7
- */
8
-
9
- import { readFileSync, writeFileSync, copyFileSync, unlinkSync, existsSync } from 'node:fs';
10
- import { createHash } from 'node:crypto';
11
- import { join } from 'node:path';
12
- import { logger } from '../../util/logger.js';
13
- import { findFileByPrefix } from './locator.js';
14
- import { backupFile, restoreFile } from './backup.js';
15
- import { makeMarker, hasMarker } from './types.js';
16
- import type { UIExtension, UIExtensionContext } from './types.js';
17
-
18
- // ── 常量 ──────────────────────────────────────────────────────────────────
19
-
20
- const OVERRIDE_ASSET_URL = new URL('../assets/openagent-override.js', import.meta.url);
21
- const ICON_ASSET_URL = new URL('../assets/icon.png', import.meta.url);
22
- const BG_ASSET_URL = new URL('../assets/bg.png', import.meta.url);
23
- const HOST_ADAPTER_URLS: Record<string, URL> = {
24
- 'oc-2026-04': new URL('../adapters/oc-2026-04.js', import.meta.url),
25
- 'oc-2026-05': new URL('../adapters/oc-2026-05.js', import.meta.url),
26
- 'oc-2026-06': new URL('../adapters/oc-2026-06.js', import.meta.url),
27
- 'oc-unknown': new URL('../adapters/oc-unknown.js', import.meta.url),
28
- };
29
- const HOST_ADAPTER_START = '/*__OPENAGENT_HOST_ADAPTER_START__*/';
30
- const HOST_ADAPTER_END = '/*__OPENAGENT_HOST_ADAPTER_END__*/';
31
- const HOST_ADAPTER_BLOCK_RE = /\/\*__OPENAGENT_HOST_ADAPTER_START__\*\/[\s\S]*?\/\*__OPENAGENT_HOST_ADAPTER_END__\*\//;
32
- const BUILD_ID_TOKEN = "buildId: '__OPENAGENT_BUILD_ID__'";
33
- const BUILD_ID_ASSIGNMENT_RE = /buildId:\s*(?:"[^"]*"|'[^']*')/;
34
-
35
- function normalizeHostVersion(hostVersion: string | undefined): string {
36
- const value = String(hostVersion || '').trim();
37
- if (!value || value === 'unknown') return '';
38
- const match = value.match(/20\d{2}\.\d{1,2}\.\d{1,2}(?:-[A-Za-z0-9._-]+)?/);
39
- return match ? match[0] : '';
40
- }
41
-
42
- function resolveHostAdapterId(hostVersion: string | undefined): string {
43
- const version = normalizeHostVersion(hostVersion);
44
- const parts = version.split('.');
45
- const year = Number.parseInt(parts[0], 10);
46
- const month = Number.parseInt(parts[1], 10);
47
- if (year === 2026 && month === 4) return 'oc-2026-04';
48
- if (year === 2026 && month === 5) return 'oc-2026-05';
49
- if (year === 2026 && month === 6) return 'oc-2026-06';
50
- return 'oc-unknown';
51
- }
52
-
53
- function getHostAdapterBlock(hostVersion: string | undefined): string {
54
- let adapterId = resolveHostAdapterId(hostVersion);
55
- let adapterUrl = HOST_ADAPTER_URLS[adapterId];
56
- if (!adapterUrl || !existsSync(adapterUrl)) {
57
- adapterId = 'oc-unknown';
58
- adapterUrl = HOST_ADAPTER_URLS[adapterId];
59
- }
60
- const adapterSource = readFileSync(adapterUrl, 'utf-8').trimEnd();
61
- return [
62
- HOST_ADAPTER_START,
63
- `// HOST ADAPTER: ${adapterId}`,
64
- adapterSource,
65
- HOST_ADAPTER_END,
66
- ].join('\n');
67
- }
68
-
69
- function injectHostAdapter(source: string, hostVersion: string | undefined): string {
70
- const block = getHostAdapterBlock(hostVersion);
71
- return HOST_ADAPTER_BLOCK_RE.test(source)
72
- ? source.replace(HOST_ADAPTER_BLOCK_RE, block)
73
- : source;
74
- }
75
-
76
- function injectBuildId(source: string): string {
77
- const normalized = source.replace(BUILD_ID_ASSIGNMENT_RE, BUILD_ID_TOKEN);
78
- const buildId = createHash('sha256').update(normalized).digest('hex').slice(0, 12);
79
- return normalized.replace(BUILD_ID_TOKEN, `buildId: ${JSON.stringify(buildId)}`);
80
- }
81
-
82
- function getOverrideAssetContent(context?: UIExtensionContext): string {
83
- let source = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
84
- const hostVersion = normalizeHostVersion(context?.hostVersion);
85
- if (hostVersion) {
86
- source = source.replace(
87
- "hostVersion: '__OPENCLAW_HOST_VERSION__'",
88
- `hostVersion: ${JSON.stringify(hostVersion)}`,
89
- );
90
- }
91
- return injectBuildId(injectHostAdapter(source, hostVersion));
92
- }
93
-
94
- function getOverrideAssetHash(context?: UIExtensionContext): string {
95
- return createHash('sha256')
96
- .update(getOverrideAssetContent(context))
97
- .digest('hex')
98
- .slice(0, 12);
99
- }
100
-
101
- // ── 辅助函数 ──────────────────────────────────────────────────────────────
102
-
103
- /** 获取 bundle 路径(不要求 source map 存在)。 */
104
- function resolveBundlePath(controlUiDir: string): string | null {
105
- const assetsDir = join(controlUiDir, 'assets');
106
- const bundlePath = findFileByPrefix(assetsDir, 'index-');
107
- if (!bundlePath) {
108
- logger.debug('[ui-ext:regex] Main bundle not found');
109
- return null;
110
- }
111
- return bundlePath;
112
- }
113
-
114
- /** 在 push({...}) 内找 object literal 闭合 } 的位置。 */
115
- function findObjectLiteralClose(code: string, pushParenStart: number): number {
116
- let i = pushParenStart;
117
- while (i < code.length && code[i] !== '{') i++;
118
- if (i >= code.length) return -1;
119
- let depth = 1;
120
- let inTemplate = false;
121
- for (let j = i + 1; j < code.length; j++) {
122
- const ch = code[j];
123
- if (ch === '`') { inTemplate = !inTemplate; continue; }
124
- if (inTemplate) continue;
125
- if (ch === '{') depth++;
126
- if (ch === '}') { depth--; if (depth === 0) return j; }
127
- }
128
- return -1;
129
- }
130
-
131
- function findFunctionByAnchor(
132
- content: string,
133
- anchor: string,
134
- lookBack = 5000,
135
- ): {
136
- anchorIdx: number;
137
- funcName: string;
138
- params: string;
139
- bodyStart: number;
140
- bodyEnd: number;
141
- body: string;
142
- } | null {
143
- const anchorIdx = content.indexOf(anchor);
144
- if (anchorIdx < 0) return null;
145
-
146
- const searchStart = Math.max(0, anchorIdx - lookBack);
147
- const regionBefore = content.substring(searchStart, anchorIdx);
148
- const funcRe = /function\s+(\w+)\(([^)]*)\)\{/g;
149
- let lastFunc: RegExpExecArray | null = null;
150
- let funcMatch: RegExpExecArray | null;
151
- while ((funcMatch = funcRe.exec(regionBefore)) !== null) lastFunc = funcMatch;
152
- if (!lastFunc) return null;
153
-
154
- const bodyStart = searchStart + lastFunc.index + lastFunc[0].length;
155
- let braceDepth = 1;
156
- let bodyEnd = bodyStart;
157
- for (let i = bodyStart; i < content.length && braceDepth > 0; i++) {
158
- if (content[i] === '{') braceDepth++;
159
- else if (content[i] === '}') braceDepth--;
160
- bodyEnd = i + 1;
161
- }
162
- if (braceDepth !== 0) return null;
163
-
164
- return {
165
- anchorIdx,
166
- funcName: lastFunc[1],
167
- params: lastFunc[2],
168
- bodyStart,
169
- bodyEnd,
170
- body: content.substring(bodyStart, bodyEnd),
171
- };
172
- }
173
-
174
-
175
- // ── Extension 1: override-script ─────────────────────────────────────────
176
-
177
- const overrideScript: UIExtension = {
178
- id: 'override-script',
179
- version: 1,
180
- alwaysApply: true,
181
-
182
- apply(controlUiDir: string, context?: UIExtensionContext): boolean {
183
- if (!existsSync(OVERRIDE_ASSET_URL)) {
184
- logger.warn('[ui-ext:regex] override-script: asset missing');
185
- return false;
186
- }
187
- const dest = join(controlUiDir, 'openagent-override.js');
188
- const srcContent = getOverrideAssetContent(context);
189
- if (existsSync(dest)) {
190
- const destContent = readFileSync(dest, 'utf-8');
191
- if (srcContent === destContent) {
192
- logger.debug('[ui-ext:regex] override-script: content unchanged, skip');
193
- return true;
194
- }
195
- }
196
- writeFileSync(dest, srcContent, 'utf-8');
197
- logger.info('[ui-ext:regex] override-script: deployed openagent-override.js');
198
-
199
- // Deploy icon.png alongside
200
- if (existsSync(ICON_ASSET_URL)) {
201
- const iconDest = join(controlUiDir, 'icon.png');
202
- try {
203
- copyFileSync(ICON_ASSET_URL, iconDest);
204
- logger.info('[ui-ext:regex] override-script: deployed icon.png');
205
- } catch (err) {
206
- logger.warn('[ui-ext:regex] override-script: failed to deploy icon.png');
207
- }
208
- }
209
- if (existsSync(BG_ASSET_URL)) {
210
- const bgDest = join(controlUiDir, 'bg.png');
211
- try {
212
- copyFileSync(BG_ASSET_URL, bgDest);
213
- logger.info('[ui-ext:regex] override-script: deployed bg.png');
214
- } catch (err) {
215
- logger.warn('[ui-ext:regex] override-script: failed to deploy bg.png');
216
- }
217
- }
218
-
219
- return true;
220
- },
221
-
222
- revert(controlUiDir: string): void {
223
- const dest = join(controlUiDir, 'openagent-override.js');
224
- if (existsSync(dest)) {
225
- unlinkSync(dest);
226
- logger.info('[ui-ext:regex] override-script: removed');
227
- }
228
- const iconDest = join(controlUiDir, 'icon.png');
229
- if (existsSync(iconDest)) {
230
- try {
231
- unlinkSync(iconDest);
232
- logger.info('[ui-ext:regex] override-script: removed icon.png');
233
- } catch {}
234
- }
235
- const bgDest = join(controlUiDir, 'bg.png');
236
- if (existsSync(bgDest)) {
237
- try {
238
- unlinkSync(bgDest);
239
- logger.info('[ui-ext:regex] override-script: removed bg.png');
240
- } catch {}
241
- }
242
- },
243
- };
244
-
245
- // ── Extension 2: html-script-tag ─────────────────────────────────────────
246
-
247
- const htmlScriptTag: UIExtension = {
248
- id: 'html-script-tag',
249
- version: 5,
250
- alwaysApply: true,
251
-
252
- apply(controlUiDir: string, context?: UIExtensionContext): boolean {
253
- const indexPath = join(controlUiDir, 'index.html');
254
- if (!existsSync(indexPath)) {
255
- logger.warn('[ui-ext:regex] html-script-tag: index.html not found');
256
- return false;
257
- }
258
- let html = readFileSync(indexPath, 'utf-8');
259
- const htmlMarker = `<!--openagent:${this.id}-->`;
260
- const markerPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/;
261
- const overrideHash = getOverrideAssetHash(context);
262
- const hostVersion = normalizeHostVersion(context?.hostVersion);
263
- const hostVersionAttr = hostVersion ? ` data-openagent-host-version="${hostVersion}"` : '';
264
- const scriptTag = `${htmlMarker}<script src="./openagent-override.js?v=${overrideHash}"${hostVersionAttr}></script>`;
265
- const markedScriptPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
266
- const oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
267
-
268
- if (markerPattern.test(html)) {
269
- let nextHtml = html.replace(markedScriptPattern, `${scriptTag}\n `);
270
- if (nextHtml === html && !nextHtml.includes('openagent-override.js')) {
271
- nextHtml = nextHtml.replace(markerPattern, `${scriptTag}\n `);
272
- } else if (nextHtml === html && nextHtml.includes('openagent-override.js')) {
273
- nextHtml = nextHtml
274
- .replace(oldScriptPattern, '')
275
- .replace(markerPattern, `${scriptTag}\n `);
276
- }
277
- if (nextHtml === html) {
278
- logger.debug('[ui-ext:regex] html-script-tag: already applied');
279
- } else {
280
- writeFileSync(indexPath, nextHtml, 'utf-8');
281
- logger.info(`[ui-ext:regex] html-script-tag: updated override cache-bust (${overrideHash})`);
282
- }
283
- return true;
284
- }
285
- if (oldScriptPattern.test(html)) {
286
- html = html.replace(oldScriptPattern, '');
287
- }
288
- const viteAnchor = '<script type="module" crossorigin src="./assets/index-';
289
- const anchorIdx = html.indexOf(viteAnchor);
290
- if (anchorIdx >= 0) {
291
- html = html.substring(0, anchorIdx) + scriptTag + '\n ' + html.substring(anchorIdx);
292
- } else {
293
- html = html.replace('</head>', `${scriptTag}\n</head>`);
294
- }
295
- writeFileSync(indexPath, html, 'utf-8');
296
- logger.info('[ui-ext:regex] html-script-tag: injected');
297
- return true;
298
- },
299
-
300
- revert(controlUiDir: string): void {
301
- const indexPath = join(controlUiDir, 'index.html');
302
- if (!existsSync(indexPath)) return;
303
- let html = readFileSync(indexPath, 'utf-8');
304
- html = html.replace(/(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g, '');
305
- writeFileSync(indexPath, html, 'utf-8');
306
- logger.info('[ui-ext:regex] html-script-tag: removed');
307
- },
308
- };
309
-
310
-
311
-
312
- // ── Extension 4: toolcard-render(ToolCard DOM 结构锚点定位)────────────────
313
-
314
- const toolcardRender: UIExtension = {
315
- id: 'toolcard-render',
316
- version: 6,
317
- sharedFile: 'index-bundle',
318
-
319
- apply(controlUiDir: string): boolean {
320
- const bundlePath = resolveBundlePath(controlUiDir);
321
- if (!bundlePath) return false;
322
- let content = readFileSync(bundlePath, 'utf-8');
323
- if (hasMarker(content, this.id)) { return true; }
324
- if (!backupFile(bundlePath)) return false;
325
-
326
- // 用 chat-tool-card__header 做 primary anchor — 3.28/4.11/latest 均存在且唯一
327
- const classAnchor = 'chat-tool-card__header';
328
- const innerFn = findFunctionByAnchor(content, classAnchor);
329
- if (!innerFn) {
330
- logger.warn('[ui-ext:regex] toolcard-render: chat-tool-card__header anchor not found');
331
- return false;
332
- }
333
-
334
- const funcName = innerFn.funcName;
335
- const params = innerFn.params;
336
- const fnBody = innerFn.body;
337
-
338
- // 校验:函数体必须包含 ToolCard DOM 结构
339
- if (!fnBody.includes('chat-tool-card')) {
340
- logger.warn('[ui-ext:regex] toolcard-render: candidate function does not contain chat-tool-card class');
341
- return false;
342
- }
343
-
344
- // 排除 markdown builder:如果函数体包含 markdown builder 特征,说明定位到了错误函数
345
- if (fnBody.includes('### Tool output') && !fnBody.includes('chat-tool-card__header')) {
346
- logger.warn('[ui-ext:regex] toolcard-render: candidate function looks like markdown builder, not ToolCard renderer');
347
- return false;
348
- }
349
-
350
- // 找 Lit html tag: return X`...` — 只在候选函数体内搜索
351
- const returnTagMatch = fnBody.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
352
- if (!returnTagMatch) {
353
- logger.warn('[ui-ext:regex] toolcard-render: Lit html tag not found in candidate function');
354
- return false;
355
- }
356
- const litHtmlVar = returnTagMatch[1];
357
-
358
- const paramParts = params.split(',').map((s: string) => s.trim());
359
- const cardParam = paramParts[0] || 'e';
360
- const sidebarParam = paramParts[1] || undefined;
361
- const sidebarArg = sidebarParam || 'undefined';
362
-
363
- const marker = makeMarker(this.id);
364
- const hook = `${marker}if(window.__openagentRenderToolCard){let __cl=window.__openagentRenderToolCard(${cardParam},${sidebarArg});if(__cl)return ${litHtmlVar}\`\${__cl}\`}`;
365
-
366
- content = content.substring(0, innerFn.bodyStart) + hook + content.substring(innerFn.bodyStart);
367
-
368
- // 4.11+ introduced an outer manual-collapse renderer. The inner ToolCard
369
- // renderer above only runs after the user expands the native card, so patch
370
- // the wrapper too to let OpenAgent own collapsed remote-agent cards from the
371
- // first render.
372
- const wrapperAnchor = 'chat-tool-msg-collapse--manual';
373
- const hasManualCollapseWrapper = content.includes(wrapperAnchor);
374
- let patchedWrapper = false;
375
- if (hasManualCollapseWrapper) {
376
- const wrapperFn = findFunctionByAnchor(content, wrapperAnchor);
377
- if (!wrapperFn) {
378
- logger.warn('[ui-ext:regex] toolcard-render: manual collapse wrapper function not found, skipping wrapper patch');
379
- } else if (!wrapperFn.body.includes('chat-tool-msg-body') || !wrapperFn.body.includes('onToggleExpanded')) {
380
- logger.warn('[ui-ext:regex] toolcard-render: manual collapse wrapper shape mismatch, skipping wrapper patch');
381
- } else {
382
- const wrapperReturnTagMatch = wrapperFn.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
383
- if (!wrapperReturnTagMatch) {
384
- logger.warn('[ui-ext:regex] toolcard-render: manual collapse Lit html tag not found, skipping wrapper patch');
385
- } else {
386
- const wrapperHtmlVar = wrapperReturnTagMatch[1];
387
- const wrapperParams = wrapperFn.params.split(',').map((s: string) => s.trim());
388
- const wrapperCardParam = wrapperParams[0] || 'e';
389
- const wrapperOptionsParam = wrapperParams[1] || '';
390
- const wrapperSidebarArg = wrapperOptionsParam ? `${wrapperOptionsParam}&&${wrapperOptionsParam}.onOpenSidebar` : 'undefined';
391
- const wrapperHook = `${marker}if(window.__openagentRenderToolCard){let __cl=window.__openagentRenderToolCard(${wrapperCardParam},${wrapperSidebarArg});if(__cl)return ${wrapperHtmlVar}\`\${__cl}\`}`;
392
- content = content.substring(0, wrapperFn.bodyStart) + wrapperHook + content.substring(wrapperFn.bodyStart);
393
- patchedWrapper = true;
394
- logger.info(`[ui-ext:regex] toolcard-render: patched manual wrapper ${wrapperFn.funcName}(${wrapperFn.params.substring(0, 20)}) html=${wrapperHtmlVar}`);
395
- }
396
- }
397
- }
398
-
399
- // 4.11+ also renders tool-result messages through the grouped message
400
- // renderer itself instead of nb(). That path only calls the inner ToolCard
401
- // renderer after the user expands the native "Tool output" row. Patch the
402
- // grouped message function so OpenAgent result cards are owned immediately.
403
- const toolMessageAnchor = 'toolmsg:${';
404
- let patchedToolMessageWrapper = false;
405
- if (content.includes(toolMessageAnchor)) {
406
- let toolMessageOk = true;
407
- const toolMessageFn = findFunctionByAnchor(content, toolMessageAnchor, 8000);
408
- if (!toolMessageFn) {
409
- logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper function not found, skipping');
410
- toolMessageOk = false;
411
- }
412
-
413
- if (toolMessageOk && (!toolMessageFn!.body.includes('chat-tool-msg-collapse--manual')
414
- || !toolMessageFn!.body.includes('chat-tool-msg-body'))) {
415
- logger.warn('[ui-ext:regex] toolcard-render: tool message wrapper shape mismatch, skipping');
416
- toolMessageOk = false;
417
- }
418
-
419
- let toolMessageHtmlVar = '';
420
- if (toolMessageOk) {
421
- const toolMessageReturnTagMatch = toolMessageFn!.body.match(/return\s+([a-zA-Z_$]\w*)\s*`/);
422
- if (!toolMessageReturnTagMatch) {
423
- logger.warn('[ui-ext:regex] toolcard-render: tool message Lit html tag not found');
424
- toolMessageOk = false;
425
- } else {
426
- toolMessageHtmlVar = toolMessageReturnTagMatch[1];
427
- }
428
- }
429
-
430
- if (toolMessageOk) {
431
- // 双模式匹配单卡 guard:
432
- // Shape A (2026.4.x): ,Z=X.length===1?X[0]:null,Y=Z&&!A&&!B?
433
- // Shape B (2026.5.x): ,N=u.length===1?u[0]:null,...ae=P?`Tool error`:ne&&!C&&!h?ne:...
434
- 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*)\?/);
435
- let cardsVar = '';
436
- let mediaVar = '';
437
- if (singleCardMatch) {
438
- // Shape A: old bundle
439
- cardsVar = singleCardMatch[2];
440
- mediaVar = singleCardMatch[4];
441
- } else {
442
- // Shape B: 2026.5.x bundle
443
- const shapeB = toolMessageFn!.body.match(/,(?:let\s+)?([a-zA-Z_$]\w*)=([a-zA-Z_$]\w*)\.length===1\?\2\[0\]:null/);
444
- if (!shapeB) {
445
- logger.warn('[ui-ext:regex] toolcard-render: tool message single-card guard not found');
446
- toolMessageOk = false;
447
- } else {
448
- cardsVar = shapeB[2];
449
- // 从 ae=P?`Tool error`:ne&&!C&&!h 中提取排除变量 C, h
450
- const aeMatch = toolMessageFn!.body.match(/,\w+=\w+\?`Tool error`:(\w+)&&!(\w+)&&!(\w+)\?/);
451
- if (aeMatch) {
452
- mediaVar = `(${aeMatch[2]}||${aeMatch[3]})`;
453
- } else {
454
- mediaVar = 'false';
455
- }
456
- }
457
- }
458
- const chatBubbleClassMatch = toolMessageFn!.body.match(/[,;]([a-zA-Z_$]\w*)=\[`chat-bubble`,/);
459
- if (!chatBubbleClassMatch) {
460
- logger.warn('[ui-ext:regex] toolcard-render: tool message chat-bubble class var not found');
461
- toolMessageOk = false;
462
- }
463
- if (toolMessageOk) {
464
- const chatBubbleClassVar = chatBubbleClassMatch![1];
465
- const toolMessageParams = toolMessageFn!.params.split(',').map((s: string) => s.trim());
466
- const toolMessageSidebarArg = toolMessageParams[3] || 'undefined';
467
- const toolMessageReturnIdx = toolMessageFn!.body.indexOf(`return ${toolMessageHtmlVar}\``);
468
- if (toolMessageReturnIdx < 0) {
469
- logger.warn('[ui-ext:regex] toolcard-render: tool message return anchor not found');
470
- toolMessageOk = false;
471
- }
472
- if (toolMessageOk) {
473
- const toolMessageHook =
474
- `${marker}/* openagent-media-aware-tool-message */let __openagentToolMsgCard=null;if(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>\`;`;
475
- const toolMessageInsertAt = toolMessageFn!.bodyStart + toolMessageReturnIdx;
476
- content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
477
- patchedToolMessageWrapper = true;
478
- logger.info(`[ui-ext:regex] toolcard-render: patched tool message wrapper ${toolMessageFn!.funcName}(${toolMessageFn!.params.substring(0, 20)}) html=${toolMessageHtmlVar}`);
479
- }
480
- }
481
- }
482
- }
483
-
484
- writeFileSync(bundlePath, content, 'utf-8');
485
- logger.info(`[ui-ext:regex] toolcard-render: patched ${funcName}(${params.substring(0, 20)}) html=${litHtmlVar}${patchedWrapper ? ' + wrapper' : ''}${patchedToolMessageWrapper ? ' + tool-message' : ''}`);
486
- return true;
487
- },
488
-
489
- revert(controlUiDir: string): void {
490
- const bundlePath = resolveBundlePath(controlUiDir);
491
- if (bundlePath) { restoreFile(bundlePath); }
492
- },
493
- };
494
-
495
-
496
- // ── Extension 5: toolcallid-propagate(正则定位,双规则锚点)──────────────
497
-
498
- const toolcallidPropagate: UIExtension = {
499
- id: 'toolcallid-propagate',
500
- version: 4,
501
- sharedFile: 'index-bundle',
502
-
503
- apply(controlUiDir: string): boolean {
504
- const bundlePath = resolveBundlePath(controlUiDir);
505
- if (!bundlePath) return false;
506
- let content = readFileSync(bundlePath, 'utf-8');
507
- if (hasMarker(content, this.id)) { return true; }
508
- if (!backupFile(bundlePath)) return false;
509
-
510
- // 双规则锚点定位 extractToolCards
511
- const ruleV4 = /\[`toolcall`,`tool_call`,`tooluse`,`tool_use`\]\.includes\(\w+\)/;
512
- const ruleV3 = /(\w+)\.push\(\{kind:`call`,name:/;
513
- const anchorMatch = content.match(ruleV4) || content.match(ruleV3);
514
- if (!anchorMatch) {
515
- logger.warn('[ui-ext:regex] toolcallid-propagate: anchor not found');
516
- return false;
517
- }
518
-
519
- // 向前找函数声明(支持多参数+默认值)
520
- const anchorPos = anchorMatch.index!;
521
- const lookBack = content.substring(Math.max(0, anchorPos - 3000), anchorPos);
522
- const funcDeclRe = /function\s+(\w+)\(([^)]+)\)\{/g;
523
- let lastFuncDecl: RegExpExecArray | null = null;
524
- let fm: RegExpExecArray | null;
525
- while ((fm = funcDeclRe.exec(lookBack)) !== null) lastFuncDecl = fm;
526
- if (!lastFuncDecl) {
527
- logger.warn('[ui-ext:regex] toolcallid-propagate: function decl not found');
528
- return false;
529
- }
530
-
531
- const funcStartGlobal = Math.max(0, anchorPos - 3000) + lastFuncDecl.index;
532
- const msgParam = lastFuncDecl[2].split(',')[0].trim();
533
- const bodyStart = funcStartGlobal + lastFuncDecl[0].length;
534
-
535
- // 找函数结束
536
- let braceDepth = 1;
537
- let funcEnd = -1;
538
- for (let i = bodyStart; i < content.length && i < bodyStart + 5000; i++) {
539
- if (content[i] === '{') braceDepth++;
540
- if (content[i] === '}') { braceDepth--; if (braceDepth === 0) { funcEnd = i + 1; break; } }
541
- }
542
- if (funcEnd < 0) {
543
- logger.warn('[ui-ext:regex] toolcallid-propagate: function end not found');
544
- return false;
545
- }
546
-
547
- const funcBody = content.substring(bodyStart, funcEnd);
548
-
549
- // 提取变量名
550
- const mVarMatch = funcBody.match(/let\s+(\w+)\s*=\s*\w+\s*,/);
551
- if (!mVarMatch) {
552
- logger.warn('[ui-ext:regex] toolcallid-propagate: message alias not found');
553
- return false;
554
- }
555
- const mVar = mVarMatch[1];
556
-
557
- const cardsMatch = funcBody.match(/,(\w+)=\[\](?:;|,)/);
558
- if (!cardsMatch) {
559
- logger.warn('[ui-ext:regex] toolcallid-propagate: cards array not found');
560
- return false;
561
- }
562
- const cardsVar = cardsMatch[1];
563
-
564
- // 循环变量:for-of 或 for-index
565
- const forOfMatches = [...funcBody.matchAll(/for\(let\s+(\w+)\s+of\s+\w+\)/g)];
566
- let callLoopVar: string | null, resultLoopVar: string | null;
567
- if (forOfMatches.length > 0) {
568
- callLoopVar = forOfMatches[0]?.[1] ?? null;
569
- resultLoopVar = forOfMatches[1]?.[1] ?? forOfMatches[0]?.[1] ?? null;
570
- } else {
571
- const indexLoopMatch = funcBody.match(/for\(let\s+(\w+)=0;\1<(\w+)\.length;\1\+\+\)/);
572
- if (indexLoopMatch) {
573
- const loopBodyStart = funcBody.indexOf(indexLoopMatch[0]) + indexLoopMatch[0].length;
574
- const loopBody = funcBody.substring(loopBodyStart, loopBodyStart + 100);
575
- const elemMatch = loopBody.match(/\{let\s+(\w+)=/);
576
- callLoopVar = elemMatch ? elemMatch[1] : null;
577
- resultLoopVar = callLoopVar;
578
- } else {
579
- callLoopVar = null;
580
- resultLoopVar = null;
581
- }
582
- }
583
-
584
- logger.debug(`[ui-ext:regex] toolcallid-propagate: mVar=${mVar} cards=${cardsVar} callLoop=${callLoopVar} resultLoop=${resultLoopVar}`);
585
-
586
- // 计算注入内容
587
- const marker = makeMarker(this.id);
588
- const tcidCapture = `${marker}var __tcid=(typeof ${msgParam}.toolCallId==='string'&&${msgParam}.toolCallId)||(typeof ${msgParam}.tool_call_id==='string'&&${msgParam}.tool_call_id)||'';`;
589
-
590
- const inlinePatchCall = callLoopVar
591
- ? `,toolCallId:__tcid||${callLoopVar}.id||${callLoopVar}.toolCallId||${callLoopVar}.tool_call_id||''`
592
- : `,toolCallId:__tcid`;
593
- const inlinePatchResult = resultLoopVar
594
- ? `,toolCallId:__tcid||${resultLoopVar}.tool_use_id||''`
595
- : `,toolCallId:__tcid`;
596
- const inlinePatchFallback = `,toolCallId:${mVar}.toolCallId||${mVar}.tool_call_id||''`;
597
-
598
- // 找 cards 数组初始化位置
599
- const arrInitPattern = `,${cardsVar}=[];`;
600
- let arrInitIdx = funcBody.indexOf(arrInitPattern);
601
- if (arrInitIdx < 0) {
602
- // v4.11+: 可能是 ,i=[](后接逗号,如 a=[],o=...),不能直接插 var
603
- const altPattern = `,${cardsVar}=[]`;
604
- arrInitIdx = funcBody.indexOf(altPattern);
605
- if (arrInitIdx < 0) {
606
- logger.warn('[ui-ext:regex] toolcallid-propagate: cards init not found');
607
- return false;
608
- }
609
- // altPattern 后面是逗号(逗号表达式),var 声明无法插入此处
610
- // 改为插入到函数体开头,利用 var 提升特性
611
- arrInitIdx = 0;
612
- } else {
613
- arrInitIdx += arrInitPattern.length;
614
- }
615
- const captureInsertOffset = bodyStart + arrInitIdx;
616
-
617
- // 找 push 位置(支持 kind: 前缀和无前缀)
618
- let pushAnchor = `${cardsVar}.push({kind:`;
619
- if (funcBody.indexOf(pushAnchor) < 0) {
620
- pushAnchor = `${cardsVar}.push({`;
621
- }
622
- const literalCloseOffsets: number[] = [];
623
- let searchPos = 0;
624
- while (literalCloseOffsets.length < 4) {
625
- const idx = funcBody.indexOf(pushAnchor, searchPos);
626
- if (idx < 0) break;
627
- const pushParenStart = bodyStart + idx + `${cardsVar}.push`.length;
628
- const objClose = findObjectLiteralClose(content, pushParenStart);
629
- if (objClose < 0) {
630
- logger.warn(`[ui-ext:regex] toolcallid-propagate: object literal close not found at ${idx}`);
631
- return false;
632
- }
633
- literalCloseOffsets.push(objClose);
634
- searchPos = idx + pushAnchor.length;
635
- }
636
-
637
- if (literalCloseOffsets.length < 2) {
638
- logger.warn(`[ui-ext:regex] toolcallid-propagate: expected >=2 push sites, found ${literalCloseOffsets.length}`);
639
- return false;
640
- }
641
-
642
- // guard 扩展 — P0: guard patch 必须成功,否则 result card 无法识别 message-level tool result
643
- // Shape A (old): &&!cards.some(...)
644
- // Shape B (4.11/latest): &&cards.length===0
645
- const guardAnchorA = `&&!${cardsVar}.some(`;
646
- const guardAnchorB = `&&${cardsVar}.length===0`;
647
- const guardIdxA = funcBody.indexOf(guardAnchorA);
648
- const guardIdxB = funcBody.indexOf(guardAnchorB);
649
- let guardPatch: [number, string, string] | null = null;
650
-
651
- if (guardIdxA >= 0) {
652
- // Shape A: if(<original> && !cards.some(...)) { ... }
653
- let ifStart = guardIdxA - 1;
654
- while (ifStart > 0 && funcBody.substring(ifStart, ifStart + 3) !== 'if(') ifStart--;
655
- if (funcBody.substring(ifStart, ifStart + 3) !== 'if(') {
656
- logger.warn('[ui-ext:regex] toolcallid-propagate: Shape A guard if() not found');
657
- return false;
658
- }
659
- const originalGuard = funcBody.substring(ifStart + 3, guardIdxA);
660
- const expandedGuard = `(${originalGuard}||typeof ${mVar}.toolCallId==\`string\`||typeof ${mVar}.tool_call_id==\`string\`||typeof ${mVar}.toolName==\`string\`||typeof ${mVar}.tool_name==\`string\`)`;
661
- guardPatch = [bodyStart + ifStart + 3, originalGuard, expandedGuard];
662
- logger.debug('[ui-ext:regex] toolcallid-propagate: using Shape A guard');
663
- } else if (guardIdxB >= 0) {
664
- // Shape B: if((<original>) && cards.length===0) { ... }
665
- let ifStart = guardIdxB - 1;
666
- // Shape B may have extra parens: if((...) && cards.length===0)
667
- // Walk back past closing paren to find the if(
668
- while (ifStart > 0 && funcBody.substring(ifStart, ifStart + 3) !== 'if(') ifStart--;
669
- if (funcBody.substring(ifStart, ifStart + 3) !== 'if(') {
670
- logger.warn('[ui-ext:regex] toolcallid-propagate: Shape B guard if() not found');
671
- return false;
672
- }
673
- // The original condition is between if( and &&cards.length===0
674
- // It might be wrapped: if((<cond>)&&cards.length===0)
675
- let originalGuard = funcBody.substring(ifStart + 3, guardIdxB);
676
- // Strip outer parens if present: (expr) → expr
677
- if (originalGuard.startsWith('(') && originalGuard.endsWith(')')) {
678
- originalGuard = originalGuard.substring(1, originalGuard.length - 1);
679
- }
680
- const expandedGuard = `(${originalGuard}||typeof ${mVar}.toolCallId==\`string\`||typeof ${mVar}.tool_call_id==\`string\`||typeof ${mVar}.toolName==\`string\`||typeof ${mVar}.tool_name==\`string\`)`;
681
- guardPatch = [bodyStart + ifStart + 3, funcBody.substring(ifStart + 3, guardIdxB), expandedGuard];
682
- logger.debug('[ui-ext:regex] toolcallid-propagate: using Shape B guard');
683
- } else {
684
- logger.warn('[ui-ext:regex] toolcallid-propagate: neither Shape A nor Shape B guard found — refusing to patch');
685
- return false;
686
- }
687
-
688
- // 从后往前插入
689
- const insertions: Array<[number, string]> = [];
690
- if (literalCloseOffsets.length >= 3) {
691
- insertions.push([literalCloseOffsets[2], inlinePatchFallback]);
692
- }
693
- if (literalCloseOffsets.length >= 2) {
694
- insertions.push([literalCloseOffsets[1], inlinePatchResult]);
695
- }
696
- insertions.push([literalCloseOffsets[0], inlinePatchCall]);
697
- insertions.push([captureInsertOffset, tcidCapture]);
698
-
699
- if (guardPatch) {
700
- const [offset, oldText, newText] = guardPatch;
701
- const globalGuardIdx = content.indexOf(oldText, offset);
702
- if (globalGuardIdx >= 0 && globalGuardIdx < offset + oldText.length + 10) {
703
- content = content.substring(0, globalGuardIdx) + newText + content.substring(globalGuardIdx + oldText.length);
704
- const delta = newText.length - oldText.length;
705
- for (let i = 0; i < insertions.length; i++) {
706
- if (insertions[i][0] > globalGuardIdx) {
707
- insertions[i] = [insertions[i][0] + delta, insertions[i][1]];
708
- }
709
- }
710
- }
711
- }
712
-
713
- for (const [offset, text] of insertions) {
714
- content = content.substring(0, offset) + text + content.substring(offset);
715
- }
716
-
717
- writeFileSync(bundlePath, content, 'utf-8');
718
- const pushCount = literalCloseOffsets.length;
719
- const patchCount = (guardPatch ? 1 : 0) + pushCount + 1;
720
- logger.info(`[ui-ext:regex] toolcallid-propagate: patched ${patchCount} injection points (pushes=${pushCount})`);
721
- return true;
722
- },
723
-
724
- revert(controlUiDir: string): void {
725
- const bundlePath = resolveBundlePath(controlUiDir);
726
- if (bundlePath) { restoreFile(bundlePath); }
727
- },
728
- };
729
-
730
-
731
- // ── Extension 6: remote-agent-message-short-circuit(三策略 A/B/C)───────
732
-
733
- const remoteAgentMessageShortCircuit: UIExtension = {
734
- id: 'remote-agent-message-short-circuit',
735
- version: 2,
736
- sharedFile: 'index-bundle',
737
-
738
- apply(controlUiDir: string): boolean {
739
- const bundlePath = resolveBundlePath(controlUiDir);
740
- if (!bundlePath) return false;
741
- let content = readFileSync(bundlePath, 'utf-8');
742
- if (hasMarker(content, this.id)) { return true; }
743
- if (!backupFile(bundlePath)) return false;
744
-
745
- // 找 showToolCalls 赋值 → 提取 toolCardsVar / hasToolCardsVar
746
- const assignRe = /(\w+)=\w+\.showToolCalls[^;]*\(\w+(?:,\w+)?\)\s*:\s*\[\]\s*,\s*(\w+)\s*=\s*\1\.length\s*>\s*0/;
747
- const assignMatch = content.match(assignRe);
748
- if (!assignMatch) {
749
- logger.warn('[ui-ext:regex] remote-agent-msg: showToolCalls assignment not found');
750
- return false;
751
- }
752
-
753
- const toolCardsVar = assignMatch[1]!;
754
- const hasToolCardsVar = assignMatch[2]!;
755
- const marker = makeMarker(this.id);
756
-
757
- // 策略A (v3.23~v4.10): if(!markdown&&hasToolCards&&isToolResult)
758
- const ifReA = new RegExp(`if\\(!(\\w+)&&${hasToolCardsVar}&&(\\w+)\\)`);
759
- const ifMatchA = content.match(ifReA);
760
-
761
- if (ifMatchA) {
762
- const markdownVar = ifMatchA[1]!;
763
- const isToolResultVar = ifMatchA[2]!;
764
- const patched = `if(${marker}(!${markdownVar}||window.__openagentOwnsToolCards&&window.__openagentOwnsToolCards(${toolCardsVar}))&&${hasToolCardsVar}&&${isToolResultVar})`;
765
- content = content.substring(0, ifMatchA.index!) + patched + content.substring(ifMatchA.index! + ifMatchA[0].length);
766
- writeFileSync(bundlePath, content, 'utf-8');
767
- logger.info(`[ui-ext:regex] remote-agent-msg: patched (strategy A, md=${markdownVar})`);
768
- return true;
769
- }
770
-
771
- // 策略C (2026.4.x): if(!VAR.showToolCalls&&...toolresult...)continue
772
- // 直接匹配 continue 跳过语句,注入 openagentOwnsToolCards 条件
773
- const continueRe = /if\(!(\w+)\.showToolCalls&&(\w+)\.role\.toLowerCase\(\)===`toolresult`\)continue/;
774
- const continueMatch = content.match(continueRe);
775
-
776
- if (continueMatch) {
777
- const scVar = continueMatch[1]!;
778
- const roleVar = continueMatch[2]!;
779
- const patched = `if(!${scVar}.showToolCalls&&!window.__openagentOwnsToolCards?.(${toolCardsVar})&&${roleVar}.role.toLowerCase()===\`toolresult\`)continue`;
780
- content = content.substring(0, continueMatch.index!) + marker + patched + content.substring(continueMatch.index! + continueMatch[0].length);
781
- writeFileSync(bundlePath, content, 'utf-8');
782
- logger.info(`[ui-ext:regex] remote-agent-msg: patched (strategy C, sc=${scVar})`);
783
- return true;
784
- }
785
-
786
- // Strategy D (v5.17+ fallback): search for any if-condition referencing
787
- // hasToolCards alongside a role-like variable — v5.17+ may replace the
788
- // ternary/compound patterns of A/B/C with a different expression shape.
789
- const strategyDRe = new RegExp(
790
- `if\\(!` + `([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*&&\\s*` +
791
- hasToolCardsVar + `\\s*&&\\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*\\)`
792
- );
793
- const strategyDMatch = content.match(strategyDRe);
794
- if (strategyDMatch) {
795
- const markdownVarD = strategyDMatch[1]!;
796
- const isToolResultVarD = strategyDMatch[2]!;
797
- const dMatchFull = strategyDMatch[0];
798
- const dIdx = strategyDMatch.index!;
799
- const patched = `if(` + marker +
800
- `(!` + markdownVarD + `||window.__openagentOwnsToolCards&&window.__openagentOwnsToolCards(` +
801
- toolCardsVar + `))&&` + hasToolCardsVar + `&&` + isToolResultVarD + `)`;
802
- content = content.substring(0, dIdx) + patched + content.substring(dIdx + dMatchFull.length);
803
- writeFileSync(bundlePath, content, 'utf-8');
804
- logger.info(`[ui-ext:regex] remote-agent-msg: patched (strategy D, md=${markdownVarD})`);
805
- return true;
806
- }
807
-
808
- // 策略B (v4.11~v4.1x fallback): compound = hasToolCards && (showToolCalls ?? !0)
809
- const compoundRe = new RegExp(`(\\w+)=${hasToolCardsVar}&&\\(\\w+\\.showToolCalls\\?\\?!0\\)`);
810
- const compoundMatch = content.match(compoundRe);
811
- if (!compoundMatch) {
812
- // All strategies (A/C/D/B) failed — dump context for diagnostics
813
- const anchorHintIdx = content.indexOf(hasToolCardsVar);
814
- const ctxStart = anchorHintIdx > 200 ? anchorHintIdx - 200 : 0;
815
- const ctxEnd = Math.min(anchorHintIdx + 200, content.length);
816
- logger.warn(
817
- `[ui-ext:regex] remote-agent-msg: all strategies (A/C/D/B) failed for toolCards=${toolCardsVar}. ` +
818
- `Context around hasToolCardsVar (at ${anchorHintIdx}): ` +
819
- content.substring(ctxStart, ctxEnd)
820
- );
821
- return false;
822
- }
823
-
824
- const compoundVar = compoundMatch[1]!;
825
- const ifReB = new RegExp(`if\\(!(\\w+)&&!${compoundVar}&&`);
826
- const ifMatchB = content.match(ifReB);
827
- if (!ifMatchB) {
828
- logger.warn(`[ui-ext:regex] remote-agent-msg: compound var ${compoundVar} found but if not matched`);
829
- return false;
830
- }
831
-
832
- const markdownVarB = ifMatchB[1]!;
833
- const patchedCond = `if(${marker}(!${markdownVarB}||window.__openagentOwnsToolCards&&window.__openagentOwnsToolCards(${toolCardsVar}))&&!${compoundVar}&&`;
834
- content = content.substring(0, ifMatchB.index!) + patchedCond + content.substring(ifMatchB.index! + ifMatchB[0].length);
835
- writeFileSync(bundlePath, content, 'utf-8');
836
- logger.info(`[ui-ext:regex] remote-agent-msg: patched (strategy B, md=${markdownVarB}, compound=${compoundVar})`);
837
- return true;
838
- },
839
-
840
- revert(controlUiDir: string): void {
841
- const bundlePath = resolveBundlePath(controlUiDir);
842
- if (bundlePath) { restoreFile(bundlePath); }
843
- },
844
- };
845
-
846
- // ── Extension 7: mention-hooks ───────────────────────────────────────────
847
-
848
- const mentionHooks: UIExtension = {
849
- id: 'mention-hooks',
850
- version: 3,
851
- sharedFile: 'index-bundle',
852
-
853
- apply(controlUiDir: string): boolean {
854
- const bundlePath = resolveBundlePath(controlUiDir);
855
- if (!bundlePath) return false;
856
- let content = readFileSync(bundlePath, 'utf-8');
857
- if (hasMarker(content, this.id)) { return true; }
858
- if (!backupFile(bundlePath)) return false;
859
-
860
- // 注入点 A: handleKeyDown
861
- // 锚定 ;return}if(X.key===`Enter`&&!X.shiftKey){
862
- // 不再依赖内层 isComposing:2026.6.10 已把 isComposing 检查拆到独立 handler
863
- const keydownRe = /;return\}if\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.key===`Enter`&&!\1\.shiftKey\)\{/;
864
- const keydownMatch = content.match(keydownRe);
865
- if (!keydownMatch) {
866
- logger.warn('[ui-ext:regex] mention-hooks: keydown anchor not found');
867
- return false;
868
- }
869
- const eventParam = keydownMatch[1];
870
- const keydownInsertIdx = content.indexOf(keydownMatch[0]) + keydownMatch[0].indexOf('if(');
871
- const markerA = makeMarker(this.id);
872
- const hookA = `${markerA}if(window.__clMention?.onKeyDown(${eventParam}))return;`;
873
- content = content.substring(0, keydownInsertIdx) + hookA + content.substring(keydownInsertIdx);
874
-
875
- // 注入点 B: handleInput — 四种形态兼容
876
- // Shape A (old): .onDraftChange(target.value)}}
877
- // Shape B (4.11~4.x): .onDraftChange(target.value) (arrow function 内)
878
- // Shape C (2026.6.x): be=t=>{let n=t.target;pL(e,n.value)} — 引入 pL helper
879
- // Shape D (generic): .onDraftChange(任意.target.value) 不加结尾限制
880
- const inputReA = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)\}\}/;
881
- const inputReB = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.value\)/;
882
- 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\)\}/;
883
- const inputReD = /\.onDraftChange\(([a-zA-Z_$][a-zA-Z0-9_$]*)\.target\.value\)/;
884
-
885
- let inputMatch = content.match(inputReA);
886
- let inputShape: 'A' | 'B' | 'C' | 'D' = 'A';
887
-
888
- if (!inputMatch) {
889
- inputMatch = content.match(inputReC);
890
- inputShape = 'C';
891
- }
892
- if (!inputMatch) {
893
- inputMatch = content.match(inputReB);
894
- inputShape = 'B';
895
- }
896
- if (!inputMatch) {
897
- inputMatch = content.match(inputReD);
898
- inputShape = 'D';
899
- }
900
-
901
- if (!inputMatch) {
902
- // All shapes missed — dump context for diagnostics
903
- const draftHintIdx = content.indexOf('.onDraftChange');
904
- const ctxStart = draftHintIdx > 200 ? draftHintIdx - 200 : 0;
905
- const ctxEnd = Math.min(draftHintIdx + 200, content.length);
906
- logger.warn(
907
- `[ui-ext:regex] mention-hooks: input anchor not found (Shape A/B/C/D all missed). ` +
908
- `Context around onDraftChange (at ${draftHintIdx}): ` +
909
- content.substring(ctxStart, ctxEnd)
910
- );
911
- return false;
912
- }
913
- const targetVar = inputMatch[1];
914
- const inputAnchorIdx = content.indexOf(inputMatch[0]);
915
- // Shape A: }} 之前;Shape C: 末尾 } 之前;Shape B/D: ) 之后
916
- const draftCallEnd = inputShape === 'A'
917
- ? inputAnchorIdx + inputMatch[0].length - 2
918
- : inputShape === 'C'
919
- ? inputAnchorIdx + inputMatch[0].length - 1
920
- : inputAnchorIdx + inputMatch[0].length;
921
- const hookB = `;window.__clMention?.onInput?.(${targetVar})`;
922
- content = content.substring(0, draftCallEnd) + hookB + content.substring(draftCallEnd);
923
-
924
- writeFileSync(bundlePath, content, 'utf-8');
925
- logger.info(`[ui-ext:regex] mention-hooks: injected (shape=${inputShape}, event=${eventParam}, target=${targetVar})`);
926
- return true;
927
- },
928
-
929
- revert(controlUiDir: string): void {
930
- const bundlePath = resolveBundlePath(controlUiDir);
931
- if (bundlePath) { restoreFile(bundlePath); }
932
- },
933
- };
934
-
935
- // ── Registry ─────────────────────────────────────────────────────────────
936
-
937
- export const UI_EXTENSIONS: UIExtension[] = [
938
- overrideScript,
939
- htmlScriptTag,
940
- toolcardRender,
941
- toolcallidPropagate,
942
- remoteAgentMessageShortCircuit,
943
- mentionHooks,
944
- ];