openclaw-openagent 1.0.11 → 1.0.12

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 (56) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/src/app/remote-agent-tool.js +110 -14
  3. package/dist/src/app/types.d.ts +2 -2
  4. package/dist/src/plugin-ui/adapters/adapters/oc-2026-04.js +103 -0
  5. package/dist/src/plugin-ui/adapters/adapters/oc-2026-05.js +125 -0
  6. package/dist/src/plugin-ui/adapters/adapters/oc-2026-06.js +125 -0
  7. package/dist/src/plugin-ui/adapters/adapters/oc-unknown.js +48 -0
  8. package/dist/src/plugin-ui/adapters/oc-2026-04.js +103 -0
  9. package/dist/src/plugin-ui/adapters/oc-2026-05.js +125 -0
  10. package/dist/src/plugin-ui/adapters/oc-2026-06.js +125 -0
  11. package/dist/src/plugin-ui/adapters/oc-unknown.js +48 -0
  12. package/dist/src/plugin-ui/assets/openagent-override.js +1007 -258
  13. package/dist/src/plugin-ui/index.d.ts +1 -1
  14. package/dist/src/plugin-ui/index.js +2 -2
  15. package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +2 -1
  16. package/dist/src/plugin-ui/ui-extension-loader/index.js +5 -5
  17. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +75 -8
  18. package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +4 -1
  19. package/dist/src/state/store.d.ts +21 -0
  20. package/dist/src/state/store.js +54 -0
  21. package/dist/src/transport/oasn/oasn-invocation.d.ts +3 -0
  22. package/dist/src/transport/oasn/oasn-invocation.js +28 -12
  23. package/dist/src/transport/oasn/oasn-types.d.ts +8 -4
  24. package/index.ts +1 -1
  25. package/package.json +4 -3
  26. package/src/app/remote-agent-tool.ts +131 -16
  27. package/src/app/types.ts +2 -2
  28. package/src/plugin-ui/adapters/oc-2026-04.js +103 -0
  29. package/src/plugin-ui/adapters/oc-2026-05.js +125 -0
  30. package/src/plugin-ui/adapters/oc-2026-06.js +125 -0
  31. package/src/plugin-ui/adapters/oc-unknown.js +48 -0
  32. package/src/plugin-ui/assets/openagent-override.js +1007 -258
  33. package/src/plugin-ui/build.cjs +249 -38
  34. package/src/plugin-ui/index.ts +2 -2
  35. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +102 -12
  36. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +105 -3
  37. package/src/plugin-ui/modules/agent-book/panel/styles.js +42 -35
  38. package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +4 -2
  39. package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +4 -1
  40. package/src/plugin-ui/modules/agent-book/scanner.js +17 -5
  41. package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +5 -0
  42. package/src/plugin-ui/modules/loader/shared-state.js +244 -20
  43. package/src/plugin-ui/modules/remote-agent/execution-card.js +54 -16
  44. package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +5 -23
  45. package/src/plugin-ui/modules/remote-agent/output-card.js +13 -7
  46. package/src/plugin-ui/modules/remote-agent/render-hooks.js +53 -41
  47. package/src/plugin-ui/modules/remote-agent/styles.js +230 -88
  48. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +72 -4
  49. package/src/plugin-ui/postinstall-deploy.cjs +52 -0
  50. package/src/plugin-ui/ui-extension-loader/index.ts +6 -6
  51. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +81 -9
  52. package/src/plugin-ui/ui-extension-loader/types.ts +5 -1
  53. package/src/state/store.ts +80 -0
  54. package/src/transport/oasn/oasn-invocation.ts +47 -12
  55. package/src/transport/oasn/oasn-types.ts +6 -2
  56. package/src/types/openclaw-plugin-sdk-media-store.d.ts +9 -0
@@ -13,25 +13,33 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
+ const crypto = require('crypto');
17
+ const childProcess = require('child_process');
16
18
 
17
19
  // ── 读取 OpenClaw 版本(用于注入到产物中)──
18
- function resolveHostVersion() {
20
+ function resolveBuildHostVersion() {
19
21
  // 优先使用环境变量
20
- if (process.env.OPENCLAW_HOST_VERSION) return process.env.OPENCLAW_HOST_VERSION;
22
+ if (process.env.OPENCLAW_HOST_VERSION) return normalizeHostVersion(process.env.OPENCLAW_HOST_VERSION);
21
23
 
22
- // package.json devDependencies 读取
23
- try {
24
- var pkgPath = path.join(__dirname, '..', 'package.json');
25
- if (fs.existsSync(pkgPath)) {
26
- var pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
27
- var ver = (pkg.devDependencies && pkg.devDependencies.openclaw) || '';
28
- // 去掉 ^ 前缀: "^2026.4.22" → "2026.4.22"
29
- ver = String(ver).replace(/^[\^~>=<]+/, '').trim();
30
- if (ver) return ver;
31
- }
32
- } catch (e) {}
24
+ var packageCandidates = [
25
+ path.join(__dirname, '..', '..', 'node_modules', 'openclaw', 'package.json'),
26
+ path.join(__dirname, '..', '..', 'package.json'),
27
+ ];
28
+ for (var i = 0; i < packageCandidates.length; i++) {
29
+ try {
30
+ if (!fs.existsSync(packageCandidates[i])) continue;
31
+ var candidatePkg = JSON.parse(fs.readFileSync(packageCandidates[i], 'utf-8'));
32
+ if (candidatePkg && candidatePkg.name === 'openclaw') {
33
+ var exactVersion = normalizeHostVersion(candidatePkg.version);
34
+ if (exactVersion) return exactVersion;
35
+ }
36
+ var depVersion = candidatePkg && candidatePkg.devDependencies && candidatePkg.devDependencies.openclaw;
37
+ depVersion = normalizeHostVersion(String(depVersion || '').replace(/^[\^~>=<]+/, '').trim());
38
+ if (depVersion) return depVersion;
39
+ } catch (e) {}
40
+ }
33
41
 
34
- return '2026.4.22'; // 默认 fallback
42
+ return '';
35
43
  }
36
44
 
37
45
  // ═══════════════════════════════════════════════════════════════
@@ -78,10 +86,201 @@ const MANIFEST = [
78
86
  // ═══════════════════════════════════════════════════════════════
79
87
 
80
88
  const modulesDir = path.join(__dirname, 'modules');
89
+ const adaptersDir = path.join(__dirname, 'adapters');
81
90
  const assetsDir = path.join(__dirname, 'assets');
82
91
  const outFile = path.join(assetsDir, 'openagent-override.js');
83
92
  const iconFile = path.join(assetsDir, 'icon.png');
84
93
  const bgFile = path.join(assetsDir, 'bg.png');
94
+ const HOST_VERSION_TOKEN = "hostVersion: '__OPENCLAW_HOST_VERSION__'";
95
+ const BUILD_ID_TOKEN = "buildId: '__OPENAGENT_BUILD_ID__'";
96
+ const BUILD_ID_ASSIGNMENT_RE = /buildId:\s*(?:"[^"]*"|'[^']*')/;
97
+ const HOST_ADAPTER_START = '/*__OPENAGENT_HOST_ADAPTER_START__*/';
98
+ const HOST_ADAPTER_END = '/*__OPENAGENT_HOST_ADAPTER_END__*/';
99
+ const HOST_ADAPTER_BLOCK_RE = /\/\*__OPENAGENT_HOST_ADAPTER_START__\*\/[\s\S]*?\/\*__OPENAGENT_HOST_ADAPTER_END__\*\//;
100
+
101
+ function normalizeHostVersion(version) {
102
+ var match = String(version || '').match(/20\d{2}\.\d{1,2}\.\d{1,2}(?:-[A-Za-z0-9._-]+)?/);
103
+ return match ? match[0] : '';
104
+ }
105
+
106
+ function injectHostVersion(content, hostVersion) {
107
+ hostVersion = normalizeHostVersion(hostVersion);
108
+ if (!hostVersion) return content;
109
+ return content.replace(HOST_VERSION_TOKEN, 'hostVersion: ' + JSON.stringify(hostVersion));
110
+ }
111
+
112
+ function resolveHostAdapterId(hostVersion) {
113
+ var parsed = normalizeHostVersion(hostVersion).split('.');
114
+ var year = parseInt(parsed[0], 10);
115
+ var month = parseInt(parsed[1], 10);
116
+ if (year === 2026 && month === 4) return 'oc-2026-04';
117
+ if (year === 2026 && month === 5) return 'oc-2026-05';
118
+ if (year === 2026 && month === 6) return 'oc-2026-06';
119
+ return 'oc-unknown';
120
+ }
121
+
122
+ function getHostAdapterBlock(hostVersion) {
123
+ var adapterId = resolveHostAdapterId(hostVersion);
124
+ var adapterPath = path.join(adaptersDir, adapterId + '.js');
125
+ if (!fs.existsSync(adapterPath)) {
126
+ adapterId = 'oc-unknown';
127
+ adapterPath = path.join(adaptersDir, adapterId + '.js');
128
+ }
129
+ var adapterContent = fs.readFileSync(adapterPath, 'utf-8').trimEnd();
130
+ return [
131
+ HOST_ADAPTER_START,
132
+ '// HOST ADAPTER: ' + adapterId,
133
+ adapterContent,
134
+ HOST_ADAPTER_END,
135
+ ].join('\n');
136
+ }
137
+
138
+ function injectHostAdapter(content, hostVersion) {
139
+ var block = getHostAdapterBlock(hostVersion);
140
+ if (HOST_ADAPTER_BLOCK_RE.test(content)) {
141
+ return content.replace(HOST_ADAPTER_BLOCK_RE, block);
142
+ }
143
+ return content;
144
+ }
145
+
146
+ function renderOverrideContent(content, hostVersion) {
147
+ return injectBuildId(injectHostAdapter(injectHostVersion(content, hostVersion), hostVersion));
148
+ }
149
+
150
+ function hashContent(content) {
151
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 12);
152
+ }
153
+
154
+ function injectBuildId(content) {
155
+ var normalized = content.replace(BUILD_ID_ASSIGNMENT_RE, BUILD_ID_TOKEN);
156
+ var buildId = hashContent(normalized);
157
+ return normalized.replace(BUILD_ID_TOKEN, 'buildId: ' + JSON.stringify(buildId));
158
+ }
159
+
160
+ function resolveControlUiHostVersion(controlUiDir) {
161
+ if (process.env.OPENCLAW_HOST_VERSION) return normalizeHostVersion(process.env.OPENCLAW_HOST_VERSION);
162
+
163
+ var candidates = [
164
+ path.join(controlUiDir, '..', '..', 'package.json'),
165
+ path.join(controlUiDir, '..', 'package.json'),
166
+ ];
167
+ for (var i = 0; i < candidates.length; i++) {
168
+ try {
169
+ if (!fs.existsSync(candidates[i])) continue;
170
+ var pkg = JSON.parse(fs.readFileSync(candidates[i], 'utf-8'));
171
+ if (pkg && pkg.name === 'openclaw') return normalizeHostVersion(pkg.version);
172
+ } catch (e) {}
173
+ }
174
+ return '';
175
+ }
176
+
177
+ function pushUniquePath(list, candidate) {
178
+ if (!list || !candidate) return;
179
+ var resolved = path.resolve(candidate);
180
+ for (var i = 0; i < list.length; i++) {
181
+ if (path.resolve(list[i]) === resolved) return;
182
+ }
183
+ list.push(resolved);
184
+ }
185
+
186
+ function collectCommandOutputLines(command, args) {
187
+ try {
188
+ var result = childProcess.spawnSync(command, args, {
189
+ encoding: 'utf-8',
190
+ stdio: ['ignore', 'pipe', 'ignore'],
191
+ });
192
+ return String(result.stdout || '')
193
+ .split(/\r?\n/)
194
+ .map(function(line) { return line.trim(); })
195
+ .filter(Boolean);
196
+ } catch (e) {
197
+ return [];
198
+ }
199
+ }
200
+
201
+ function collectGlobalNodeModuleRoots() {
202
+ var roots = [];
203
+
204
+ try {
205
+ pushUniquePath(roots, path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules'));
206
+ } catch (e) {}
207
+
208
+ var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
209
+ var npmRoots = collectCommandOutputLines(npmCommand, ['root', '-g']);
210
+ for (var n = 0; n < npmRoots.length; n++) {
211
+ pushUniquePath(roots, npmRoots[n]);
212
+ }
213
+
214
+ var openclawLocator = process.platform === 'win32' ? 'where' : 'which';
215
+ var openclawBins = collectCommandOutputLines(openclawLocator, ['openclaw']);
216
+ for (var b = 0; b < openclawBins.length; b++) {
217
+ var binPath = openclawBins[b];
218
+ try {
219
+ pushUniquePath(roots, path.join(path.dirname(binPath), '..', 'lib', 'node_modules'));
220
+ } catch (e) {}
221
+ try {
222
+ var realBin = fs.realpathSync(binPath);
223
+ var needle = path.join('node_modules', 'openclaw');
224
+ var idx = realBin.lastIndexOf(needle);
225
+ if (idx >= 0) {
226
+ pushUniquePath(roots, realBin.substring(0, idx + 'node_modules'.length));
227
+ }
228
+ } catch (e) {}
229
+ }
230
+
231
+ return roots;
232
+ }
233
+
234
+ function addGlobalOpenclawTargets(deployTargets, assetTargets) {
235
+ var roots = collectGlobalNodeModuleRoots();
236
+ for (var i = 0; i < roots.length; i++) {
237
+ var root = roots[i];
238
+ pushUniquePath(deployTargets, path.join(root, 'openclaw', 'dist', 'control-ui', 'openagent-override.js'));
239
+ pushUniquePath(deployTargets, path.join(root, 'openclaw-openagent', 'src', 'plugin-ui', 'assets', 'openagent-override.js'));
240
+ pushUniquePath(deployTargets, path.join(root, 'openclaw-openagent', 'dist', 'src', 'plugin-ui', 'assets', 'openagent-override.js'));
241
+
242
+ pushUniquePath(assetTargets, path.join(root, 'openclaw', 'dist', 'control-ui'));
243
+ pushUniquePath(assetTargets, path.join(root, 'openclaw-openagent', 'src', 'plugin-ui', 'assets'));
244
+ pushUniquePath(assetTargets, path.join(root, 'openclaw-openagent', 'dist', 'src', 'plugin-ui', 'assets'));
245
+ }
246
+ }
247
+
248
+ function updateControlUiIndex(controlUiDir, contentHash, hostVersion) {
249
+ var indexPath = path.join(controlUiDir, 'index.html');
250
+ if (!fs.existsSync(indexPath)) return false;
251
+
252
+ var html = fs.readFileSync(indexPath, 'utf-8');
253
+ var htmlMarker = '<!--openagent:html-script-tag-->';
254
+ var hostVersionAttr = hostVersion ? ' data-openagent-host-version="' + hostVersion + '"' : '';
255
+ var scriptTag = htmlMarker + '<script src="./openagent-override.js?v=' + contentHash + '"' + hostVersionAttr + '></script>';
256
+ var markedScriptPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
257
+ var oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
258
+ var nextHtml;
259
+
260
+ if (/(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/.test(html)) {
261
+ nextHtml = html.replace(markedScriptPattern, scriptTag + '\n ');
262
+ if (nextHtml === html && !nextHtml.includes('openagent-override.js')) {
263
+ nextHtml = nextHtml.replace(/(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/, scriptTag + '\n ');
264
+ } else if (nextHtml === html && nextHtml.includes('openagent-override.js')) {
265
+ nextHtml = nextHtml.replace(oldScriptPattern, '').replace(/(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/, scriptTag + '\n ');
266
+ }
267
+ } else {
268
+ nextHtml = html.replace(oldScriptPattern, '');
269
+ var viteAnchor = '<script type="module" crossorigin src="./assets/index-';
270
+ var anchorIdx = nextHtml.indexOf(viteAnchor);
271
+ if (anchorIdx >= 0) {
272
+ nextHtml = nextHtml.substring(0, anchorIdx) + scriptTag + '\n ' + nextHtml.substring(anchorIdx);
273
+ } else {
274
+ nextHtml = nextHtml.replace('</head>', scriptTag + '\n</head>');
275
+ }
276
+ }
277
+
278
+ if (nextHtml && nextHtml !== html) {
279
+ fs.writeFileSync(indexPath, nextHtml, 'utf-8');
280
+ return true;
281
+ }
282
+ return false;
283
+ }
85
284
 
86
285
  const HEADER = `/**
87
286
  * OpenAgent UI Override — Auto-generated, do not edit directly!
@@ -122,6 +321,12 @@ function build() {
122
321
  body += `// MODULE: ${file}\n`;
123
322
  body += `// ════════════════════════════════════════════════════════════════\n\n`;
124
323
  body += content.trimEnd() + '\n';
324
+ if (file === 'loader/shared-state.js') {
325
+ body += `\n// ════════════════════════════════════════════════════════════════\n`;
326
+ body += `// MODULE: host-adapter/selected\n`;
327
+ body += `// ════════════════════════════════════════════════════════════════\n\n`;
328
+ body += getHostAdapterBlock('') + '\n';
329
+ }
125
330
  }
126
331
 
127
332
  if (errors > 0) {
@@ -133,11 +338,12 @@ function build() {
133
338
  // ── 不做构建时版本注入,由运行时 DOM/CSS 特征检测 ──
134
339
  // (build.cjs 无法知道实际运行的 OpenClaw 版本)
135
340
  var output = HEADER + body + FOOTER;
341
+ var assetOutput = renderOverrideContent(output, '');
136
342
 
137
343
  fs.mkdirSync(path.dirname(outFile), { recursive: true });
138
- fs.writeFileSync(outFile, output, 'utf-8');
344
+ fs.writeFileSync(outFile, assetOutput, 'utf-8');
139
345
 
140
- const lineCount = output.split('\n').length;
346
+ const lineCount = assetOutput.split('\n').length;
141
347
  console.log(`✅ Built: ${path.relative(process.cwd(), outFile)} (${lineCount} lines)`);
142
348
 
143
349
  // ── Auto-deploy to runtime targets ──────────────────────────────────────
@@ -162,19 +368,24 @@ function build() {
162
368
  path.join(homedir, '.nvm/versions/node/v22.21.1/lib/node_modules/openclaw-openagent/src/plugin-ui/assets/openagent-override.js'),
163
369
  path.join(homedir, '.nvm/versions/node/v22.21.1/lib/node_modules/openclaw-openagent/dist/src/plugin-ui/assets/openagent-override.js'),
164
370
  ];
165
-
166
- // 动态发现 control-ui 目录(通过 node 可执行文件路径推断)
167
- try {
168
- const nodeDir = path.dirname(process.execPath);
169
- const controlUiCandidate = path.join(nodeDir, '..', 'lib', 'node_modules', 'openclaw', 'dist', 'control-ui', 'openagent-override.js');
170
- DEPLOY_TARGETS.push(path.resolve(controlUiCandidate));
171
- } catch {}
371
+ addGlobalOpenclawTargets(DEPLOY_TARGETS, null);
172
372
 
173
373
  let deployed = 0;
174
374
  for (const target of DEPLOY_TARGETS) {
175
375
  try {
176
376
  if (fs.existsSync(path.dirname(target))) {
177
- fs.copyFileSync(outFile, target);
377
+ const targetDir = path.dirname(target);
378
+ const hostVersion = path.basename(targetDir) === 'control-ui'
379
+ ? resolveControlUiHostVersion(targetDir)
380
+ : '';
381
+ const targetOutput = renderOverrideContent(output, hostVersion);
382
+ fs.writeFileSync(target, targetOutput, 'utf-8');
383
+ if (hostVersion) {
384
+ const contentHash = hashContent(targetOutput);
385
+ if (updateControlUiIndex(targetDir, contentHash, hostVersion)) {
386
+ console.log(` ↻ updated index.html cache-bust (${hostVersion}, ${resolveHostAdapterId(hostVersion)}, ${contentHash})`);
387
+ }
388
+ }
178
389
  console.log(` → ${path.relative(homedir, target)}`);
179
390
  deployed++;
180
391
  }
@@ -200,11 +411,7 @@ function build() {
200
411
  path.join(homedir, '.nvm/versions/node/v22.21.1/lib/node_modules/openclaw-openagent/src/plugin-ui/assets'),
201
412
  path.join(homedir, '.nvm/versions/node/v22.21.1/lib/node_modules/openclaw-openagent/dist/src/plugin-ui/assets'),
202
413
  ];
203
-
204
- try {
205
- const nodeDir = path.dirname(process.execPath);
206
- ASSET_DEPLOY_TARGETS.push(path.resolve(path.join(nodeDir, '..', 'lib', 'node_modules', 'openclaw', 'dist', 'control-ui')));
207
- } catch {}
414
+ addGlobalOpenclawTargets(null, ASSET_DEPLOY_TARGETS);
208
415
 
209
416
  // ── Deploy image assets (icon.png, bg.png) ──
210
417
  const IMAGE_ASSETS = [
@@ -237,16 +444,20 @@ const isWatchMode = process.argv.includes('--watch');
237
444
  build();
238
445
 
239
446
  if (isWatchMode) {
240
- console.log(`\n👀 Watching for changes in ${path.relative(process.cwd(), modulesDir)}...`);
447
+ console.log(`\n👀 Watching for changes in ${path.relative(process.cwd(), modulesDir)} and ${path.relative(process.cwd(), adaptersDir)}...`);
241
448
 
242
449
  let debounceTimer;
243
- fs.watch(modulesDir, { recursive: true }, (eventType, filename) => {
244
- // Ignore dot files and non-js files
245
- if (!filename || filename.startsWith('.') || !filename.endsWith('.js')) return;
450
+ function watchSourceDir(sourceDir) {
451
+ fs.watch(sourceDir, { recursive: true }, (eventType, filename) => {
452
+ // Ignore dot files and non-js files
453
+ if (!filename || filename.startsWith('.') || !filename.endsWith('.js')) return;
246
454
 
247
- clearTimeout(debounceTimer);
248
- debounceTimer = setTimeout(() => {
249
- build();
250
- }, 100);
251
- });
455
+ clearTimeout(debounceTimer);
456
+ debounceTimer = setTimeout(() => {
457
+ build();
458
+ }, 100);
459
+ });
460
+ }
461
+ watchSourceDir(modulesDir);
462
+ watchSourceDir(adaptersDir);
252
463
  }
@@ -13,6 +13,6 @@ import { loadUIExtensions } from './ui-extension-loader/index.js';
13
13
  * 由 index.ts register() 调用。内部通过 Manifest Reconciler
14
14
  * 自动完成扩展点的 apply / revert / skip。
15
15
  */
16
- export async function injectControlUiOverride(): Promise<void> {
17
- await loadUIExtensions();
16
+ export async function injectControlUiOverride(hostVersion?: string): Promise<void> {
17
+ await loadUIExtensions({ hostVersion });
18
18
  }
@@ -6,6 +6,7 @@ let panel = null;
6
6
  let backdrop = null;
7
7
 
8
8
  let inputWrapperRef = null; // 保存输入框包裹层引用
9
+ let panelPositionResizeObserver = null;
9
10
 
10
11
  // ── 键盘导航状态(§1.5)──
11
12
  // 对标 TIM index.ts:13 "let selectedIndex: number = 0"
@@ -40,7 +41,7 @@ panel.className = 'openagent-agent-panel';
40
41
  panel.innerHTML = `
41
42
  <div class="openagent-panel-head">
42
43
  <span class="openagent-panel-title">
43
- <svg class="openagent-panel-title-icon" width="28" height="28" viewBox="0 0 56 56" fill="none">
44
+ <svg class="openagent-panel-title-icon" width="24" height="24" viewBox="0 0 56 56" fill="none">
44
45
  <path d="M28 25H32C34.2091 25 36 26.7909 36 29V39C36 41.2091 34.2091 43 32 43H14C11.7909 43 10 41.2091 10 39V29C10 26.7909 11.7909 25 14 25H15" stroke="#12CCF6" stroke-width="3.3" stroke-linecap="round"/>
45
46
  <path d="M39 31H42C44.2091 31 46 29.2091 46 27V17C46 14.7909 44.2091 13 42 13H24C21.7909 13 20 14.7909 20 17V27C20 29.2091 21.7909 31 24 31H30.0652C30.236 31 30.3282 31.2004 30.217 31.3302L27.5 34.5" stroke="#12CCF6" stroke-width="3.3" stroke-linecap="round"/>
46
47
  <path d="M27 25.5L32.5 21" stroke="#12CCF6" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
@@ -83,12 +84,71 @@ const width = Math.min(rect.width, window.innerWidth - viewportPadding * 2);
83
84
  const left = Math.max(viewportPadding, Math.min(rect.left, window.innerWidth - width - viewportPadding));
84
85
  panel.style.left = left + 'px';
85
86
  panel.style.width = width + 'px';
86
- panel.style.bottom = (window.innerHeight - rect.top + 8) + 'px';
87
+ const toolbar = _findPanelToolbar();
88
+ const isComposerWrapper = inputWrapperRef.classList
89
+ && inputWrapperRef.classList.contains('agent-chat__composer-combobox');
90
+ if (isComposerWrapper) {
91
+ panel.style.bottom = (_readElementHeight(inputWrapperRef) + _readElementHeight(toolbar) + 35) + 'px';
92
+ } else {
93
+ panel.style.bottom = (window.innerHeight - rect.top + 8) + 'px';
94
+ }
87
95
  }
88
96
 
89
97
  /** resize 时实时跟随输入框 */
90
98
  function _onResize() { _positionPanel(); }
91
99
 
100
+ function _findPanelToolbar() {
101
+ if (!inputWrapperRef) return null;
102
+ var chatInput = inputWrapperRef.closest && inputWrapperRef.closest('.agent-chat__input');
103
+ if (chatInput) {
104
+ var toolbar = chatInput.querySelector('.agent-chat__toolbar');
105
+ if (toolbar) return toolbar;
106
+ }
107
+
108
+ var parentEl = inputWrapperRef.parentElement;
109
+ if (parentEl) {
110
+ for (var i = 0; i < parentEl.children.length; i++) {
111
+ var child = parentEl.children[i];
112
+ if (child !== inputWrapperRef && child.className && String(child.className).indexOf('agent-chat__toolbar') !== -1) {
113
+ return child;
114
+ }
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+
120
+ function _readElementHeight(el) {
121
+ if (!el) return 0;
122
+ var rect = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
123
+ if (rect && rect.height > 0) return rect.height;
124
+ return typeof el.offsetHeight === 'number' ? el.offsetHeight : 0;
125
+ }
126
+
127
+ function _schedulePanelPosition() {
128
+ window.requestAnimationFrame
129
+ ? window.requestAnimationFrame(_positionPanel)
130
+ : setTimeout(_positionPanel, 0);
131
+ }
132
+
133
+ function _installPanelPositionObservers() {
134
+ if (panelPositionResizeObserver) {
135
+ panelPositionResizeObserver.disconnect();
136
+ panelPositionResizeObserver = null;
137
+ }
138
+ if (typeof ResizeObserver !== 'function' || !inputWrapperRef) return;
139
+
140
+ panelPositionResizeObserver = new ResizeObserver(_schedulePanelPosition);
141
+ panelPositionResizeObserver.observe(inputWrapperRef);
142
+ var toolbar = _findPanelToolbar();
143
+ if (toolbar) panelPositionResizeObserver.observe(toolbar);
144
+ }
145
+
146
+ function _teardownPanelPositionObservers() {
147
+ if (!panelPositionResizeObserver) return;
148
+ panelPositionResizeObserver.disconnect();
149
+ panelPositionResizeObserver = null;
150
+ }
151
+
92
152
  // 搜索状态管理
93
153
  let currentAbort = null; // AbortController for in-flight search
94
154
  let searchDebounceId = null;
@@ -115,6 +175,7 @@ if (oldEmpty) oldEmpty.remove();
115
175
 
116
176
  const p = ensurePanel();
117
177
  _positionPanel(); // 动态定位到 input 上方
178
+ _installPanelPositionObservers();
118
179
  p.classList.add('visible');
119
180
  backdrop.classList.add('visible');
120
181
  window.addEventListener('resize', _onResize);
@@ -177,6 +238,7 @@ function closePanel() {
177
238
  if (panel) panel.classList.remove('visible');
178
239
  if (backdrop) backdrop.classList.remove('visible');
179
240
  window.removeEventListener('resize', _onResize);
241
+ _teardownPanelPositionObservers();
180
242
  // 清理搜索状态
181
243
  if (currentAbort) { currentAbort.abort(); currentAbort = null; }
182
244
  if (searchDebounceId) { clearTimeout(searchDebounceId); searchDebounceId = null; }
@@ -231,7 +293,6 @@ function _scrollItemIntoView(index) {
231
293
 
232
294
  function _appendParentSection(container, parentTitle, children, textarea, renderedAgents) {
233
295
  const parentSec = _el('section', 'openagent-parent-section');
234
- parentSec.appendChild(_text('div', 'openagent-parent-heading', parentTitle));
235
296
  children.forEach(function(child) {
236
297
  if (child.agents && child.agents.length > 0) {
237
298
  _appendAgentSection(parentSec, child.title, child.agents, textarea, renderedAgents);
@@ -306,7 +367,12 @@ function _dismissEmptyToast() {
306
367
  }
307
368
 
308
369
  /** 独立浮层空状态(不在面板内) */
309
- function _showEmptyToast(query) {
370
+ function _getEmptyMessage(query) {
371
+ return CL.agentBookLastError ||
372
+ (query ? '暂未匹配到您想要的Agent 我们将持续上新~' : '暂无在线 Agent,请输入关键词搜索');
373
+ }
374
+
375
+ function _showEmptyToast(query, message) {
310
376
  _dismissEmptyToast();
311
377
  var toast = _el('div', 'openagent-empty-toast');
312
378
 
@@ -316,8 +382,7 @@ function _showEmptyToast(query) {
316
382
  '</svg>';
317
383
  toast.appendChild(iconWrap);
318
384
 
319
- toast.appendChild(_text('span', 'openagent-empty-state__text',
320
- query ? '暂未匹配到您想要的Agent 我们将持续上新~' : '暂无在线 Agent,请输入关键词搜索'));
385
+ toast.appendChild(_text('span', 'openagent-empty-state__text', message || _getEmptyMessage(query)));
321
386
 
322
387
  var closeBtn = _el('button', 'openagent-empty-state__close');
323
388
  closeBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none">' +
@@ -333,12 +398,17 @@ function _showEmptyToast(query) {
333
398
  toast.style.left = Math.max(16, rect.left) + 'px';
334
399
  toast.style.width = Math.min(rect.width, window.innerWidth - 32) + 'px';
335
400
  toast.style.bottom = (window.innerHeight - rect.top + 8) + 'px';
401
+ // ── V2 版本兼容:v1 bottom 固定 117px ──
402
+ var isV2Toast = typeof _clIsHostVersionGte === 'function' && _clIsHostVersionGte('2026.6.9');
403
+ if (!isV2Toast) {
404
+ toast.style.bottom = '117px';
405
+ }
336
406
  toast.style.zIndex = '100000';
337
407
  }
338
408
  document.body.appendChild(toast);
339
409
  }
340
410
 
341
- function _createEmptyState(query) {
411
+ function _createEmptyState(query, message) {
342
412
  var wrapper = _el('div', 'openagent-empty-state');
343
413
  var row = _el('div', 'openagent-empty-state__row');
344
414
 
@@ -348,8 +418,7 @@ function _createEmptyState(query) {
348
418
  '</svg>';
349
419
  row.appendChild(iconWrap);
350
420
 
351
- var text = _text('span', 'openagent-empty-state__text',
352
- query ? '暂未匹配到您想要的Agent 我们将持续上新~' : '暂无在线 Agent,请输入关键词搜索');
421
+ var text = _text('span', 'openagent-empty-state__text', message || _getEmptyMessage(query));
353
422
  row.appendChild(text);
354
423
 
355
424
  var closeBtn = _el('button', 'openagent-empty-state__close');
@@ -367,10 +436,11 @@ function _createEmptyState(query) {
367
436
 
368
437
  function renderAgentList(container, agents, textarea, query) {
369
438
  if (!agents.length) {
439
+ var emptyMessage = _getEmptyMessage(query);
370
440
  _currentAgents = [];
371
441
  _currentTextarea = textarea;
372
442
  closePanel();
373
- _showEmptyToast(query);
443
+ _showEmptyToast(query, emptyMessage);
374
444
  return;
375
445
  }
376
446
 
@@ -407,6 +477,19 @@ _highlightItem(0);
407
477
  * 对齐 openagent-frontend NewChatPanel L92-105:
408
478
  * 找到最后一个 @,替换其后内容为 @agentName
409
479
  */
480
+ function _pickMentionAgentDescription(agent) {
481
+ if (!agent || typeof agent !== 'object') return '';
482
+ if (CL && typeof CL.pickAgentIdentityDescription === 'function') {
483
+ return CL.pickAgentIdentityDescription(agent);
484
+ }
485
+ const keys = ['agent_bio', 'agentBio', 'bio', 'description', 'desc', 'agent_description', 'agentDescription', 'MOM', 'mom'];
486
+ for (let i = 0; i < keys.length; i++) {
487
+ const value = agent[keys[i]];
488
+ if (typeof value === 'string' && value.trim()) return value.trim();
489
+ }
490
+ return '';
491
+ }
492
+
410
493
  function insertMention(textarea, agentName, agentData) {
411
494
  // 重新查找 textarea(Lit 可能重新渲染导致引用失效)
412
495
  const liveTextarea = document.querySelector(
@@ -426,9 +509,11 @@ liveTextarea.focus();
426
509
  const agentId = agentData?.agent_id || agentData?.agentId || agentData?.id || agentData?.tag ||
427
510
  agentData?.user_id || agentData?.userId || agentData?.identifier || agentData?.tim_user_id || agentData?.timUserId || '';
428
511
  const agentAvatar = agentData?.avatar || agentData?.avatar_url || agentData?.avatarUrl || agentData?.image || agentData?.logo || agentData?.photo || null;
512
+ const agentBio = _pickMentionAgentDescription(agentData);
429
513
  console.log('[cl-diag][mention-select]', {
430
514
  agentName,
431
515
  resolvedAgentId: agentId || null,
516
+ hasAgentBio: !!agentBio,
432
517
  rawKeys: Object.keys(agentData || {}),
433
518
  rawAgent: agentData,
434
519
  });
@@ -437,9 +522,14 @@ if (!agentId) {
437
522
  console.warn('[openagent] mention skipped: missing agent identifier for', agentName, agentData);
438
523
  return;
439
524
  }
440
- CL.pendingMention = { agent_id: agentId, name: agentName, avatar: agentAvatar };
525
+ CL.pendingMention = { agent_id: agentId, name: agentName, avatar: agentAvatar, agent_bio: agentBio };
441
526
  if (CL && typeof CL.storeAgentIdentity === 'function') {
442
- CL.storeAgentIdentity({ agent_id: agentId, name: agentName, avatar: agentAvatar });
527
+ CL.storeAgentIdentity(Object.assign({}, agentData || {}, {
528
+ agent_id: agentId,
529
+ name: agentName,
530
+ avatar: agentAvatar,
531
+ agent_bio: agentBio,
532
+ }));
443
533
  }
444
534
  console.log('[openagent] mention stored:', CL.pendingMention);
445
535