openclaw-openagent 1.0.13 → 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 (128) hide show
  1. package/dist/src/messaging/executor.d.ts +2 -3
  2. package/dist/src/messaging/executor.js +4 -31
  3. package/dist/src/plugin-ui/assets/openagent-override.js +4 -5
  4. package/dist/src/runtime/update-checker.d.ts +1 -1
  5. package/dist/src/runtime/update-checker.js +9 -46
  6. package/package.json +1 -3
  7. package/src/app/channel-tools.ts +0 -249
  8. package/src/app/discovery-tools.ts +0 -273
  9. package/src/app/download-file-tool.ts +0 -133
  10. package/src/app/hooks.ts +0 -144
  11. package/src/app/index.ts +0 -84
  12. package/src/app/messaging-tools.ts +0 -79
  13. package/src/app/ops-tools.ts +0 -155
  14. package/src/app/remote-agent-tool.ts +0 -762
  15. package/src/app/types.ts +0 -68
  16. package/src/app/upload-file-tool.ts +0 -411
  17. package/src/app/verbose-preflight.ts +0 -190
  18. package/src/auth/config.ts +0 -266
  19. package/src/auth/credential-manager.ts +0 -146
  20. package/src/auth/index.ts +0 -24
  21. package/src/auth/verify.ts +0 -99
  22. package/src/channel.ts +0 -566
  23. package/src/compat.ts +0 -94
  24. package/src/config/config-schema.ts +0 -45
  25. package/src/messaging/aggregator.ts +0 -120
  26. package/src/messaging/collector.ts +0 -89
  27. package/src/messaging/executor.ts +0 -72
  28. package/src/messaging/inbound.ts +0 -150
  29. package/src/messaging/index.ts +0 -11
  30. package/src/messaging/mention-protocol.ts +0 -94
  31. package/src/messaging/process-c2c-request.ts +0 -564
  32. package/src/messaging/process-message.ts +0 -373
  33. package/src/messaging/scheduler.ts +0 -55
  34. package/src/messaging/types.ts +0 -38
  35. package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
  36. package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
  37. package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
  38. package/src/plugin-ui/adapters/oc-unknown.js +0 -48
  39. package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
  40. package/src/plugin-ui/assets/bg.png +0 -0
  41. package/src/plugin-ui/assets/icon.png +0 -0
  42. package/src/plugin-ui/assets/magic.svg +0 -5
  43. package/src/plugin-ui/assets/openagent-override.js +0 -11263
  44. package/src/plugin-ui/build.cjs +0 -463
  45. package/src/plugin-ui/index.ts +0 -18
  46. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
  47. package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
  48. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
  49. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
  50. package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
  51. package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -832
  52. package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
  53. package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
  54. package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
  55. package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
  56. package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
  57. package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
  58. package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -262
  59. package/src/plugin-ui/modules/loader/bootstrap.js +0 -133
  60. package/src/plugin-ui/modules/loader/shared-state.js +0 -838
  61. package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
  62. package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
  63. package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
  64. package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
  65. package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
  66. package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
  67. package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
  68. package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
  69. package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
  70. package/src/plugin-ui/modules/remote-agent/styles.js +0 -903
  71. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
  72. package/src/plugin-ui/postinstall-deploy.cjs +0 -52
  73. package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
  74. package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
  75. package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
  76. package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
  77. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
  78. package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
  79. package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
  80. package/src/proxy/auth-proxy.ts +0 -414
  81. package/src/runtime/account.ts +0 -549
  82. package/src/runtime/index.ts +0 -7
  83. package/src/runtime/plugin-runtime.ts +0 -94
  84. package/src/runtime/registry.ts +0 -71
  85. package/src/runtime/update-checker.ts +0 -286
  86. package/src/sdk/CLASS_MAP.md +0 -143
  87. package/src/sdk/index.d.ts +0 -126
  88. package/src/sdk/index.js +0 -23990
  89. package/src/sdk/modules/cloud-search-module.js +0 -1117
  90. package/src/sdk/modules/follow-module.js +0 -1069
  91. package/src/sdk/modules/group-module.js +0 -7397
  92. package/src/sdk/modules/relationship-module.js +0 -2269
  93. package/src/sdk/modules/signaling-module.js +0 -1468
  94. package/src/sdk/modules/tim-upload-plugin.js +0 -730
  95. package/src/sdk/node-env/http-request.js +0 -90
  96. package/src/sdk/node-env/index.js +0 -57
  97. package/src/sdk/node-env/storage.js +0 -114
  98. package/src/sdk/package.json +0 -10
  99. package/src/sdk/tsconfig.json +0 -16
  100. package/src/state/pending-invocation-store.ts +0 -43
  101. package/src/state/store.ts +0 -756
  102. package/src/tim/c2c.ts +0 -451
  103. package/src/tim/channels.ts +0 -364
  104. package/src/tim/client.ts +0 -330
  105. package/src/tim/index.ts +0 -18
  106. package/src/tim/messages.ts +0 -166
  107. package/src/tim/sdk-logger-init.ts +0 -50
  108. package/src/tools.ts +0 -10
  109. package/src/transport/factory.ts +0 -95
  110. package/src/transport/oasn/index.ts +0 -17
  111. package/src/transport/oasn/oasn-agent-card.ts +0 -111
  112. package/src/transport/oasn/oasn-discovery.ts +0 -108
  113. package/src/transport/oasn/oasn-files.ts +0 -210
  114. package/src/transport/oasn/oasn-http.ts +0 -529
  115. package/src/transport/oasn/oasn-invocation.ts +0 -604
  116. package/src/transport/oasn/oasn-normalize.ts +0 -133
  117. package/src/transport/oasn/oasn-register.ts +0 -33
  118. package/src/transport/oasn/oasn-transport.ts +0 -318
  119. package/src/transport/oasn/oasn-types.ts +0 -404
  120. package/src/transport/tim/index.ts +0 -8
  121. package/src/transport/tim/tim-transport.ts +0 -515
  122. package/src/transport/types.ts +0 -538
  123. package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
  124. package/src/types/openclaw.d.ts +0 -97
  125. package/src/types/tencentcloud-chat.d.ts +0 -15
  126. package/src/util/http.ts +0 -113
  127. package/src/util/logger.ts +0 -131
  128. 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
- ];