@wendongfly/myhi 1.3.163 → 1.3.165

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.
package/dist/chat.html CHANGED
@@ -1528,16 +1528,32 @@
1528
1528
  const t = text.trimStart();
1529
1529
  if (t.startsWith('This session is being continued from a previous conversation')) return 'compact';
1530
1530
  if (t.startsWith('[Request interrupted by user')) return 'interrupt'; // 含 "...for tool use" 变体
1531
+ // claude 本地 slash 命令注入的合成 user 事件(1.3.165):命令回显与免责声明——
1532
+ // 发 /context 这类本地命令时 claude 会在流里回 <command-name> 回显 + <local-command-caveat> 说明
1533
+ if (t.startsWith('<command-name>')) return 'command';
1534
+ if (t.startsWith('<local-command-caveat>') || t.startsWith('Caveat:')) return 'caveat';
1535
+ if (t.startsWith('<system-reminder>') || t.startsWith('<local-command-stdout>')) return 'noise';
1531
1536
  return null;
1532
1537
  }
1533
1538
 
1534
1539
  function buildSyntheticUserEl(content) {
1535
1540
  const el = document.createElement('div');
1536
- if (syntheticUserKind(content) === 'interrupt') {
1541
+ const kind = syntheticUserKind(content);
1542
+ if (kind === 'interrupt') {
1537
1543
  el.className = 'msg msg-status';
1538
1544
  el.textContent = '— ⏹ 请求已中断 —';
1539
1545
  return el;
1540
1546
  }
1547
+ if (kind === 'command') {
1548
+ // 本地命令回显:状态行显示执行了什么命令(用户自己发的命令已有气泡,这是 claude 的回显)
1549
+ el.className = 'msg msg-status';
1550
+ const name = (content.match(/<command-name>([^<]*)<\/command-name>/) || [])[1] || '命令';
1551
+ el.textContent = `— ⌘ ${name} —`;
1552
+ return el;
1553
+ }
1554
+ if (kind === 'caveat' || kind === 'noise') {
1555
+ return null; // 给模型看的免责声明/系统提醒/命令输出回显,对用户是纯噪音,不渲染
1556
+ }
1541
1557
  el.className = 'msg msg-synthetic';
1542
1558
  const head = document.createElement('div');
1543
1559
  head.className = 'syn-head';
@@ -2248,9 +2264,27 @@
2248
2264
  break;
2249
2265
  // 服务端主动发起的消息(自动接力发出的草稿)—— 渲染成用户气泡,
2250
2266
  // 跟你自己按回车发出来的长得一样,能看见原文而不只是一行系统提示。
2251
- case 'user':
2252
- if (msg.content) { endStream(); addInputMessage(msg.content); }
2267
+ // claude 注入的合成 user 事件(压缩摘要/中断/本地命令回显与免责声明)不是用户发言:
2268
+ // 新 CLI 下这类事件带顶层 content,必须分类分流,否则长成蓝色用户气泡(1.3.165)。
2269
+ case 'user': {
2270
+ // 只有字符串 content 才可能是真人/草稿发言;claude 原生 user 事件带数组 content
2271
+ // (tool_result 块)不是用户说话,别渲染成气泡
2272
+ if (msg.content && typeof msg.content === 'string') {
2273
+ endStream();
2274
+ const kind = syntheticUserKind(msg.content);
2275
+ if (kind === 'compact' || kind === 'interrupt') {
2276
+ const sel = buildSyntheticUserEl(msg.content);
2277
+ if (sel) { chatArea.appendChild(sel); trimMessages(); scrollToBottom(); }
2278
+ } else if (kind === 'command') {
2279
+ const sel = buildSyntheticUserEl(msg.content);
2280
+ if (sel) addStatusMessage(sel.textContent);
2281
+ } else if (!kind) {
2282
+ addInputMessage(msg.content);
2283
+ }
2284
+ // caveat/noise:纯噪音,不渲染
2285
+ }
2253
2286
  break;
2287
+ }
2254
2288
  case 'assistant':
2255
2289
  removeThinking();
2256
2290
  if (msg.message?.usage) {
@@ -2303,11 +2337,15 @@
2303
2337
  // 关键:idle 放最后——渲染最终答复的 addAssistantMessage 会把状态置回 'working',
2304
2338
  // 若在其之前置 idle 会被顶回「执行中」+ 取消按钮不复位。渲染全部完成后再回 idle。
2305
2339
  setWorkState('idle');
2306
- // 收尾水位提示:水位只认 assistant 消息的 usage(单次 API 调用 = 当前真实上下文)。
2307
- // ⚠ result 的 usage 是「claude 进程启动以来所有调用的累计值」(1.3.162 实测确认),
2308
- // 拿它更新水位会把会话越跑越「虚高」——长会话轮末突然报 231% 而实际 21% 就是它。
2309
- // assistant 流中已实时更新 chip,这里只按已有水位补一次阈值提示(80/90 档各一次)。
2310
- if (_lastCtxTokens > 0) ctxMaybeNudge(_lastCtxTokens);
2340
+ // 收尾水位提示:server 在 result 上附带了刚从 transcript 反查的精确水位
2341
+ // (ctxTokens——claude 2.1.292 起 stream 的 assistant usage 被清零,前端自己算不了)。
2342
+ // 旧 server 无此字段时退回 _lastCtxTokens(老 CLI 的 assistant 流仍带真实 usage)。
2343
+ if (msg.ctxTokens > 0) {
2344
+ ctxUpdate(msg.ctxTokens);
2345
+ ctxMaybeNudge(msg.ctxTokens);
2346
+ } else if (_lastCtxTokens > 0) {
2347
+ ctxMaybeNudge(_lastCtxTokens);
2348
+ }
2311
2349
  break;
2312
2350
  case 'content_block_start':
2313
2351
  // 流式内容块开始:thinking 时创建占位,由 thinking_delta 增量填充;
@@ -2398,11 +2436,12 @@
2398
2436
  endStream(); // 每条历史消息都是独立的,不要流式合并
2399
2437
  if (subaRoute(msg)) continue; // 子代理内部历史消息归入对应面板
2400
2438
  if (msg.type === 'user' && msg.content) {
2401
- // claude 注入的合成消息(压缩续接摘要/中断标记)渲染成系统面板,不长成用户发言
2402
- if (syntheticUserKind(msg.content)) {
2403
- chatArea.appendChild(buildSyntheticUserEl(msg.content));
2439
+ // claude 注入的合成消息(压缩摘要/中断/命令回显/免责声明)渲染成系统状态行或不渲染,不长成用户发言
2440
+ const sel = buildSyntheticUserEl(msg.content);
2441
+ if (sel) {
2442
+ chatArea.appendChild(sel);
2404
2443
  trimMessages(); scrollToBottom();
2405
- } else {
2444
+ } else if (!syntheticUserKind(msg.content)) {
2406
2445
  addInputMessage(msg.content);
2407
2446
  }
2408
2447
  } else if (msg.type === 'assistant' && msg.message?.content) {
@@ -2486,9 +2525,10 @@
2486
2525
  const frag = document.createDocumentFragment();
2487
2526
  for (const msg of d.messages) {
2488
2527
  if (msg.type === 'user' && msg.content) {
2489
- if (syntheticUserKind(msg.content)) {
2490
- frag.appendChild(buildSyntheticUserEl(msg.content));
2491
- } else {
2528
+ const sel = buildSyntheticUserEl(msg.content);
2529
+ if (sel) {
2530
+ frag.appendChild(sel);
2531
+ } else if (!syntheticUserKind(msg.content)) {
2492
2532
  const el = document.createElement('div');
2493
2533
  el.className = 'msg msg-input';
2494
2534
  el.innerHTML = `<div><div class="bubble">${escHtml(msg.content)}</div><div class="meta">${msg.timestamp ? new Date(msg.timestamp).toLocaleString('zh-CN') : ''}</div></div>`;