wezard 2.0.0 → 2.0.2

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 (59) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CLAUDE.md +7 -8
  4. package/README.md +20 -32
  5. package/cli/wezard.sh +0 -62
  6. package/config.example.jsonc +3 -9
  7. package/dist/cli/init.js +12 -21
  8. package/dist/cli/init.js.map +1 -1
  9. package/dist/daemon/approval.js +2 -4
  10. package/dist/daemon/approval.js.map +1 -1
  11. package/dist/daemon/cc-session.js +136 -0
  12. package/dist/daemon/cc-session.js.map +1 -0
  13. package/dist/daemon/detail.js +2 -3
  14. package/dist/daemon/detail.js.map +1 -1
  15. package/dist/daemon/inbound.js +90 -110
  16. package/dist/daemon/inbound.js.map +1 -1
  17. package/dist/daemon/index.js +130 -111
  18. package/dist/daemon/index.js.map +1 -1
  19. package/dist/daemon/memory-steward.js +82 -0
  20. package/dist/daemon/memory-steward.js.map +1 -0
  21. package/dist/daemon/mirror-bridge.js +201 -62
  22. package/dist/daemon/mirror-bridge.js.map +1 -1
  23. package/dist/daemon/session-name.js +4 -27
  24. package/dist/daemon/session-name.js.map +1 -1
  25. package/dist/daemon/spawn-tmux.js +35 -13
  26. package/dist/daemon/spawn-tmux.js.map +1 -1
  27. package/dist/daemon/wizard-memory.js +21 -26
  28. package/dist/daemon/wizard-memory.js.map +1 -1
  29. package/dist/daemon/wizard.js +54 -11
  30. package/dist/daemon/wizard.js.map +1 -1
  31. package/dist/mcp/server.js +63 -153
  32. package/dist/mcp/server.js.map +1 -1
  33. package/dist/shared/chat-http.js +44 -16
  34. package/dist/shared/chat-http.js.map +1 -1
  35. package/dist/shared/chat-render.js +10 -8
  36. package/dist/shared/chat-render.js.map +1 -1
  37. package/dist/shared/cli-backends.js +2 -7
  38. package/dist/shared/cli-backends.js.map +1 -1
  39. package/dist/shared/config.js +10 -12
  40. package/dist/shared/config.js.map +1 -1
  41. package/dist/shared/detail-render.js +35 -16
  42. package/dist/shared/detail-render.js.map +1 -1
  43. package/dist/shared/detail-store.js +14 -3
  44. package/dist/shared/detail-store.js.map +1 -1
  45. package/dist/shared/role-render.js +4 -1
  46. package/dist/shared/role-render.js.map +1 -1
  47. package/dist/shared/role-view.js +49 -11
  48. package/dist/shared/role-view.js.map +1 -1
  49. package/dist/shared/world.js +11 -1
  50. package/dist/shared/world.js.map +1 -1
  51. package/hooks/pre-tool-use.sh +4 -4
  52. package/package.json +1 -2
  53. package/web/chat.css +103 -58
  54. package/web/chat.js +153 -82
  55. package/commands/wrc.md +0 -7
  56. package/dist/daemon/cc-bridge.js +0 -183
  57. package/dist/daemon/cc-bridge.js.map +0 -1
  58. package/dist/daemon/sessions.js +0 -4
  59. package/dist/daemon/sessions.js.map +0 -1
@@ -190,7 +190,7 @@ if [[ "$TOOL_NAME" == "ExitPlanMode" ]]; then
190
190
  emit "allow" "ExitPlanMode passthrough"
191
191
  fi
192
192
 
193
- # wezard 自家 MCP 工具全部走 loopback 到本 daemon, 自审会把 /wrc 首次绑定卡死
193
+ # wezard 自家 MCP 工具全部走 loopback 到本 daemon, 自审会把首次绑定卡死
194
194
  # (还没 defaultChat, 卡片无处可推 → 鸡生蛋), 直接放行。
195
195
  # 命名两条路径:
196
196
  # 1. cli/sync.ts (legacy claude-internal): mcp__wezard__<tool>
@@ -200,8 +200,8 @@ if [[ "$TOOL_NAME" == mcp__*wezard__* ]]; then
200
200
  emit "allow" "wezard mcp self-call bypass"
201
201
  fi
202
202
 
203
- # wezard 自家 Skill (slash commands like /wrc) 也是本地插件代码, 无须审批。
204
- # tool_input.skill 形如 "wezard:wrc"; plugin 命名空间用冒号分隔。
203
+ # wezard 自家 Skill (slash commands like /wezard:audit) 也是本地插件代码, 无须审批。
204
+ # tool_input.skill 形如 "wezard:audit"; plugin 命名空间用冒号分隔。
205
205
  if [[ "$TOOL_NAME" == "Skill" ]]; then
206
206
  SKILL_NAME=$(printf '%s' "$TOOL_INPUT" | jq -r '.skill // ""')
207
207
  if [[ "$SKILL_NAME" == wezard:* ]]; then
@@ -216,7 +216,7 @@ if [[ "$TOOL_NAME" == "Bash" ]]; then
216
216
  && [[ ! "$CMD" =~ [\;\|\&\>\<\`\$\(] ]]; then
217
217
  emit "allow" "read-only bypass"
218
218
  fi
219
- # wezard CLI 同理: /wrc /cd 这些 slash command 的 ! bash 都打到本 daemon,
219
+ # wezard CLI 同理: /wezard:audit /wezard:update 这些 slash command 的 ! bash 都打到本 daemon,
220
220
  # 自己审自己没意义, 也避免首次绑定时无处推卡。
221
221
  if [[ "$CMD" =~ (^|/|[[:space:]])wezard(\.sh)?([[:space:]]|$) ]]; then
222
222
  emit "allow" "wezard self-call bypass"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wezard",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Bridge Claude Code and WeCom: PreToolUse approval cards + bidirectional tmux session mirror with image paste",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -61,7 +61,6 @@
61
61
  "systemd/wezard.service.template",
62
62
  "systemd/wezard-svr.service.template",
63
63
  "config.example.jsonc",
64
- "commands/wrc.md",
65
64
  "commands/audit.md",
66
65
  "commands/update.md",
67
66
  "README.md",
package/web/chat.css CHANGED
@@ -18,20 +18,24 @@
18
18
  --pub: #237a55; /* 公开 */
19
19
  }
20
20
 
21
- html, body { height: 100% }
21
+ html { height: 100% }
22
22
  /* overscroll-behavior:none 阻止整页橡皮筋回弹 —— webview 里它会和内层
23
23
  .thread 抢滚动手势, 表现为"滚一下卡住/整页在弹"。 */
24
- body { overflow: hidden; overscroll-behavior: none; color: var(--ink) }
24
+ /* body 是一根竖轴: 上面 .app (两栏), 下面整页页脚 (wizard 的用量条)。 */
25
+ body {
26
+ height: 100vh; height: 100dvh; display: flex; flex-direction: column;
27
+ overflow: hidden; overscroll-behavior: none; color: var(--ink);
28
+ }
25
29
  button { font: inherit; color: inherit }
26
30
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px }
27
31
 
28
- .app { display: flex; height: 100vh; height: 100dvh }
32
+ .app { display: flex; flex: 1; min-height: 0 }
29
33
  @keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .3 } }
30
34
 
31
35
  /* 名字 = 地址。点是地址的一部分, 也是这一页唯一的颜色记号。 */
32
36
  .nm { font-weight: 600 }
33
37
  .nm::before { content: "."; color: var(--accent); font-weight: 700 }
34
- .nm.human::before, .nm.task::before, .nm.chat::before { content: none }
38
+ .nm.human::before, .nm.task::before, .nm.system::before, .nm.chat::before { content: none }
35
39
 
36
40
  /* ── 侧栏: 我 + 会话 ─────────────────────────────────────────────── */
37
41
  .side {
@@ -44,19 +48,35 @@ button { font: inherit; color: inherit }
44
48
  position: relative; flex: none; padding: 16px 16px 14px; border-bottom: 1px solid var(--line);
45
49
  display: flex; flex-direction: column; gap: 8px; background: var(--card);
46
50
  }
47
- .rolebar .conn { position: absolute; top: 12px; right: 14px }
48
- .rolebar .id { display: flex; gap: 10px; align-items: center; min-width: 0; padding-right: 14px }
51
+ .rolebar .id { display: flex; gap: 10px; align-items: center; min-width: 0 }
49
52
  .rolebar .id .av { font-size: 26px; line-height: 1; flex: none }
50
53
  .rolebar .id .l { min-width: 0; display: flex; flex-direction: column; gap: 2px }
51
54
  .rolebar .id .nm { font-size: 18px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
52
55
  .rolebar .id .k { font-size: 11.5px; color: var(--mute) }
56
+ /* 名片上的状态: 只有执行中带颜色与呼吸, 空闲 / 已关闭是一个安静的词。 */
57
+ .rolebar .st { margin-left: 8px; white-space: nowrap }
58
+ .rolebar .st::before {
59
+ content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 4px;
60
+ border-radius: 50%; background: #b1bac4; vertical-align: 1px;
61
+ }
62
+ .rolebar .st.run { color: #1a7f37 }
63
+ .rolebar .st.run::before { background: #1a7f37; animation: pulse 1.4s infinite }
64
+ .rolebar .st.off { color: var(--faint) }
65
+ .rolebar .st.off::before { background: transparent; box-shadow: inset 0 0 0 1.5px #b1bac4 }
53
66
  .rolebar .job { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink); opacity: .8 }
54
67
  .rolebar .facts { display: flex; gap: 6px 12px; flex-wrap: wrap; font-size: 11.5px; color: var(--mute) }
55
68
  .rolebar .facts span { white-space: nowrap }
56
- .rolebar .facts .go { cursor: pointer; color: var(--accent) }
57
- .rolebar .facts .go:hover { text-decoration: underline }
69
+ .rolebar .go { cursor: pointer; color: var(--accent) }
70
+ .rolebar .go:hover { text-decoration: underline }
58
71
  .rolebar .acts { display: flex; gap: 6px; flex-wrap: wrap; align-items: center }
59
72
  .rolebar .acts:empty { display: none }
73
+ .bd {
74
+ font-size: 11px; line-height: 18px; padding: 0 8px; border: 0; border-radius: 9px; cursor: pointer;
75
+ background: #f1f3f6; color: var(--mute); white-space: nowrap;
76
+ }
77
+ .bd:hover { color: var(--accent) }
78
+ .bd.on { background: var(--mine-soft); color: var(--accent) }
79
+ .bd b { font-weight: 600; margin-left: 4px; font-variant-numeric: tabular-nums }
60
80
  /* session 切换: 按钮 + 浮层列表, 取代原生 <select>。 */
61
81
  .sp { position: relative; flex: 1 1 100%; min-width: 0 }
62
82
  .sp-btn {
@@ -93,39 +113,35 @@ button { font: inherit; color: inherit }
93
113
  .sp-it.all { border-bottom: 1px solid var(--line); border-radius: 6px 6px 0 0; margin-bottom: 3px }
94
114
  .sp-it.all .id { font-family: inherit }
95
115
 
96
- /* 左栏底: session 切换 + 用量, 都是轮次的事 —— 与顶上的身份分开, 钉在会话列表下面。 */
97
- .sidefoot {
98
- flex: none; border-top: 1px solid var(--line); padding: 10px 12px 12px;
99
- display: flex; flex-direction: column; gap: 8px; background: var(--paper);
100
- }
101
- .sidefoot[hidden] { display: none }
102
- #rb-sess:empty, #rb-prof:empty { display: none }
116
+ /* 左栏底: session 切换 —— 轮次的事, 与顶上的身份分开, 钉在会话列表下面。 */
117
+ .sidefoot { flex: none; border-top: 1px solid var(--line); padding: 10px 12px; background: var(--paper) }
103
118
  /* 在底部, 浮层往上开。 */
104
119
  .sidefoot .sp-list { top: auto; bottom: calc(100% + 4px); transform: translateY(4px) }
105
120
  .sidefoot .sp.open .sp-list { transform: none }
106
121
 
107
- /* profile: 这个 role 自己跑过的轮次的总账 —— 平铺在左栏底, 不套卡片。 */
108
- .prof { font-family: ui-monospace, Menlo, monospace }
109
- .prof .hd { display: flex; align-items: center; gap: 8px; font-size: 11.5px; margin-bottom: 6px }
110
- .prof .hd .lb { font-size: 10px; color: var(--faint); text-transform: uppercase; letter-spacing: .4px; font-family: system-ui, sans-serif }
111
- .prof .model {
112
- margin-left: auto; color: var(--mute);
113
- max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
114
- }
115
- .prof .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px 4px }
116
- .prof .kv { display: flex; flex-direction: column; align-items: flex-start; min-width: 0 }
117
- .prof .kv .v { font-size: 13px; font-weight: 700; color: #1f2328; font-variant-numeric: tabular-nums; white-space: nowrap }
118
- .prof .kv .k { font-size: 9.5px; color: #8c959f; text-transform: uppercase; letter-spacing: .4px }
119
- .prof .io { margin-top: 8px; display: flex; flex-direction: column; gap: 6px }
120
- .prof .bar { display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: #eaeef2 }
121
- .prof .bar .seg { height: 100% }
122
- .prof .leg { display: flex; gap: 4px 9px; flex-wrap: wrap }
123
- .prof .lg { display: flex; align-items: center; gap: 4px; font-size: 10.5px; color: #8c959f }
124
- .prof .lg i { width: 8px; height: 8px; border-radius: 2px; flex: none }
125
- .prof .lg b { color: #1f2328; font-weight: 700; font-variant-numeric: tabular-nums }
126
- .conn { display: flex; align-items: center; gap: 5px }
127
- .conn .d { width: 7px; height: 7px; border-radius: 50%; background: #1a7f37 }
128
- .conn.off .d { background: #cf222e }
122
+ /* ── 用量条: 一组轮次的总账 ─────────────────────────────────────────
123
+ 一个组件两处挂: 整页页脚 (wizard 的总账) / 聊天窗口底 (人与某个 wizard 这段往来的账)。
124
+ 宽屏一行排完: 标签 · 指标 · I/O 条 + 图例 · 模型。每一组自身不折行, 一行排不下时
125
+ 整组落到下一行 —— 窄屏自然变成两三行, 不需要另一套结构。 */
126
+ .usage {
127
+ flex: none; display: flex; align-items: center; flex-wrap: wrap; gap: 6px 20px;
128
+ padding: 7px 20px; border-top: 1px solid var(--line); background: var(--paper);
129
+ font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: var(--mute);
130
+ font-variant-numeric: tabular-nums;
131
+ }
132
+ .usage b { font-weight: 700; color: var(--ink) }
133
+ .usage .u-lb { font: 10px system-ui, sans-serif; color: var(--faint); letter-spacing: .4px }
134
+ .usage .u-kvs { display: flex; gap: 4px 16px }
135
+ .usage .kv { white-space: nowrap; font-size: 9.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .4px }
136
+ .usage .kv b { font-size: 12.5px; margin-right: 4px; letter-spacing: 0; text-transform: none }
137
+ .usage .u-io { display: flex; align-items: center; gap: 12px; min-width: 0 }
138
+ .usage .bar { flex: none; width: 140px; display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: var(--line) }
139
+ .usage .bar .seg { height: 100% }
140
+ .usage .leg { display: flex; gap: 4px 12px }
141
+ .usage .lg { display: flex; align-items: center; gap: 4px; white-space: nowrap }
142
+ .usage .lg i { width: 8px; height: 8px; border-radius: 2px; flex: none }
143
+ .usage .lg b { margin-left: 1px }
144
+ .usage .u-model { margin-left: auto; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
129
145
 
130
146
  /* 群聊头像 = 参与者头像拼图: 1 格居中, 2 格左右, 3/4 格田字。 */
131
147
  .mosaic {
@@ -145,7 +161,6 @@ button { font: inherit; color: inherit }
145
161
  display: flex; align-items: baseline; gap: 6px;
146
162
  }
147
163
  .convs h2 span { font-weight: 400; color: var(--faint); font-variant-numeric: tabular-nums }
148
- .convs .none { padding: 6px 10px; font-size: 12px; color: var(--faint) }
149
164
 
150
165
  .ci {
151
166
  display: flex; gap: 10px; align-items: flex-start; width: 100%; text-align: left; overflow: hidden;
@@ -164,23 +179,43 @@ button { font: inherit; color: inherit }
164
179
  .ci .pv { font-size: 12px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
165
180
  .ci .pv:empty { display: none }
166
181
  .ci .tag { font-size: 10.5px; border-radius: 4px; padding: 0 4px; flex: none }
182
+ /* 选中的 role 项上的换视角开关。 */
183
+ .sw {
184
+ flex: none; align-self: center; display: inline-grid; place-items: center;
185
+ width: 20px; height: 20px; border-radius: 5px; color: var(--accent); cursor: pointer;
186
+ }
187
+ .sw:hover { background: #2f55c91a }
167
188
  .ci .tag.priv { color: var(--priv); background: #9a5b1014 }
168
189
 
169
- /* 群里的其他 role —— 缩进挂在群下, 点开是「我与它在这个群里」的往来。 */
170
- .subs { margin: 0 0 6px 34px; border-left: 1px solid var(--line); padding-left: 6px }
190
+ /* 群里的其他 role —— 挂在群下, 与会话项同一种读法 (名字 · 时刻 / 最近一句), 只压一档。
191
+ 点开是「我与它在这个群里」的往来。 */
192
+ .subs { margin: 0 0 6px 16px; border-left: 1px solid var(--line); padding-left: 2px }
171
193
  .si {
172
- display: flex; align-items: center; gap: 7px; width: 100%; text-align: left;
173
- padding: 4px 8px; border: 0; border-radius: 7px; background: transparent; cursor: pointer;
174
- font-size: 12.5px; color: var(--ink);
194
+ display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; overflow: hidden;
195
+ padding: 5px 8px; border: 0; border-radius: 8px; background: transparent; cursor: pointer; color: var(--ink);
175
196
  }
176
197
  .si:hover { background: #f4f6f9 }
177
- .si.on { background: #eaf0fc; color: var(--accent) }
178
- .si .n { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
179
- .si .c { margin-left: auto; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums }
180
- .si.quiet { color: var(--faint) }
198
+ .si.on { background: #eaf0fc }
199
+ .si > .av { font-size: 17px; line-height: 1; flex: none; width: 24px; text-align: center }
200
+ .si .b { min-width: 0; flex: 1; overflow: hidden }
201
+ .si .l1 { display: flex; align-items: baseline; gap: 6px }
202
+ .si .t { font-size: 12.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
203
+ .si.on .t { color: var(--accent) }
204
+ .si .ts { margin-left: auto; flex: none; font-size: 10.5px; color: var(--faint); font-variant-numeric: tabular-nums }
205
+ .si .pv { display: block; font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
206
+ .si .pv:empty { display: none }
207
+
208
+ /* 执行中的 wizard: 头像角上一盏呼吸灯 (同关系图的 .wav .live)。 */
209
+ .ci > .av, .si > .av { position: relative }
210
+ .ci > .av .live, .si > .av .live {
211
+ position: absolute; right: 1px; bottom: -3px; width: 9px; height: 9px;
212
+ border-radius: 50%; background: #1a7f37; border: 2px solid #fff;
213
+ animation: pulse 1.4s infinite;
214
+ }
215
+ .si > .av .live { right: -1px; width: 8px; height: 8px }
181
216
 
182
217
  /* ── 右栏头: 这个群聊 / 私聊是什么 ───────────────────────────────── */
183
- .main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--paper) }
218
+ .main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--card) }
184
219
  .chathead {
185
220
  flex: none; display: flex; align-items: center; gap: 12px; padding: 10px 20px; min-height: 58px;
186
221
  background: var(--card); border-bottom: 1px solid var(--line);
@@ -206,7 +241,7 @@ button { font: inherit; color: inherit }
206
241
  flex: 1; min-height: 0; overflow-y: auto; overflow-anchor: none;
207
242
  padding: 16px 20px 28px; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
208
243
  }
209
- .thread-in { max-width: 960px; margin: 0 auto; display: flex; flex-direction: column; gap: 14px }
244
+ .thread-in { display: flex; flex-direction: column; gap: 14px }
210
245
  .empty { color: var(--faint); text-align: center; padding: 60px 0; font-size: 13px }
211
246
  .more-btn {
212
247
  align-self: center; background: var(--card); border: 1px solid var(--line); border-radius: 16px;
@@ -214,15 +249,16 @@ button { font: inherit; color: inherit }
214
249
  }
215
250
  .more-btn:hover { color: var(--accent); border-color: var(--accent) }
216
251
 
217
- .mrow { display: flex; align-items: flex-start; gap: 10px }
218
- .mrow .av {
219
- flex: none; width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--line);
220
- background: var(--card); font-size: 19px; line-height: 32px; text-align: center; cursor: default; padding: 0;
252
+ /* 头像已并进名字那一行, 不再占气泡旁的一列 —— 省下的那一格宽度 (--av) 还给气泡。 */
253
+ .mrow { --av: 24px; display: flex; align-items: flex-start; gap: 10px }
254
+ .mwho .av {
255
+ flex: none; align-self: center; width: var(--av); height: var(--av); border-radius: 50%; border: 1px solid var(--line);
256
+ background: var(--card); font-size: 14px; line-height: 22px; text-align: center; cursor: default; padding: 0;
221
257
  }
222
- .mcol { min-width: 0; max-width: min(78%, 760px); display: flex; flex-direction: column; gap: 4px }
258
+ .mcol { min-width: 0; max-width: min(78% + var(--av), 760px + var(--av)); display: flex; flex-direction: column; gap: 4px }
223
259
  .mrow.mine { flex-direction: row-reverse }
224
260
  .mrow.mine .mcol { align-items: flex-end }
225
- .mwho { font-size: 11.5px; color: var(--mute); display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap }
261
+ .mwho { font-size: 11.5px; color: var(--mute); display: flex; gap: 6px; align-items: center; flex-wrap: wrap }
226
262
  .mwho .to { color: var(--faint) }
227
263
  .mwho .ch { font-size: 10.5px; border-radius: 4px; padding: 0 4px }
228
264
  .mwho .ch.priv { color: var(--priv); background: #9a5b1014 }
@@ -241,6 +277,7 @@ button { font: inherit; color: inherit }
241
277
  .mchip.priv { color: var(--priv); background: #9a5b1014 }
242
278
  .mchip.pub { color: var(--pub); background: #237a5514 }
243
279
  .mchip.graph { color: #6639ba; background: #8250df12 }
280
+ .mchip.sys { color: #0a7d6b; background: #0a7d6b12 }
244
281
 
245
282
  /* 自己发出的那条: 整块就是视角色。TURN_CSS 里写死的白卡 / 灰底 (嵌套气泡、
246
283
  代码块、折叠头) 会在蓝底上显成一张张贴纸 —— 这里把它们收回到气泡自己的
@@ -254,6 +291,9 @@ button { font: inherit; color: inherit }
254
291
  .mrow.mine .mb .md-body pre code { background: transparent }
255
292
  .mrow.mine .mb .bubble details, .mrow.mine .mb .tg-head { border-color: var(--mine-line) }
256
293
  .mrow.mine .mb .tool-body { border-left-color: var(--mine-line) }
294
+ /* 工具正文是展开时才取的 (chat.js loadTool) —— 空壳期间说一句在干什么。 */
295
+ .tool-body[data-lazy-turn]:empty::before { content: "加载中…"; color: #8c959f; font-size: 12px }
296
+ .tool-body[data-lazy-turn][data-err]:empty::before { content: "加载失败 · 收起再展开重试"; color: #cf222e }
257
297
  .mrow.mine .mb .io-bar { background: var(--mine-soft) }
258
298
 
259
299
  /* 气泡对面那片空白 = 换视角的门。平时不可见, 悬停才露出要去谁那里。 */
@@ -275,8 +315,8 @@ button { font: inherit; color: inherit }
275
315
  .flip .fc { flex: none }
276
316
  .flip:hover, .flip:focus-visible { background: #2f55c90d; color: var(--accent) }
277
317
  .flip[disabled] { cursor: default; background: transparent !important; color: transparent !important }
278
- .mrow .av.go { cursor: pointer }
279
- .mrow .av.go:hover { border-color: var(--accent) }
318
+ .mwho .av.go { cursor: pointer }
319
+ .mwho .av.go:hover { border-color: var(--accent) }
280
320
 
281
321
  .mrow.mark { justify-content: center }
282
322
  .mrow.mark > * { flex: 1; max-width: 520px }
@@ -355,10 +395,15 @@ button { font: inherit; color: inherit }
355
395
  .chathead { padding: 8px 12px; gap: 8px }
356
396
  .chathead .back { display: block }
357
397
  .thread { padding: 12px }
358
- .mcol { max-width: 86% }
398
+ .mcol { max-width: calc(86% + var(--av)) }
359
399
  .flip { min-width: 16px; padding: 0 4px; font-size: 0 }
360
400
  .turn-group { padding: 12px }
361
401
  .thread { padding-bottom: calc(12px + env(safe-area-inset-bottom)) }
402
+ .usage { padding: 8px 12px; gap: 6px 14px }
403
+ .usage .u-kvs, .usage .leg { flex-wrap: wrap }
404
+ .usage .u-io { flex: 1 1 100%; flex-wrap: wrap; gap: 6px 12px }
405
+ .usage .bar { flex: 1 1 100%; width: auto }
406
+ .usage.page { padding-bottom: calc(8px + env(safe-area-inset-bottom)) }
362
407
  }
363
408
 
364
409
  /* UA 的 [hidden] 规则和 `.gbar{display:flex}` 这类类选择器同权重, 而作者样式