@bolloon/bolloon-agent 0.3.16 → 0.3.18

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.
@@ -0,0 +1,222 @@
1
+ /**
2
+ * lsp/lsp-tools.ts — 注册 LSP 工具到 Bolloon agent 工具系统
3
+ *
4
+ * 暴露 6 个工具:
5
+ * - lsp_detect: 检测本机已安装的语言服务器
6
+ * - lsp_hover: 悬停查看类型/文档
7
+ * - lsp_completion: 代码补全
8
+ * - lsp_diagnostics: 诊断当前文件
9
+ * - lsp_go_to_definition: 跳转到定义
10
+ * - lsp_start / lsp_stop: 手动管理 LSP 服务器生命周期
11
+ */
12
+ import { detectInstalledLspServers, startLspServer, stopLspServer, stopAllLspServers, findLspForFile, lspDidOpen, lspHover, lspCompletion, lspDiagnostics, lspGoToDefinition, } from './lsp-manager.js';
13
+ /** 已检测到的 LSP 服务器列表 (缓存) */
14
+ let cachedSpecs = null;
15
+ async function getSpecs() {
16
+ if (!cachedSpecs)
17
+ cachedSpecs = await detectInstalledLspServers();
18
+ return cachedSpecs;
19
+ }
20
+ /**
21
+ * 注册 6 个 LSP 工具到 Bolloon agent 的工具系统.
22
+ * 在 registerBuiltinTools 内部调用.
23
+ */
24
+ export function registerLspTools(ctx) {
25
+ ctx.tools.set('lsp_detect', {
26
+ name: 'lsp_detect',
27
+ description: '检测本机已安装的 LSP (Language Server Protocol) 服务器, 返回可用的语言列表',
28
+ parameters: {},
29
+ execute: async (_args) => {
30
+ try {
31
+ const specs = await getSpecs();
32
+ if (specs.length === 0) {
33
+ return { success: true, output: '⚠️ 未检测到任何 LSP 服务器。可安装:\n npm install -g typescript-language-server\n cargo install rust-analyzer' };
34
+ }
35
+ const lines = specs.map(s => ` ✅ ${s.displayName} (${s.language}): ${s.fileExtensions.join(', ')}`);
36
+ return { success: true, output: `已检测到 ${specs.length} 个 LSP 服务器:\n${lines.join('\n')}` };
37
+ }
38
+ catch (e) {
39
+ return { success: false, error: String(e) };
40
+ }
41
+ },
42
+ });
43
+ ctx.tools.set('lsp_start', {
44
+ name: 'lsp_start',
45
+ description: '启动指定语言的语言服务器 (如 typescript / rust / python)',
46
+ parameters: { language: '语言 id, 如 typescript / rust / python (必填)' },
47
+ execute: async (args) => {
48
+ try {
49
+ const language = String(args.language || '').trim().toLowerCase();
50
+ if (!language)
51
+ return { success: false, error: 'language 必填' };
52
+ const inst = await startLspServer(language);
53
+ if (!inst)
54
+ return { success: false, error: `未找到 ${language} 的 LSP 服务器, 请先安装` };
55
+ return { success: true, output: `✅ ${inst.spec.displayName} 已启动` };
56
+ }
57
+ catch (e) {
58
+ return { success: false, error: String(e) };
59
+ }
60
+ },
61
+ });
62
+ ctx.tools.set('lsp_hover', {
63
+ name: 'lsp_hover',
64
+ description: '在文件某位置悬停, 查看类型/文档 (需要先 lsp_start)',
65
+ parameters: {
66
+ file: '文件路径 (必填)',
67
+ line: '行号 (从 0 开始)',
68
+ character: '列号 (从 0 开始)',
69
+ content: '文件内容 (可选, 首次需要以触发 didOpen)',
70
+ },
71
+ execute: async (args) => {
72
+ try {
73
+ const file = String(args.file || '').trim();
74
+ if (!file)
75
+ return { success: false, error: 'file 必填' };
76
+ const line = parseInt(String(args.line || '0'), 10);
77
+ const character = parseInt(String(args.character || '0'), 10);
78
+ const content = args.content ? String(args.content) : undefined;
79
+ const specs = await getSpecs();
80
+ const spec = findLspForFile(file, specs);
81
+ if (!spec)
82
+ return { success: false, error: `未找到 ${file} 对应的 LSP 服务器` };
83
+ const inst = await startLspServer(spec.language);
84
+ if (!inst)
85
+ return { success: false, error: `LSP ${spec.language} 启动失败` };
86
+ if (content)
87
+ lspDidOpen(inst, file, content);
88
+ const result = await lspHover(inst, file, line, character);
89
+ if (!result)
90
+ return { success: true, output: '(无悬停信息)' };
91
+ return { success: true, output: result.contents };
92
+ }
93
+ catch (e) {
94
+ return { success: false, error: String(e) };
95
+ }
96
+ },
97
+ });
98
+ ctx.tools.set('lsp_completion', {
99
+ name: 'lsp_completion',
100
+ description: '在文件某位置获取代码补全建议 (需要先 lsp_start)',
101
+ parameters: {
102
+ file: '文件路径 (必填)',
103
+ line: '行号 (从 0 开始)',
104
+ character: '列号 (从 0 开始)',
105
+ content: '文件内容 (可选)',
106
+ },
107
+ execute: async (args) => {
108
+ try {
109
+ const file = String(args.file || '').trim();
110
+ if (!file)
111
+ return { success: false, error: 'file 必填' };
112
+ const line = parseInt(String(args.line || '0'), 10);
113
+ const character = parseInt(String(args.character || '0'), 10);
114
+ const content = args.content ? String(args.content) : undefined;
115
+ const specs = await getSpecs();
116
+ const spec = findLspForFile(file, specs);
117
+ if (!spec)
118
+ return { success: false, error: `未找到 ${file} 对应的 LSP 服务器` };
119
+ const inst = await startLspServer(spec.language);
120
+ if (!inst)
121
+ return { success: false, error: `LSP ${spec.language} 启动失败` };
122
+ if (content)
123
+ lspDidOpen(inst, file, content);
124
+ const result = await lspCompletion(inst, file, line, character);
125
+ if (result.items.length === 0)
126
+ return { success: true, output: '(无补全建议)' };
127
+ const items = result.items.slice(0, 20).map(i => ` ${i.label}${i.detail ? ` — ${i.detail}` : ''}`);
128
+ return { success: true, output: `补全建议 (显示前 20 条):\n${items.join('\n')}` };
129
+ }
130
+ catch (e) {
131
+ return { success: false, error: String(e) };
132
+ }
133
+ },
134
+ });
135
+ ctx.tools.set('lsp_diagnostics', {
136
+ name: 'lsp_diagnostics',
137
+ description: '获取文件的诊断结果 (错误/警告)',
138
+ parameters: { file: '文件路径 (必填)', content: '文件内容 (可选)' },
139
+ execute: async (args) => {
140
+ try {
141
+ const file = String(args.file || '').trim();
142
+ if (!file)
143
+ return { success: false, error: 'file 必填' };
144
+ const content = args.content ? String(args.content) : undefined;
145
+ const specs = await getSpecs();
146
+ const spec = findLspForFile(file, specs);
147
+ if (!spec)
148
+ return { success: false, error: `未找到 ${file} 对应的 LSP 服务器` };
149
+ const inst = await startLspServer(spec.language);
150
+ if (!inst)
151
+ return { success: false, error: `LSP ${spec.language} 启动失败` };
152
+ if (content)
153
+ lspDidOpen(inst, file, content);
154
+ const result = await lspDiagnostics(inst, file);
155
+ if (result.diagnostics.length === 0)
156
+ return { success: true, output: '✅ 无诊断问题' };
157
+ const lines = result.diagnostics.map(d => {
158
+ const sev = ['', '错误', '警告', '信息', '提示'][d.severity || 0] || '未知';
159
+ const pos = `L${d.range.start.line}:${d.range.start.character}`;
160
+ return ` ${sev === '错误' ? '❌' : sev === '警告' ? '⚠️' : 'ℹ️'} [${pos}] ${sev}: ${d.message}`;
161
+ });
162
+ return { success: true, output: `诊断结果 (${result.diagnostics.length} 条):\n${lines.join('\n')}` };
163
+ }
164
+ catch (e) {
165
+ return { success: false, error: String(e) };
166
+ }
167
+ },
168
+ });
169
+ ctx.tools.set('lsp_go_to_definition', {
170
+ name: 'lsp_go_to_definition',
171
+ description: '跳转到符号定义的位置',
172
+ parameters: {
173
+ file: '当前文件路径 (必填)',
174
+ line: '行号 (从 0 开始)',
175
+ character: '列号 (从 0 开始)',
176
+ },
177
+ execute: async (args) => {
178
+ try {
179
+ const file = String(args.file || '').trim();
180
+ if (!file)
181
+ return { success: false, error: 'file 必填' };
182
+ const line = parseInt(String(args.line || '0'), 10);
183
+ const character = parseInt(String(args.character || '0'), 10);
184
+ const specs = await getSpecs();
185
+ const spec = findLspForFile(file, specs);
186
+ if (!spec)
187
+ return { success: false, error: `未找到 ${file} 对应的 LSP 服务器` };
188
+ const inst = await startLspServer(spec.language);
189
+ if (!inst)
190
+ return { success: false, error: `LSP ${spec.language} 启动失败` };
191
+ const loc = await lspGoToDefinition(inst, file, line, character);
192
+ if (!loc)
193
+ return { success: true, output: '(未找到定义位置)' };
194
+ return { success: true, output: `定义位置: ${loc.uri} (L${loc.range.start.line}:${loc.range.start.character})` };
195
+ }
196
+ catch (e) {
197
+ return { success: false, error: String(e) };
198
+ }
199
+ },
200
+ });
201
+ ctx.tools.set('lsp_stop', {
202
+ name: 'lsp_stop',
203
+ description: '关闭指定语言的语言服务器 (或 all 关闭所有)',
204
+ parameters: { language: '语言 id, 如 typescript, 或 all 关闭全部' },
205
+ execute: async (args) => {
206
+ try {
207
+ const language = String(args.language || '').trim().toLowerCase();
208
+ if (!language)
209
+ return { success: false, error: 'language 必填 (或 "all")' };
210
+ if (language === 'all') {
211
+ stopAllLspServers();
212
+ return { success: true, output: '✅ 所有 LSP 服务器已关闭' };
213
+ }
214
+ await stopLspServer(language);
215
+ return { success: true, output: `✅ ${language} LSP 服务器已关闭` };
216
+ }
217
+ catch (e) {
218
+ return { success: false, error: String(e) };
219
+ }
220
+ },
221
+ });
222
+ }
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * npm 自动更新检查器
4
3
  *
@@ -14,48 +13,11 @@
14
13
  * 开发态下若 cwd 的 package.json 就是本包则回退到 cwd,不受任意工作目录影响。
15
14
  * - 检查频率受节流缓存约束(默认 24h 一次),不会每次启动都打 npm。
16
15
  */
17
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- var desc = Object.getOwnPropertyDescriptor(m, k);
20
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
- desc = { enumerable: true, get: function() { return m[k]; } };
22
- }
23
- Object.defineProperty(o, k2, desc);
24
- }) : (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- o[k2] = m[k];
27
- }));
28
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
- Object.defineProperty(o, "default", { enumerable: true, value: v });
30
- }) : function(o, v) {
31
- o["default"] = v;
32
- });
33
- var __importStar = (this && this.__importStar) || (function () {
34
- var ownKeys = function(o) {
35
- ownKeys = Object.getOwnPropertyNames || function (o) {
36
- var ar = [];
37
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
- return ar;
39
- };
40
- return ownKeys(o);
41
- };
42
- return function (mod) {
43
- if (mod && mod.__esModule) return mod;
44
- var result = {};
45
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
- __setModuleDefault(result, mod);
47
- return result;
48
- };
49
- })();
50
- Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.checkAndUpdate = checkAndUpdate;
52
- exports.checkForUpdates = checkForUpdates;
53
- exports.performUpdate = performUpdate;
54
- const child_process_1 = require("child_process");
55
- const fs = __importStar(require("fs"));
56
- const path = __importStar(require("path"));
57
- const https = __importStar(require("https"));
58
- const http = __importStar(require("http"));
16
+ import { execSync } from 'child_process';
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import * as https from 'https';
20
+ import * as http from 'http';
59
21
  // ANSI 颜色
60
22
  const RESET = '\x1b[0m';
61
23
  const BOLD = '\x1b[1m';
@@ -113,7 +75,7 @@ function getGlobalBolloonDir() {
113
75
  const candidates = [];
114
76
  // 1. npm root -g(最可靠)
115
77
  try {
116
- const npmRoot = (0, child_process_1.execSync)('npm root -g', { encoding: 'utf-8', timeout: 8000 }).trim();
78
+ const npmRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 8000 }).trim();
117
79
  if (npmRoot)
118
80
  candidates.push(path.join(npmRoot, '@bolloon', 'bolloon-agent'));
119
81
  }
@@ -122,7 +84,7 @@ function getGlobalBolloonDir() {
122
84
  }
123
85
  // 2. npm prefix -g
124
86
  try {
125
- const npmPrefix = (0, child_process_1.execSync)('npm prefix -g', { encoding: 'utf-8', timeout: 8000 }).trim();
87
+ const npmPrefix = execSync('npm prefix -g', { encoding: 'utf-8', timeout: 8000 }).trim();
126
88
  if (npmPrefix)
127
89
  candidates.push(path.join(npmPrefix, 'lib', 'node_modules', '@bolloon', 'bolloon-agent'));
128
90
  }
@@ -280,7 +242,7 @@ async function checkBolloonUpdates() {
280
242
  */
281
243
  function checkNpmOutdated() {
282
244
  try {
283
- const output = (0, child_process_1.execSync)('npm outdated --json', {
245
+ const output = execSync('npm outdated --json', {
284
246
  encoding: 'utf-8',
285
247
  timeout: 30000,
286
248
  maxBuffer: 10 * 1024 * 1024,
@@ -344,7 +306,7 @@ async function updatePackagesWithVersion(packagesWithVersion) {
344
306
  const args = ['npm', 'install', '-g', ...packagesWithVersion];
345
307
  notify(`\n${CYAN}📦 正在更新包...${RESET}\n`, RESET);
346
308
  try {
347
- (0, child_process_1.execSync)(args.join(' '), {
309
+ execSync(args.join(' '), {
348
310
  encoding: 'utf-8',
349
311
  timeout: 300000,
350
312
  stdio: 'inherit',
@@ -462,7 +424,7 @@ function resolveAutoUpdatePolicy() {
462
424
  * 例如 Electron 用 app.relaunch(),Node 用 detached 重新 spawn)。
463
425
  * 若未提供或 autoRestart=false,则仅提示用户手动重启。
464
426
  */
465
- async function checkAndUpdate(opts = {}) {
427
+ export async function checkAndUpdate(opts = {}) {
466
428
  const policy = resolveAutoUpdatePolicy();
467
429
  if (policy.blocked) {
468
430
  return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(已显式禁用)' };
@@ -566,13 +528,13 @@ async function checkAndUpdate(opts = {}) {
566
528
  /**
567
529
  * 仅检查更新,不自动安装
568
530
  */
569
- async function checkForUpdates() {
531
+ export async function checkForUpdates() {
570
532
  return await checkBolloonUpdates();
571
533
  }
572
534
  /**
573
535
  * 手动触发更新
574
536
  */
575
- async function performUpdate(packages) {
537
+ export async function performUpdate(packages) {
576
538
  return await updatePackages(packages);
577
539
  }
578
540
  // CLI 入口
@@ -595,4 +557,3 @@ if (process.argv[1]?.includes('auto-update')) {
595
557
  }
596
558
  })();
597
559
  }
598
- //# sourceMappingURL=auto-update.js.map
@@ -3383,12 +3383,28 @@ ${data.error || "channel not found"}`, "error");
3383
3383
  console.error("Failed to check API config:", err);
3384
3384
  }
3385
3385
  }
3386
+ async function loadUserIdentity() {
3387
+ try {
3388
+ const res = await fetch("/api/user/identity");
3389
+ if (!res.ok) return;
3390
+ const identity = await res.json();
3391
+ const letter = identity.name ? identity.name.charAt(0).toUpperCase() : "?";
3392
+ const nameEl = document.getElementById("user-name");
3393
+ const didEl = document.getElementById("user-did");
3394
+ const letterEl = document.getElementById("avatar-letter");
3395
+ if (nameEl) nameEl.textContent = identity.name || "\u533F\u540D";
3396
+ if (didEl) didEl.textContent = identity.didShort ? `did:key:${identity.didShort}` : "";
3397
+ if (letterEl) letterEl.textContent = letter;
3398
+ } catch (e) {
3399
+ }
3400
+ }
3386
3401
  async function init() {
3387
3402
  const themeData = await loadTheme();
3388
3403
  currentAgentId = themeData.agentId || `agent_${generateId().substring(0, 8)}`;
3389
3404
  if (!themeData.agentId) {
3390
3405
  await saveTheme(themeData.theme, currentAgentId);
3391
3406
  }
3407
+ loadUserIdentity();
3392
3408
  try {
3393
3409
  const res = await fetch("/api/tools");
3394
3410
  if (res.ok) {
@@ -78,6 +78,13 @@
78
78
  </div>
79
79
 
80
80
  <div class="sidebar-footer">
81
+ <div class="user-avatar" id="user-avatar" title="点击查看 DID 身份">
82
+ <span class="avatar-letter" id="avatar-letter">?</span>
83
+ </div>
84
+ <div class="user-info">
85
+ <span class="user-name" id="user-name">加载中...</span>
86
+ <span class="user-did" id="user-did"></span>
87
+ </div>
81
88
  <div class="agent-status">
82
89
  <span class="status-dot"></span>
83
90
  <span class="status-text">已连接</span>
@@ -2082,9 +2082,7 @@ ${goalDesc}
2082
2082
  // v3 修复: 用 setInterval 替代一次性 setTimeout, 确保分享变更后能持续推送给 peer
2083
2083
  setInterval(v3BroadcastOwn, 30000);
2084
2084
  // 保留 @diap/sdk 的旧实例 (它的 Hyperswarm 实例能帮 P2PDirect 做 DHT bootstrap)
2085
- // 2026-07-04: hyperswarm 4.x 删除了 Discovery.update() (被 .flushed() 替代).
2086
- // @diap/sdk 0.1.10 还调 .update(), 是上游 bug (已记录 docs/plans/2026-06-17-supervisor-iter-1.md).
2087
- // 这里静默 joinTopic 失败, P2PDirect (v3 主路径) 不依赖 @diap/sdk, 不影响功能.
2085
+ // 2026-07-04: @diap/sdk v0.2.0 已修复 seed/*update*/connect 类型, 此处走正常路径.
2088
2086
  try {
2089
2087
  const rawSeed = crypto.getRandomValues(new Uint8Array(32));
2090
2088
  p2pCommunicator = createHyperswarmCommunicator({
@@ -2113,18 +2111,17 @@ ${goalDesc}
2113
2111
  });
2114
2112
  await p2pCommunicator.start();
2115
2113
  // @diap/sdk 也 join topic — 它的 Hyperswarm 实例帮 P2PDirect 做 DHT 引导
2116
- // joinTopic 失败已知 (hyperswarm 4.x + @diap/sdk 0.1.10), catch 静默
2114
+ // v0.2.1 (2026-07-28): hyperswarm seed 类型固定 + join() 返回 { refresh, flushed, destroy }
2115
+ // discovery.update 错误已彻底修复. catch 保留做防御性兜底.
2117
2116
  const oldTopic = createTopic('bolloon-agent-harness');
2118
2117
  try {
2119
2118
  await p2pCommunicator.joinTopic(oldTopic);
2120
2119
  console.log(`P2P 老通道已就绪 (DHT bootstrap 帮 P2PDirect, 实际数据走 P2PDirect)`);
2121
2120
  }
2122
2121
  catch (joinErr) {
2123
- // 已知: @diap/sdk 0.1.10 + hyperswarm 4.x → discovery.update is not a function
2124
- // v3 P2PDirect 是主路径, 此处不阻断
2125
2122
  const msg = String(joinErr?.message || joinErr);
2126
2123
  if (msg.includes('discovery.update') || msg.includes('is not a function')) {
2127
- console.warn(`[v3-legacy] @diap/sdk joinTopic 失败 (已知 hyperswarm 4.x 兼容问题, 已忽略): ${msg}`);
2124
+ console.warn(`[v3-legacy] joinTopic 触发旧版兼容警告: ${msg}`);
2128
2125
  }
2129
2126
  else {
2130
2127
  throw joinErr;
@@ -2223,6 +2220,50 @@ ${goalDesc}
2223
2220
  app.get('/api/tools', (_req, res) => {
2224
2221
  res.json(listTools().map(t => t.id));
2225
2222
  });
2223
+ // 2026-07-28: 用户 DID 身份端点 — 静默生成/加载, 持久化到 ~/.bolloon/identity/user.json
2224
+ let userIdentityCache = null;
2225
+ const IDENTITY_DIR = `${process.env.HOME || '/tmp'}/.bolloon/identity`;
2226
+ async function loadOrCreateUserIdentity() {
2227
+ if (userIdentityCache)
2228
+ return userIdentityCache;
2229
+ try {
2230
+ const { readFileSync, existsSync, mkdirSync, writeFileSync } = await import('fs');
2231
+ const file = `${IDENTITY_DIR}/user.json`;
2232
+ if (existsSync(file)) {
2233
+ const raw = readFileSync(file, 'utf-8');
2234
+ const parsed = JSON.parse(raw);
2235
+ if (parsed.did && parsed.publicKeyHex) {
2236
+ userIdentityCache = parsed;
2237
+ return parsed;
2238
+ }
2239
+ }
2240
+ // 生成新 DID keypair
2241
+ const kp = KeyManager.generate();
2242
+ const didShort = kp.did.split(':').pop()?.substring(0, 8) || 'unknown';
2243
+ const publicKeyHex = Buffer.from(kp.publicKey).toString('hex');
2244
+ const username = getUserName();
2245
+ const identity = {
2246
+ did: kp.did,
2247
+ didShort,
2248
+ publicKeyHex,
2249
+ name: `blln-${username}`,
2250
+ createdAt: new Date().toISOString(),
2251
+ };
2252
+ mkdirSync(IDENTITY_DIR, { recursive: true });
2253
+ writeFileSync(file, JSON.stringify(identity, null, 2), { mode: 0o600 });
2254
+ userIdentityCache = identity;
2255
+ console.log(`[user-identity] ✅ DID: ${kp.did.substring(0, 30)}...`);
2256
+ return identity;
2257
+ }
2258
+ catch (e) {
2259
+ console.warn('[user-identity] 加载失败:', e.message);
2260
+ return { did: '', didShort: 'anon', publicKeyHex: '', name: getUserName() };
2261
+ }
2262
+ }
2263
+ app.get('/api/user/identity', async (_req, res) => {
2264
+ const identity = await loadOrCreateUserIdentity();
2265
+ res.json(identity);
2266
+ });
2226
2267
  // 2026-07-01 (v0.2.6): 前后端分离核心 — 后端切 LLM 输出为结构化 segments
2227
2268
  // - POST /api/segment-reply { reply, knownTools }
2228
2269
  // - 返回 ChatSegment[] (think / text / env_details / tool_call / final)
@@ -822,16 +822,71 @@ body {
822
822
 
823
823
  /* Sidebar Footer */
824
824
  .sidebar-footer {
825
- padding: 16px;
825
+ padding: 12px 16px;
826
826
  border-top: 1px solid var(--border);
827
+ display: flex;
828
+ align-items: center;
829
+ gap: 10px;
827
830
  }
828
831
 
829
- .agent-status {
832
+ /* 用户头像 — DID 身份 */
833
+ .user-avatar {
834
+ width: 32px;
835
+ height: 32px;
836
+ border-radius: 50%;
837
+ background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
830
838
  display: flex;
831
839
  align-items: center;
832
- gap: 8px;
840
+ justify-content: center;
841
+ flex-shrink: 0;
842
+ cursor: pointer;
843
+ transition: transform 0.2s, box-shadow 0.2s;
844
+ }
845
+ .user-avatar:hover {
846
+ transform: scale(1.1);
847
+ box-shadow: 0 0 12px var(--accent-glow);
848
+ }
849
+ .avatar-letter {
850
+ font-size: 14px;
851
+ font-weight: 700;
852
+ color: #1a1a18;
853
+ text-transform: uppercase;
854
+ user-select: none;
855
+ }
856
+
857
+ .user-info {
858
+ flex: 1;
859
+ min-width: 0;
860
+ display: flex;
861
+ flex-direction: column;
862
+ gap: 2px;
863
+ }
864
+ .user-name {
833
865
  font-size: 12px;
866
+ font-weight: 600;
867
+ color: var(--text);
868
+ white-space: nowrap;
869
+ overflow: hidden;
870
+ text-overflow: ellipsis;
871
+ line-height: 1.2;
872
+ }
873
+ .user-did {
874
+ font-size: 10px;
875
+ font-family: var(--font-mono);
876
+ color: var(--text-muted);
877
+ white-space: nowrap;
878
+ overflow: hidden;
879
+ text-overflow: ellipsis;
880
+ line-height: 1.2;
881
+ }
882
+
883
+ .agent-status {
884
+ display: flex;
885
+ align-items: center;
886
+ gap: 6px;
887
+ font-size: 11px;
834
888
  color: var(--text-secondary);
889
+ flex-shrink: 0;
835
890
  }
836
891
 
837
892
  .status-dot {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "type": "module",
5
- "description": "P2P AI Document Agent - \u5168\u5c40\u5b89\u88c5\u540e\u6267\u884c `bolloon` \u542f\u52a8\u4ea7\u54c1",
5
+ "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",
7
7
  "author": "Bolloon Leo",
8
8
  "repository": {
@@ -48,7 +48,7 @@
48
48
  "src/constraint-runtime"
49
49
  ],
50
50
  "dependencies": {
51
- "@bolloon/bolloon-agent": "^0.3.13",
51
+ "@bolloon/bolloon-agent": "^0.3.16",
52
52
  "@bolloon/constraint-runtime": "0.1.0",
53
53
  "@capacitor/core": "^8.4.1",
54
54
  "@capacitor/ios": "^8.4.1",