ot-cli 0.2.1__py3-none-any.whl

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 (91) hide show
  1. openterminal/__init__.py +3 -0
  2. openterminal/agent/__init__.py +39 -0
  3. openterminal/agent/backend.py +180 -0
  4. openterminal/agent/build.py +115 -0
  5. openterminal/agent/middleware.py +111 -0
  6. openterminal/agent/model.py +41 -0
  7. openterminal/agent/prompt.py +63 -0
  8. openterminal/agent/runner.py +340 -0
  9. openterminal/agent/sysprobe.py +164 -0
  10. openterminal/agent/token_est.py +49 -0
  11. openterminal/app.py +52 -0
  12. openterminal/approval.py +7 -0
  13. openterminal/backend.py +3 -0
  14. openterminal/banner.py +7 -0
  15. openterminal/cli.py +426 -0
  16. openterminal/cmdset.py +7 -0
  17. openterminal/config.py +322 -0
  18. openterminal/connections/__init__.py +20 -0
  19. openterminal/connections/db.py +240 -0
  20. openterminal/connections/models.py +289 -0
  21. openterminal/connections/secrets.py +68 -0
  22. openterminal/connections_db.py +6 -0
  23. openterminal/core/__init__.py +41 -0
  24. openterminal/core/ai_flow.py +781 -0
  25. openterminal/core/auth.py +499 -0
  26. openterminal/core/cmdset_runner.py +352 -0
  27. openterminal/core/exec_events.py +650 -0
  28. openterminal/core/history.py +79 -0
  29. openterminal/core/hook_gate.py +415 -0
  30. openterminal/core/messages.py +193 -0
  31. openterminal/core/nested_ssh.py +178 -0
  32. openterminal/core/pipeline.py +1187 -0
  33. openterminal/core/pump.py +190 -0
  34. openterminal/core/transcript.py +26 -0
  35. openterminal/frontend/__init__.py +21 -0
  36. openterminal/frontend/approval.py +40 -0
  37. openterminal/frontend/banner.py +40 -0
  38. openterminal/frontend/headless.py +290 -0
  39. openterminal/frontend/render.py +41 -0
  40. openterminal/frontend/taskview.py +184 -0
  41. openterminal/frontend/term.py +1417 -0
  42. openterminal/headless.py +7 -0
  43. openterminal/history_db.py +7 -0
  44. openterminal/local_pty.py +3 -0
  45. openterminal/local_win.py +3 -0
  46. openterminal/policy.py +362 -0
  47. openterminal/render.py +7 -0
  48. openterminal/secrets_store.py +5 -0
  49. openterminal/session/__init__.py +17 -0
  50. openterminal/session/base.py +328 -0
  51. openterminal/session/local_posix.py +184 -0
  52. openterminal/session/local_win.py +173 -0
  53. openterminal/session/ssh.py +321 -0
  54. openterminal/shell/__init__.py +17 -0
  55. openterminal/shell/cmdset.py +49 -0
  56. openterminal/shell/integration.py +1298 -0
  57. openterminal/shell_integration.py +7 -0
  58. openterminal/shell_session.py +8 -0
  59. openterminal/skills/__init__.py +45 -0
  60. openterminal/skills/markdown-tables/SKILL.md +247 -0
  61. openterminal/ssh_pty.py +6 -0
  62. openterminal/sysprobe.py +7 -0
  63. openterminal/taskview.py +7 -0
  64. openterminal/term_frontend.py +7 -0
  65. openterminal/token_est.py +3 -0
  66. openterminal/transcript.py +7 -0
  67. openterminal/web/__init__.py +1 -0
  68. openterminal/web/files_api.py +789 -0
  69. openterminal/web/frontend/index.html +37 -0
  70. openterminal/web/frontend/static/app.js +3632 -0
  71. openterminal/web/frontend/static/block_stream.js +274 -0
  72. openterminal/web/frontend/static/block_view.js +383 -0
  73. openterminal/web/frontend/static/files_panel.js +782 -0
  74. openterminal/web/frontend/static/md_lite.js +127 -0
  75. openterminal/web/frontend/static/modal.js +122 -0
  76. openterminal/web/frontend/static/pad_ledger.js +46 -0
  77. openterminal/web/frontend/static/style.css +915 -0
  78. openterminal/web/frontend/static/ui/ui.css +1 -0
  79. openterminal/web/frontend/static/ui/ui.js +6292 -0
  80. openterminal/web/frontend/vendor/addon-fit.js +2 -0
  81. openterminal/web/frontend/vendor/xterm.css +8 -0
  82. openterminal/web/frontend/vendor/xterm.js +8 -0
  83. openterminal/web/protocol.py +41 -0
  84. openterminal/web/server.py +322 -0
  85. openterminal/web/worker.py +62 -0
  86. ot_cli-0.2.1.dist-info/METADATA +439 -0
  87. ot_cli-0.2.1.dist-info/RECORD +91 -0
  88. ot_cli-0.2.1.dist-info/WHEEL +5 -0
  89. ot_cli-0.2.1.dist-info/entry_points.txt +3 -0
  90. ot_cli-0.2.1.dist-info/licenses/LICENSE +202 -0
  91. ot_cli-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,915 @@
1
+ /* OpenTerminal Web 前端样式:深色专业风(参考阿里云 Workbench CLI Agent 终端)。
2
+ 单管线架构:唯一全屏 xterm(PTY 字节直写),AI 以「锚定在终端行上的装饰卡」
3
+ 叠加——分析卡(.ablock)、总结卡(.scard)、审批卡(.acard)。锚定用 xterm
4
+ 原生 registerDecoration(一卡一钉),滚动/resize/trim 由 xterm 管。 */
5
+
6
+ :root {
7
+ --bg: #10141a; /* 应用底色:深炭蓝 */
8
+ --panel: #161b22; /* 侧栏 */
9
+ --panel2: #1c2128; /* pane 头部 / 输入条 */
10
+ --border: #262c33;
11
+ --text: #e6e8ea;
12
+ --muted: #9aa0a6;
13
+ --accent: #ff6a00; /* 强调色:阿里云橙,用于活跃态 */
14
+ --accent-soft: rgba(255, 106, 0, 0.15);
15
+ --term-bg: #0d1117;
16
+ --ok: #3fb950;
17
+ --warn: #d29922;
18
+ --danger: #f85149;
19
+ --violet: #bc8cff; /* 编辑态(与红/橙/绿/灰区分) */
20
+ --cyan: #39c5cf; /* 用户输入文本 / 块状光标(workbench 青) */
21
+ --ap-blue: #2f6feb; /* 普通审批蓝框 / Agent 蓝点 */
22
+ --mono: ui-monospace, "Cascadia Mono", Consolas, "Courier New", monospace;
23
+ --sans: -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
24
+ }
25
+
26
+ * { box-sizing: border-box; }
27
+ [hidden] { display: none !important; } /* author display 不覆盖 hidden 语义 */
28
+ html, body { height: 100%; margin: 0; }
29
+ body {
30
+ display: flex;
31
+ background: var(--bg);
32
+ color: var(--text);
33
+ font: 13px/1.5 var(--sans);
34
+ }
35
+ button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
36
+ input { font: inherit; color: var(--text); background: var(--panel2); border: 1px solid var(--border); border-radius: 4px; }
37
+ input:focus { outline: none; border-color: var(--accent); }
38
+
39
+ /* --- 侧栏 --- */
40
+ #sidebar {
41
+ width: 264px; flex-shrink: 0;
42
+ display: flex; flex-direction: column;
43
+ background: var(--panel); border-right: 1px solid var(--border);
44
+ }
45
+ body.sidebar-collapsed #sidebar { display: none; }
46
+
47
+ #sidebar-head {
48
+ display: flex; align-items: center; gap: 6px;
49
+ padding: 8px 10px; border-bottom: 1px solid var(--border);
50
+ }
51
+ #sidebar-head .title { font-weight: 600; flex: 1; }
52
+ #sidebar-head button { color: var(--muted); padding: 2px 6px; border-radius: 4px; }
53
+ #sidebar-head button:hover { background: var(--panel2); color: var(--text); }
54
+
55
+ #sidebar-search { margin: 8px 10px; padding: 5px 8px; width: auto; }
56
+
57
+ #targets { flex: 1; overflow-y: auto; padding-bottom: 12px; }
58
+
59
+ .sep {
60
+ display: flex; align-items: center; gap: 4px;
61
+ padding: 10px 12px 4px; color: var(--muted);
62
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
63
+ }
64
+ .sep .sep-caret { font-size: 9px; }
65
+
66
+ .target {
67
+ display: flex; align-items: center; gap: 8px;
68
+ width: 100%; text-align: left;
69
+ padding: 6px 12px 6px 16px; color: var(--text);
70
+ }
71
+ .target:hover { background: var(--panel2); }
72
+ .target .t-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
73
+ .target .t-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
74
+ .target .t-sub {
75
+ color: var(--muted); font-size: 10.5px;
76
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
77
+ }
78
+ .target .t-actions { display: none; gap: 2px; flex: none; }
79
+ .target:hover .t-actions { display: flex; }
80
+ .target .t-actions button {
81
+ background: none; border: none; color: var(--muted); cursor: pointer;
82
+ font-size: 12px; padding: 0 4px; border-radius: 4px; line-height: 1.6;
83
+ }
84
+ .target .t-actions button:hover {
85
+ color: var(--text); background: var(--bg);
86
+ }
87
+
88
+ .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
89
+ .dot.idle { background: #454c54; }
90
+ .dot.connecting { background: var(--warn); animation: blink 1s infinite; }
91
+ .dot.connected { background: var(--ok); }
92
+ .dot.closed { background: var(--danger); }
93
+ .dot.error { background: var(--danger); }
94
+ /* tab 标签页上的连接状态点:略小,垂直居中于单行标签 */
95
+ .tab .dot { width: 7px; height: 7px; }
96
+ @keyframes blink { 50% { opacity: 0.35; } }
97
+
98
+ /* --- 主区 --- */
99
+ #main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
100
+
101
+ #tabbar { display: flex; flex: none; min-width: 0; background: var(--panel); }
102
+ #btn-show-sidebar {
103
+ display: none; flex: none; align-self: center; margin: 0 4px;
104
+ padding: 4px 8px; border-radius: 4px; color: var(--muted); white-space: nowrap;
105
+ }
106
+ body.sidebar-collapsed #btn-show-sidebar { display: block; }
107
+ #btn-show-sidebar:hover { background: var(--panel2); color: var(--text); }
108
+
109
+ #tabs {
110
+ flex: 1; min-width: 0;
111
+ display: flex; align-items: stretch; gap: 2px;
112
+ padding: 6px 8px 0; background: var(--panel);
113
+ border-bottom: 1px solid var(--border);
114
+ overflow-x: auto; min-height: 34px;
115
+ }
116
+ .tab {
117
+ display: flex; align-items: center; gap: 6px;
118
+ padding: 5px 8px 5px 12px; border-radius: 6px 6px 0 0;
119
+ color: var(--muted); border: 1px solid transparent; border-bottom: 0;
120
+ max-width: 200px;
121
+ }
122
+ .tab:hover { background: var(--panel2); color: var(--text); }
123
+ .tab.active { background: var(--bg); color: var(--text); border-color: var(--border); }
124
+ .tab .tab-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
125
+ .tab .tab-x { color: var(--muted); padding: 0 2px; border-radius: 3px; }
126
+ .tab .tab-x:hover { background: var(--border); color: var(--text); }
127
+
128
+ #panes { flex: 1; position: relative; min-height: 0; }
129
+ .pane { position: absolute; inset: 0; display: none; flex-direction: column; }
130
+ .pane.active { display: flex; }
131
+
132
+ .pane-head {
133
+ display: flex; align-items: center; gap: 10px;
134
+ padding: 6px 10px; background: var(--panel2); border-bottom: 1px solid var(--border);
135
+ }
136
+ .seg { display: flex; border: 1px solid var(--border); border-radius: 5px; overflow: hidden; }
137
+ .seg button { padding: 3px 12px; color: var(--muted); background: var(--bg); }
138
+ .seg button + button { border-left: 1px solid var(--border); }
139
+ .seg button:hover { color: var(--text); }
140
+ .seg button.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
141
+ .pane-host { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
142
+
143
+ /* --- 唯一渲染管线:全屏连续 xterm(SSH/Agent 共用,不再切换显示) --- */
144
+ .term {
145
+ flex: 1; min-height: 0; padding: 4px 6px;
146
+ background: var(--term-bg); overflow: hidden;
147
+ position: relative; /* 连接中浮层(.connect-overlay)的定位上下文 */
148
+ }
149
+ .term .xterm { height: 100%; }
150
+
151
+ /* 连接中浮层:盖住终端区居中显示,两阶段文案(连接中 → 初始化 Agent),
152
+ ready 即撤;SSH 弹窗须压在连接浮层和文件抽屉之上 */
153
+ .connect-overlay {
154
+ position: absolute; inset: 0; z-index: 40;
155
+ display: flex; align-items: center; justify-content: center; gap: 12px;
156
+ background: color-mix(in srgb, var(--term-bg) 72%, transparent);
157
+ color: var(--muted); font-size: 15px; letter-spacing: 1px;
158
+ }
159
+ .connect-overlay[hidden] { display: none; }
160
+ .connect-overlay .cspin {
161
+ width: 18px; height: 18px; flex: none; border-radius: 50%;
162
+ border: 2.5px solid var(--border); border-top-color: var(--text);
163
+ animation: spin 0.8s linear infinite;
164
+ }
165
+ .connect-overlay .cdots::after {
166
+ content: ""; display: inline-block; width: 1.2em; text-align: left;
167
+ animation: tdots 1.2s steps(1) infinite;
168
+ }
169
+
170
+ /* --- 装饰卡层:锚定终端行的 HTML 卡(样式参照 docs/prototype/deco-demo.html)--- */
171
+
172
+ /* 分析卡(.ablock):蓝边 / 呼吸点 / 「分析」标签 / sans 字体(配色对齐
173
+ main 分支 .ablock:ap-blue 系边框与底色)。宿主宽度由 app.js 设为全宽
174
+ calc(100% - 12px),左缘与提示符列对齐 */
175
+ .ablock {
176
+ position: relative; margin: 0 0 2px 0; padding: 7px 12px 8px;
177
+ border: 1px solid rgba(47, 111, 235, 0.32);
178
+ border-left: 3px solid var(--ap-blue);
179
+ border-radius: 4px; background: #101827;
180
+ font: 13px/1.75 var(--sans);
181
+ max-width: calc(100% - 12px);
182
+ }
183
+ .ablock .ahead {
184
+ display: flex; align-items: center; gap: 6px; margin-bottom: 4px;
185
+ font-family: var(--sans); font-size: 11px; font-weight: 650;
186
+ letter-spacing: 0.06em; color: #7aa7ff;
187
+ }
188
+ .ablock .adot {
189
+ width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
190
+ background: var(--ap-blue); box-shadow: 0 0 0 3px rgba(47, 111, 235, 0.16);
191
+ animation: blink 1.2s infinite;
192
+ }
193
+ .ablock.done .adot { animation: none; } /* 定格后圆点静止 */
194
+ .ablock.failed .alabel { color: var(--muted); }
195
+ .ablock .alabel { color: #7aa7ff; }
196
+ .ablock .atext {
197
+ white-space: pre-wrap; word-break: break-word; color: #c3cedd;
198
+ font-family: var(--sans); font-size: 13px; line-height: 1.75;
199
+ }
200
+ /* 「AI 正在思考」占位(task_start → 首个 token):与流式态同壳(蓝边/表头常显),
201
+ 仅正文位放 spinner 文案——换态只换正文,不重挂不重占位,消切换闪 */
202
+ .ablock.think .atext.think {
203
+ display: flex; align-items: center; gap: 8px;
204
+ color: var(--muted); font-size: 12px; line-height: 1.4; font-style: normal;
205
+ }
206
+ .atext.think::before {
207
+ content: ""; width: 12px; height: 12px; flex: none;
208
+ border: 2px solid #2a3138; border-top-color: var(--cyan);
209
+ border-radius: 50%; animation: spin 0.8s linear infinite;
210
+ }
211
+ @keyframes spin { to { transform: rotate(360deg); } }
212
+
213
+ /* 总结卡(.scard):青系(--cyan #39c5cf 同族,沉稳不刺眼)——青左边框 +
214
+ 青点 + 「总结」标签、底色青调深色;mdLite 渲染 markdown 表格(v4:echarts 移除)。
215
+ 宿主宽度由 app.js 设为全宽 calc(100% - 12px) */
216
+ .scard {
217
+ margin-left: 0; padding: 7px 12px 8px;
218
+ border: 1px solid rgba(57, 197, 207, 0.35);
219
+ border-left: 3px solid var(--cyan);
220
+ border-radius: 4px; background: #0e1618;
221
+ max-width: calc(100% - 12px);
222
+ font: 13px/1.75 var(--sans);
223
+ }
224
+ .scard .ahead {
225
+ display: flex; align-items: center; gap: 6px; margin-bottom: 4px;
226
+ font-family: var(--sans); font-size: 11px; font-weight: 650;
227
+ letter-spacing: 0.06em; color: var(--cyan);
228
+ }
229
+ .scard .adot {
230
+ width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
231
+ background: var(--cyan); box-shadow: 0 0 0 3px rgba(57, 197, 207, 0.15);
232
+ }
233
+ /* mdLite 表格:整表圆角外框 + 蓝色渐变表头(对齐 main .atext .mtable) */
234
+ .scard .mtable {
235
+ overflow-x: auto; margin: 6px 0 10px;
236
+ border: 1px solid var(--border); border-radius: 8px;
237
+ background: rgba(255, 255, 255, 0.015);
238
+ }
239
+ .scard .mtable table, .scard table {
240
+ border-collapse: separate; border-spacing: 0;
241
+ font-size: 12.5px; width: 100%; margin: 0;
242
+ }
243
+ .scard th, .scard td {
244
+ padding: 5px 14px; word-break: break-word; text-align: left;
245
+ border: 0; border-bottom: 1px solid rgba(255, 255, 255, 0.05);
246
+ }
247
+ .scard th {
248
+ background: linear-gradient(180deg, rgba(47, 111, 235, 0.60), rgba(47, 111, 235, 0.38));
249
+ color: #eaf1ff; font-weight: 600; white-space: nowrap;
250
+ border-bottom: 2px solid rgba(47, 111, 235, 0.65);
251
+ }
252
+ .scard tbody tr:last-child td { border-bottom: 0; }
253
+ .scard tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.02); }
254
+ .scard tbody tr:hover { background: rgba(47, 111, 235, 0.10); }
255
+ .scard .a-center { text-align: center; }
256
+ .scard .a-right { text-align: right; }
257
+ .scard .mh { font-weight: 650; margin: 12px 0 6px; color: var(--text); }
258
+ .scard .mh1 { font-size: 15px; }
259
+ .scard .mh2 { font-size: 14px; }
260
+ .scard .mh3, .scard .mh4 { font-size: 13px; color: #c3cedd; }
261
+ .scard .mline { margin: 2px 0; word-break: break-word; }
262
+ .scard .mgap { height: 10px; }
263
+ .scard ol, .scard ul { margin: 3px 0; padding-left: 22px; }
264
+ .scard li { margin: 2px 0; }
265
+ .scard code {
266
+ padding: 1px 5px; border-radius: 4px; background: #1b2129;
267
+ color: #c9d1d9; font-family: var(--mono); font-size: 12px;
268
+ }
269
+ .scard strong { color: #fff; font-weight: 650; }
270
+
271
+ /* 审批卡(.acard):现有审批框样式沿用(.aprobe),宿主全宽 */
272
+ .acard { max-width: calc(100% - 12px); }
273
+
274
+ /* 决策摘要行:审批决策后由思考占位卡原地变成的一行 DOM 摘要(锚在提示符
275
+ 上方,写网格会被 readline 的 \x1b[A\x1b[K 清理擦掉,故必须是 DOM) */
276
+ .dline {
277
+ display: flex; align-items: center; gap: 8px;
278
+ margin: 0 0 2px 0; padding: 2px 10px;
279
+ border: 1px solid var(--border); border-left: 3px solid var(--ok);
280
+ border-radius: 6px; background: #101a13;
281
+ font: 12px/1.5 var(--sans);
282
+ max-width: calc(100% - 12px);
283
+ white-space: nowrap; overflow: hidden;
284
+ }
285
+ .dline.fail { border-left-color: var(--danger); background: rgba(248, 81, 73, .06); }
286
+ .dline.edit { border-left-color: var(--warn); background: rgba(210, 153, 34, .06); }
287
+ .dline .dstate { font-weight: 650; color: var(--ok); flex-shrink: 0; }
288
+ .dline.fail .dstate { color: var(--danger); }
289
+ .dline.edit .dstate { color: var(--warn); }
290
+ .dline .dcmd {
291
+ font-family: var(--mono); font-size: 11.5px; color: var(--muted);
292
+ overflow: hidden; text-overflow: ellipsis;
293
+ }
294
+
295
+ /* 决策后折叠为一行摘要:结果 + 命令(用户拍板,省空间且待办一眼可见) */
296
+ .aprobe.apfolded {
297
+ display: flex; align-items: center; gap: 8px;
298
+ padding: 3px 10px; opacity: .8;
299
+ white-space: nowrap; overflow: hidden;
300
+ }
301
+ .aprobe.apfolded .apstate { font-size: 12px; font-weight: 650; flex-shrink: 0; }
302
+ .aprobe.apfolded .apfold-cmd {
303
+ font-family: var(--mono); font-size: 11.5px; color: var(--muted);
304
+ overflow: hidden; text-overflow: ellipsis;
305
+ }
306
+
307
+ /* --- 审批框本体(蓝框 / 高危红框) --- */
308
+ .aprobe {
309
+ border: 1px solid var(--ap-blue); border-radius: 4px;
310
+ background: #0d1420; overflow: hidden;
311
+ font-family: var(--sans);
312
+ }
313
+ .aprobe.high { border-color: var(--danger); background: #1c1113; }
314
+ .aprobe.ok { border-color: var(--border); background: var(--panel); } /* 决策后转静态中性框 */
315
+
316
+ /* 工具调用小卡:橙色系(区别审批蓝/高危红) */
317
+ .aprobe.tcard { border-color: #c66a1e; background: #181207; }
318
+ .aprobe.tcard .aphead { background: rgba(230, 145, 56, 0.13); }
319
+ .aprobe.tcard .aphead .apq { color: #e8933a; }
320
+
321
+ .aphead {
322
+ display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
323
+ padding: 8px 12px; background: rgba(47, 111, 235, 0.12);
324
+ }
325
+ .aprobe.high .aphead { background: rgba(248, 81, 73, 0.14); }
326
+ .aphead .apq { font-size: 12.5px; font-weight: 650; color: var(--text); }
327
+ .aphead .apbtns { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-left: auto; }
328
+ /* 高危执行二次确认:确认条浮在「执行」按钮正下方(卡头内绝对定位,不占流内
329
+ 高度——卡体高度走 pad 账目,流内弹入会把卡撑高再缩回) */
330
+ .apexec { position: relative; display: inline-flex; }
331
+ .apconfirm {
332
+ position: absolute; right: 0; top: calc(100% + 5px); z-index: 30;
333
+ display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
334
+ padding: 6px 9px; border: 1px solid var(--danger); border-radius: 6px;
335
+ background: #1c1113; box-shadow: 0 4px 14px rgba(0, 0, 0, .5);
336
+ }
337
+ .apconfirm-q { font-size: 12px; font-weight: 650; color: var(--text); }
338
+ .apconfirm button {
339
+ padding: 3px 10px; border: 1px solid var(--border); border-radius: 5px;
340
+ background: var(--panel2); color: var(--text); font-size: 12px;
341
+ }
342
+ .apconfirm button:hover { border-color: var(--muted); }
343
+ .apconfirm button.primary {
344
+ background: #da3633; border-color: #da3633; color: #fff; font-weight: 600;
345
+ }
346
+ .apconfirm button.primary:hover { filter: brightness(1.1); }
347
+ .apbtns button {
348
+ display: inline-flex; align-items: center; gap: 2px;
349
+ padding: 4px 11px; border: 1px solid var(--border); border-radius: 5px;
350
+ background: var(--panel2); color: var(--text); font-size: 12px;
351
+ }
352
+ .apbtns button:hover { border-color: var(--muted); background: #232931; }
353
+ .apbtns button.primary {
354
+ background: var(--ap-blue); border-color: var(--ap-blue); color: #fff; font-weight: 600;
355
+ }
356
+ .aprobe.high .apbtns button.primary { background: #da3633; border-color: #da3633; }
357
+ .apbtns button.primary:hover { filter: brightness(1.1); }
358
+ .apbtns button.ghost { color: var(--muted); border-color: transparent; background: none; }
359
+ .apbtns button.ghost:hover { color: var(--text); border-color: var(--border); }
360
+ .aprobe kbd {
361
+ margin-left: 5px; padding: 0 4px; font-size: 10.5px; line-height: 15px;
362
+ color: var(--muted); background: rgba(255, 255, 255, 0.06);
363
+ border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 3px;
364
+ font-family: var(--mono);
365
+ }
366
+ .apbtns button.primary kbd {
367
+ color: rgba(255, 255, 255, 0.85); border-color: rgba(255, 255, 255, 0.35);
368
+ background: rgba(255, 255, 255, 0.12);
369
+ }
370
+ .aprobe .apcode {
371
+ margin: 0; padding: 9px 12px; background: #0d1117; color: var(--text);
372
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
373
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
374
+ font-family: var(--mono); font-size: 12.5px;
375
+ white-space: pre-wrap; word-break: break-all;
376
+ }
377
+ .aprobe .apreasons { padding: 7px 12px; color: var(--muted); font-size: 12px; }
378
+ .aprobe .apreasons[hidden] { display: none; }
379
+ .apedit { padding: 9px 12px; }
380
+ .apedit[hidden] { display: none; }
381
+ /* 编辑框下拉浮层:绝对定位在卡体下方、不撑卡高——否则 pad 会为编辑框预留
382
+ 空行,保存后卡缩回而预留空行无法回收=真机「修改保存后卡上下大白空」;
383
+ 浮层伸出 host 盒由 EditorCard 临时把宿主置 overflow visible */
384
+ .acard { position: relative; }
385
+ /* 编辑框=pane 级底部浮层(cards.jsx 经 portal 挂到 .pane):卡内流内渲染会
386
+ 溢出宿主预留行、落到缓冲末行之下,滚动到不了(真机「编辑框溢底部看不到」);
387
+ 浮层与终端流无关,不碰 pad 账目、关闭不留白空 */
388
+ .pane > .apedit {
389
+ position: absolute; left: 12px; right: 12px; bottom: 42px; z-index: 30;
390
+ background: var(--panel2, #161b22);
391
+ border: 1px solid var(--border); border-radius: 10px;
392
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
393
+ }
394
+ .apedit textarea {
395
+ width: 100%; padding: 7px 9px; resize: vertical; min-height: 44px;
396
+ background: #0d1117; color: var(--text);
397
+ border: 1px solid var(--ap-blue); border-radius: 6px; outline: none;
398
+ font-family: var(--mono); font-size: 12.5px;
399
+ }
400
+ .apedit-btns { display: flex; gap: 7px; margin-top: 7px; }
401
+ .apedit-btns button {
402
+ padding: 3px 11px; border: 1px solid var(--border); border-radius: 5px;
403
+ background: var(--panel2); font-size: 12px;
404
+ }
405
+ .apedit-btns button.primary {
406
+ background: var(--ap-blue); border-color: var(--ap-blue); color: #fff; font-weight: 600;
407
+ }
408
+ .apstate { font-size: 12px; font-weight: 650; padding: 2px 0; margin-left: auto; }
409
+ /* 决策后徽标接管问句槽(aphead 首位):不再右推,字号字重对齐问句观感 */
410
+ .aphead > .apstate { margin-left: 0; font-size: 12.5px; }
411
+ .apstate.ok { color: var(--ok); }
412
+ .apstate.edit { color: var(--warn); }
413
+ .apstate.fail { color: var(--danger); }
414
+ /* 已同意:命令置灰=已定稿不可更改的观感(拒绝/编辑态保留原色) */
415
+ .aprobe .apcode.locked { color: var(--muted); }
416
+
417
+ /* --- Phase 2 命令卡(execute 工具卡+审批卡二合一) ---
418
+ 四行全定高(name/命令/状态/操作),挂载后高度/行不再变化(红线④):
419
+ 状态行/操作行/理由槽渲染出即占位(空态也渲染定高元素),
420
+ 按钮簇替换为回执不改变行高。*/
421
+ .cmdcode {
422
+ height: 2.6em; /* 定高 2 行 */
423
+ overflow: hidden;
424
+ }
425
+ .aprobe .cmdcode { color: var(--cyan); }
426
+ .cmdstat {
427
+ display: flex; align-items: center; gap: 10px;
428
+ height: 1.9em; /* 定高 1 行:理由槽+状态文案 */
429
+ overflow: hidden;
430
+ }
431
+ .cmdreason {
432
+ flex: 1; min-width: 0; font-size: 12px; color: var(--muted);
433
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
434
+ }
435
+ .cmdacts {
436
+ display: flex; align-items: center; gap: 7px;
437
+ height: 1.9em; /* 定高 1 行:按钮簇→回执同高替换 */
438
+ overflow: hidden;
439
+ /* 还原原版布局:按钮簇在卡头右侧(aphead 内 margin-left:auto 右推) */
440
+ margin-left: auto;
441
+ }
442
+ /* 卡头不换行:按钮簇超宽由 .cmdacts 裁剪,不得挤到第二行撑高卡体 */
443
+ .cmdhead { flex-wrap: nowrap; min-width: 0; }
444
+ .cmdhead .alabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
445
+ .cmdacts .apbtns {
446
+ display: flex; align-items: center; gap: 7px;
447
+ flex-wrap: nowrap; /* 定高槽内不换行(换行被裁=按钮失踪) */
448
+ margin-left: 0;
449
+ }
450
+ .cmdacts .apexec { position: relative; display: flex; gap: 7px; }
451
+
452
+ /* 命令卡状态配色(真机约定,盖过 .aprobe.high 的统一红框):
453
+ 等待审批红 / 编辑紫 / 拒绝灰 / 完成绿 / 失败橙 */
454
+ .cmdcard.waiting { border-color: var(--danger); background: #1c1113; }
455
+ .cmdcard.waiting .aphead { background: rgba(248, 81, 73, 0.14); }
456
+ .cmdcard.editing { border-color: var(--violet); background: #181028; }
457
+ .cmdcard.editing .aphead { background: rgba(188, 140, 255, 0.13); }
458
+ .cmdcard.rejected { border-color: var(--muted); background: var(--panel); }
459
+ .cmdcard.rejected .aphead { background: rgba(154, 160, 166, 0.12); }
460
+ .cmdcard.done { border-color: var(--ok); background: #101a13; }
461
+ .cmdcard.done .aphead { background: rgba(63, 185, 80, 0.13); }
462
+ .cmdcard.failed { border-color: var(--warn); background: #1c1608; }
463
+ .cmdcard.failed .aphead { background: rgba(210, 153, 34, 0.14); }
464
+ /* 状态徽标配色:等待红 / 拒绝灰 / 失败橙 / 编辑紫(ok 沿用绿) */
465
+ .apstate.waiting { color: var(--danger); }
466
+ /* 红色警告卡:配置错误/系统异常(模型超时、工具超时等) */
467
+ .alertcard { border-color: var(--danger); background: #1c1113; }
468
+ .alertcard .aphead { background: rgba(248, 81, 73, 0.14); }
469
+ .alertcard .aphead .apq { color: var(--danger); }
470
+ .apstate.rejected { color: var(--muted); }
471
+ .apstate.fail { color: var(--warn); }
472
+ .apstate.edit, .apreceipt.edit { color: var(--violet); }
473
+ .apreceipt {
474
+ font-size: 12px; font-weight: 650;
475
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
476
+ max-width: 100%;
477
+ }
478
+ .apreceipt.ok { color: var(--ok); }
479
+ .apreceipt.edit { color: var(--warn); }
480
+ .apreceipt.fail { color: var(--danger); }
481
+
482
+ /* --- 开启新会话(唯一入口,状态栏;此前内嵌每张审批卡已删) --- */
483
+ .statusbar .status-newtask {
484
+ padding: 2px 9px; font-size: 12px; font-family: var(--sans);
485
+ color: var(--muted); border: 1px solid var(--border); border-radius: 5px;
486
+ background: var(--panel2);
487
+ }
488
+ .statusbar .status-newtask:hover { color: var(--text); border-color: var(--muted); }
489
+
490
+ /* --- 状态栏 --- */
491
+ .statusbar {
492
+ display: flex; align-items: center; gap: 12px;
493
+ padding: 3px 10px; background: var(--panel2); border-top: 1px solid var(--border);
494
+ color: var(--muted); font-size: 12px;
495
+ }
496
+ .statusbar .status-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
497
+ .statusbar .status-meta { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
498
+ /* ⏹ 中断按钮:任务条右侧常驻(任务全程可点),圆形红框醒目态 */
499
+ .statusbar .status-stop {
500
+ font-size: 13px; line-height: 1; width: 26px; height: 26px;
501
+ display: inline-flex; align-items: center; justify-content: center;
502
+ padding: 0; cursor: pointer; flex-shrink: 0;
503
+ background: var(--panel); color: var(--danger);
504
+ border: 1px solid var(--border); border-radius: 50%;
505
+ }
506
+ .statusbar.task-on .status-stop {
507
+ border-color: rgba(248, 81, 73, 0.55);
508
+ box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.15);
509
+ }
510
+ .statusbar .status-stop:hover { background: rgba(248, 81, 73, 0.12); border-color: var(--danger); }
511
+ .statusbar .status-stop[hidden] { display: none; }
512
+ /* 「AI正在思考」任务条:任务全程常显于状态栏左侧(思考/流式卡/执行/审批),
513
+ 配色对齐分析卡蓝(#7aa7ff),spinner 复用 spin 关键帧,尾部动画省略号 */
514
+ .statusbar .status-think {
515
+ display: inline-flex; align-items: center; gap: 8px; flex: 1;
516
+ min-width: 0; overflow: hidden;
517
+ color: #7aa7ff; font-size: 12px; white-space: nowrap;
518
+ }
519
+ .statusbar .status-think .ttext { letter-spacing: 0.5px; }
520
+ .statusbar .status-think .tdots::after {
521
+ content: ""; display: inline-block; width: 1.2em; text-align: left;
522
+ animation: tdots 1.2s steps(1) infinite;
523
+ }
524
+ @keyframes tdots {
525
+ 0% { content: ""; } 25% { content: "."; }
526
+ 50% { content: ".."; } 75% { content: "..."; }
527
+ }
528
+ .statusbar.task-on {
529
+ background: rgba(122, 167, 255, 0.06);
530
+ }
531
+ /* 「连接中」徽标:建连期(含 local shell 启动、SSH 握手)常显于状态栏左侧,
532
+ spinner + 循环滚动省略号;ready/closed/error 即撤。样式对齐 status-think */
533
+ .statusbar .status-connect {
534
+ display: inline-flex; align-items: center; gap: 8px; flex: 1;
535
+ min-width: 0; overflow: hidden;
536
+ color: var(--muted); font-size: 12px; white-space: nowrap;
537
+ }
538
+ .statusbar .status-connect[hidden] { display: none; }
539
+ .statusbar .status-connect .cspin {
540
+ width: 12px; height: 12px; flex: none; border-radius: 50%;
541
+ border: 2px solid var(--border); border-top-color: var(--muted);
542
+ animation: spin 0.8s linear infinite;
543
+ }
544
+ .statusbar .status-connect .cdots::after {
545
+ content: ""; display: inline-block; width: 1.2em; text-align: left;
546
+ animation: tdots 1.2s steps(1) infinite;
547
+ }
548
+ .statusbar .status-think[hidden] { display: none; }
549
+ .statusbar .status-think .tspin {
550
+ width: 12px; height: 12px; flex: none; border-radius: 50%;
551
+ border: 2px solid rgba(122, 167, 255, 0.25); border-top-color: #7aa7ff;
552
+ animation: spin 0.8s linear infinite;
553
+ }
554
+ .statusbar .status-tokens,
555
+ .statusbar .status-cmdset,
556
+ .statusbar .status-time { font-family: var(--mono); font-size: 11px; white-space: nowrap; }
557
+ .statusbar .status-cmdset { opacity: 0.75; }
558
+
559
+ /* Agent 空提示符占位提示(幽灵文本):叠在光标行 prompt 之后,纯装饰 */
560
+ .ot-ghost {
561
+ color: var(--muted); opacity: .55; font-style: italic;
562
+ pointer-events: none; user-select: none; white-space: pre;
563
+ overflow: hidden; height: 100%;
564
+ }
565
+ .statusbar .status-cmdset-resume {
566
+ background: var(--panel); color: var(--text);
567
+ border: 1px solid var(--border); border-radius: 4px;
568
+ font-size: 11px; padding: 0 6px; cursor: pointer;
569
+ }
570
+ .statusbar .status-cmdset-resume:hover { border-color: var(--accent); }
571
+ .statusbar .status-model {
572
+ font-family: var(--mono); font-size: 11px;
573
+ background: var(--panel); color: var(--text);
574
+ border: 1px solid var(--border); border-radius: 4px; padding: 1px 4px;
575
+ max-width: 160px;
576
+ }
577
+ .statusbar .status-model[hidden] { display: none; }
578
+ .statusbar .status-size { flex-shrink: 0; }
579
+
580
+ /* --- 模式切换分段控件(快捷键 kbd 胶囊;seg 仅作后端 hook 开关) --- */
581
+ .seg button { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; font-size: 12px; }
582
+ .seg button kbd {
583
+ padding: 0 4px; font-size: 10px; line-height: 15px; color: var(--muted);
584
+ background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border);
585
+ border-radius: 3px; font-family: var(--mono);
586
+ }
587
+ .seg button.on kbd { color: var(--accent); border-color: rgba(255, 106, 0, 0.35); }
588
+
589
+ /* --- 模态框 --- */
590
+ #modal-root { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.55); z-index: 1100; }
591
+ #modal-root:empty { display: none; }
592
+ .modal { width: min(440px, 92vw); max-height: 85vh; display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; box-shadow: 0 8px 30px rgba(0,0,0,0.5); }
593
+ .modal h3 { margin: 0 0 8px; font-size: 14px; }
594
+ .modal-body { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
595
+ .modal-body p { margin: 0 0 8px; color: var(--text); white-space: pre-wrap; }
596
+ .modal-body input { width: 100%; padding: 6px 10px; }
597
+ .modal-check { display: flex; align-items: center; gap: 6px; margin-top: 8px; font-size: 12.5px; color: var(--muted); cursor: pointer; }
598
+ .modal-check input { width: auto; padding: 0; }
599
+ .modal-btns { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; flex-shrink: 0; }
600
+ .modal-btns button { padding: 4px 14px; border: 1px solid var(--border); border-radius: 4px; }
601
+ .modal-btns button:hover { background: var(--panel2); }
602
+ .modal-btns button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
603
+
604
+ /* --- 连接表单弹窗(添加/编辑远程机器)--- */
605
+ .form-grid {
606
+ display: grid; grid-template-columns: 76px 1fr;
607
+ gap: 9px 10px; align-items: center;
608
+ }
609
+ .form-grid label { color: var(--muted); font-size: 12.5px; text-align: right; }
610
+ .form-grid input, .form-grid select, .form-grid textarea {
611
+ background: var(--bg); color: var(--text);
612
+ border: 1px solid var(--border); border-radius: 6px;
613
+ padding: 6px 9px; font-size: 13px; width: 100%; box-sizing: border-box;
614
+ }
615
+ .form-grid input:focus, .form-grid select:focus, .form-grid textarea:focus {
616
+ outline: none; border-color: var(--accent);
617
+ }
618
+ .form-grid input.invalid { border-color: var(--danger); }
619
+ .form-grid textarea { font-family: var(--mono); resize: vertical; }
620
+
621
+ /* --- 右键菜单:复制 / 粘贴 --- */
622
+ .ctxmenu {
623
+ position: fixed; z-index: 1000; min-width: 110px;
624
+ background: var(--panel2); border: 1px solid var(--border); border-radius: 6px;
625
+ padding: 4px; box-shadow: 0 4px 16px rgba(0, 0, 0, .45);
626
+ }
627
+ .ctxmenu button {
628
+ display: block; width: 100%; text-align: left;
629
+ padding: 5px 10px; border: none; background: none; color: var(--text);
630
+ font-size: 12px; border-radius: 4px; cursor: pointer;
631
+ }
632
+ .ctxmenu button:hover:not(:disabled) { background: var(--border); }
633
+ .ctxmenu button:disabled { opacity: .4; cursor: default; }
634
+
635
+ /* --- 复制成功小提示:出现 1s 后淡出(复用同一节点,连按不叠影)--- */
636
+ .copy-toast {
637
+ position: absolute; left: 50%; bottom: 44px; transform: translateX(-50%);
638
+ z-index: 900; padding: 5px 14px; border-radius: 999px;
639
+ background: var(--panel2); border: 1px solid var(--border);
640
+ color: var(--text); font-size: 12px; pointer-events: none;
641
+ opacity: 0; transition: opacity .3s ease;
642
+ }
643
+ .copy-toast.show { opacity: .92; }
644
+
645
+ /* --- 文件面板(远程文件浏览/上传/下载,仅 SSH 会话)---
646
+ 抽屉式覆盖在 pane 右侧;top/bottom 由 JS 按 pane-head/statusbar 实际
647
+ 高度设置,不改 .term 布局(避开 xterm fit 敏感链路)。 */
648
+ .files-toggle {
649
+ margin-left: auto; padding: 3px 10px; font-size: 12px;
650
+ color: var(--muted); background: var(--bg);
651
+ border: 1px solid var(--border); border-radius: 5px; cursor: pointer;
652
+ }
653
+ .files-toggle:hover { color: var(--text); border-color: var(--accent); }
654
+
655
+ .files-panel {
656
+ position: absolute; right: 0; top: 0; bottom: 0; width: 340px; max-width: 90%;
657
+ z-index: 700; display: flex; flex-direction: column;
658
+ background: var(--panel); border-left: 1px solid var(--border);
659
+ box-shadow: -6px 0 18px rgba(0, 0, 0, 0.35);
660
+ }
661
+ .files-panel[hidden] { display: none; }
662
+ /* 面板打开时整个块流区让位(历史卡片与活动终端一起收缩);面板仍是
663
+ absolute 覆盖层不改 DOM 结构,只靠 .ot-stage 的 margin 让位。min() 与面板
664
+ width/max-width 对齐——窄窗口面板压到 90% 时终端同样只留 10%。
665
+ 列宽同步走 fit()(fitAddon → onResize → 后端 resize),与窗口 resize
666
+ 同一条链路,卡片重贴不旁路。 */
667
+ .pane.fp-open > .ot-stage { margin-right: min(340px, 90%); }
668
+ /* 头部两行:标题+来源徽标一行,操作按钮一行并允许换行——按钮一多塞单行
669
+ 会把标题挤成三行、右侧按钮溢出面板(真机截图) */
670
+ .fp-head {
671
+ display: flex; flex-direction: column; gap: 6px;
672
+ padding: 6px 10px; border-bottom: 1px solid var(--border);
673
+ }
674
+ .fp-head-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
675
+ .fp-head-acts { display: flex; flex-wrap: wrap; gap: 6px; }
676
+ /* 关闭键常驻标题行右端:塞进操作行会让它单独掉到第二行(真机量尺寸) */
677
+ .fp-head-top .fp-x { flex: none; padding: 2px 7px; }
678
+ .fp-title { flex: 1; min-width: 0; font-size: 12px; font-weight: 600;
679
+ color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
680
+ /* 来源徽标:本机(会话直连)/ 级联目标主机 */
681
+ .fp-badge {
682
+ flex-shrink: 0; max-width: 40%; overflow: hidden;
683
+ text-overflow: ellipsis; white-space: nowrap;
684
+ padding: 1px 7px; font-size: 10px; border-radius: 999px;
685
+ color: var(--muted); background: var(--bg); border: 1px solid var(--border);
686
+ }
687
+ .fp-badge.cascade { color: #39c5cf; border-color: #2a6f75; }
688
+ .fp-connect-hint { padding: 2px 12px 0; font-size: 11px; color: var(--muted); }
689
+ .fp-btn {
690
+ padding: 2px 8px; font-size: 12px; color: var(--muted);
691
+ background: var(--bg); border: 1px solid var(--border);
692
+ border-radius: 4px; cursor: pointer; white-space: nowrap;
693
+ }
694
+ .fp-btn:hover { color: var(--text); border-color: var(--accent); }
695
+ /* 工具栏图标按钮:文字按钮换图标后收紧内边距,与 tab 关闭 × 同构 */
696
+ .fp-btn.fp-icon { padding: 2px 7px; line-height: 1.4; font-size: 13px; }
697
+ .fp-path {
698
+ padding: 5px 10px; font-size: 11px; color: var(--muted);
699
+ border-bottom: 1px solid var(--border);
700
+ overflow-x: auto; white-space: nowrap; flex: none;
701
+ }
702
+ .fp-path a { color: var(--cyan); cursor: pointer; }
703
+ .fp-muted { color: var(--muted); }
704
+ .fp-empty .fp-retry { margin-top: 10px; }
705
+ .fp-path a:hover { text-decoration: underline; }
706
+ .fp-list {
707
+ flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0;
708
+ }
709
+ .fp-list.drop { outline: 2px dashed var(--accent); outline-offset: -4px; }
710
+ .fp-row {
711
+ display: flex; align-items: center; gap: 6px;
712
+ padding: 4px 10px; font-size: 12px; color: var(--text); cursor: pointer;
713
+ }
714
+ /* 选中态不能只靠 accent-soft——那与 :hover 同色,看不出选中。
715
+ 用左侧竖条把「选中」与「鼠标掠过」分开。 */
716
+ .fp-row:hover { background: var(--accent-soft); }
717
+ .fp-row.active {
718
+ background: var(--accent-soft);
719
+ box-shadow: inset 2px 0 0 var(--accent);
720
+ }
721
+ .fp-row.active:hover { background: var(--accent-soft); }
722
+ .fp-row .fp-icon { flex: none; }
723
+ .fp-row .fp-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
724
+ .fp-row .fp-meta { flex: none; font-size: 11px; color: var(--muted); }
725
+ .fp-empty { padding: 18px 10px; text-align: center; font-size: 12px; color: var(--muted); }
726
+ .fp-progress {
727
+ height: 3px; background: var(--bg); flex: none;
728
+ }
729
+ .fp-progress i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .15s; }
730
+ .fp-foot {
731
+ display: flex; align-items: center; gap: 8px;
732
+ padding: 8px 10px; border-top: 1px solid var(--border); flex: none;
733
+ }
734
+ .fp-hint { font-size: 10px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
735
+
736
+ /* 面板操作结果提示:复用 copy-toast 的浮层样式 */
737
+ .fp-toast {
738
+ position: absolute; left: 50%; bottom: 44px; transform: translateX(-50%);
739
+ z-index: 901; padding: 5px 14px; border-radius: 999px;
740
+ background: var(--panel2); border: 1px solid var(--border);
741
+ color: var(--text); font-size: 12px; pointer-events: none;
742
+ opacity: 0; transition: opacity .3s ease; max-width: 80%;
743
+ }
744
+ .fp-toast.show { opacity: .92; }
745
+ .fp-toast.bad { border-color: var(--danger); color: var(--danger); }
746
+
747
+ /* --- 文件面板管理操作(新建/编辑/删除)--- */
748
+ .fp-acts { display: none; gap: 2px; flex: none; }
749
+ /* 悬浮出现;选中行也常驻——否则选中后鼠标移开就找不到下载按钮 */
750
+ .fp-row:hover .fp-acts,
751
+ .fp-row.active .fp-acts { display: flex; }
752
+ .fp-act {
753
+ padding: 0 4px; font-size: 11px; line-height: 1.5;
754
+ color: var(--muted); border-radius: 3px; cursor: pointer;
755
+ }
756
+ .fp-act:hover { color: var(--text); background: var(--border); }
757
+ .fp-name-input { width: 100%; }
758
+ .fp-name-input + .fp-name-input { margin-top: 8px; }
759
+ /* 级联连接弹窗:密码框间距 + 记住密码勾选 + 说明行 */
760
+ .fp-remember {
761
+ display: flex; align-items: center; gap: 6px;
762
+ margin: 6px 0 0; font-size: 12px; color: var(--text); cursor: pointer;
763
+ }
764
+ .fp-hint2 { margin: 8px 0 0; font-size: 11px; color: var(--muted); }
765
+ .fp-edit {
766
+ width: 100%; height: 55vh; resize: vertical;
767
+ background: var(--term-bg); color: var(--text);
768
+ border: 1px solid var(--border); border-radius: 4px; padding: 8px;
769
+ font: 12px/1.5 var(--mono); white-space: pre; overflow: auto;
770
+ }
771
+ .fp-edit:focus { outline: none; border-color: var(--accent); }
772
+ .fp-edit-hint { margin: 6px 0 0; font-size: 11px; color: var(--muted); }
773
+
774
+ /* --- 响应式:不同显示器宽度 / 系统缩放下布局收敛 ---
775
+ 此前本文件没有任何 @media:264px 侧栏在小屏常驻会把终端挤成一条缝,
776
+ 状态栏的次要元数据(token/尺寸/耗时)在窄窗口里把操作按钮挤出可视区。
777
+ 原则是「终端优先」:先收侧栏宽度,再收浮层化,最后裁状态栏次要项;
778
+ 任何档位都不改变卡片几何(卡宽恒 calc(100% - 12px),量高由 JS 按实测)。 */
779
+ @media (max-width: 1280px) {
780
+ #sidebar { width: 218px; }
781
+ .tab { max-width: 168px; }
782
+ }
783
+ @media (max-width: 1024px) {
784
+ #sidebar { width: 188px; }
785
+ .tab { max-width: 148px; }
786
+ .statusbar { gap: 8px; }
787
+ .statusbar .status-meta { gap: 8px; }
788
+ }
789
+ @media (max-width: 860px) {
790
+ /* 侧栏转浮层:终端拿满宽度,仍可从顶栏按钮唤出(配套 JS 在窄屏启动时
791
+ 自动收起、跨过阈值变窄时也收起,只收不弹——不与用户手动展开抢状态) */
792
+ #sidebar {
793
+ position: fixed; inset: 0 auto 0 0; z-index: 600;
794
+ width: min(78vw, 264px);
795
+ box-shadow: 0 0 28px rgba(0, 0, 0, 0.5);
796
+ }
797
+ /* 次要元数据让位给状态文案与操作钮 */
798
+ .statusbar .status-meta .status-tokens,
799
+ .statusbar .status-meta .status-size,
800
+ .statusbar .status-meta .status-time { display: none; }
801
+ }
802
+ @media (max-width: 640px) {
803
+ .statusbar { gap: 6px; padding: 3px 6px; }
804
+ .statusbar .status-text { display: none; }
805
+ .pane-head { gap: 6px; padding: 5px 8px; }
806
+ .seg button { padding: 3px 8px; }
807
+ #tabs { padding: 4px 6px 0; }
808
+ .tab { max-width: 118px; padding: 5px 6px 5px 8px; }
809
+ }
810
+
811
+ /* --- 块流布局(Phase 1):卡片块与文本块是平级兄弟节点,不再贴在 xterm 网格行上 ---
812
+ 这样三条硬约束同时消失:不遮挡(兄弟节点不重叠)、不裁切(没有 clamp)、
813
+ 无大空白(没有 pad,也就没有「不可回吐的垫行」)。 */
814
+ /* 单一滚动面:块区与活动终端是同一个流,不能各滚各的——两套滚动条会让
815
+ 卡片滚出可视区却被当「在屏内」量几何(真机 C2 按钮 hit=null/DIV 的根因) */
816
+ .ot-stage {
817
+ flex: 1; min-height: 0; display: flex; flex-direction: column;
818
+ position: relative; background: var(--term-bg);
819
+ overflow-y: auto; overflow-x: hidden;
820
+ }
821
+ /* 冻结块区:历史文本块 + 卡片块。空时不占位 */
822
+ .ot-blocks {
823
+ flex: 0 0 auto; overflow: visible;
824
+ background: var(--term-bg);
825
+ }
826
+ .ot-blocks:empty { display: none; }
827
+ .ot-stage .term { flex: 0 0 auto; position: relative; }
828
+
829
+ /* 文本块 / 过渡段 / 横幅 */
830
+ .ot-blk { position: relative; }
831
+ .ot-blk-body {
832
+ font-family: var(--mono); font-size: 13px; line-height: 1.5;
833
+ color: var(--text); white-space: pre-wrap; word-break: break-word;
834
+ padding: 2px 12px;
835
+ }
836
+ .ot-blk-banner .ot-blk-body { color: var(--muted); }
837
+ /* 过渡段(sudo su - / ssh 跳板):紫色左边线标注,内容照常显示 */
838
+ .ot-blk-transition { border-left: 2px solid rgba(188, 140, 255, 0.55); margin: 4px 0; }
839
+ .ot-blk-transition .ot-blk-body { color: var(--muted); }
840
+
841
+ /* 卡片块:高度由内容自撑——这是「不裁切」的结构来源 */
842
+ .ot-blk-card { padding: 0 0 2px; }
843
+ .ot-flow-host {
844
+ overflow: visible !important; /* 覆盖 island.css 的 overflow:hidden */
845
+ height: auto !important;
846
+ max-height: none !important;
847
+ margin: 6px 12px 4px;
848
+ }
849
+ .ot-flow-host > * { margin-top: 0; }
850
+
851
+ /* 块流里卡片高度自撑,不再需要「裁进网格行」。island.css 给 .acard/.ablock
852
+ 的 overflow:hidden 原本是防卡片越过预留行盖住下方文本——块布局下兄弟节点
853
+ 本就不重叠,留着它只会把高危两步确认的弹层、编辑浮层裁到看不见(真机
854
+ 「点执行按钮没反应」的直接成因:弹层在按钮下方,被卡体裁掉)。 */
855
+ .ot-flow-host .acard,
856
+ .ot-flow-host .ablock,
857
+ .ot-flow-host .scard,
858
+ .ot-flow-host .aprobe,
859
+ .ot-flow-host .dline {
860
+ overflow: visible;
861
+ }
862
+ /* 确认弹层压在卡体之上。**层级必须显式钉死**:.apcode/.cmdstat 是不透明底
863
+ (#0d1117)且 DOM 顺序在 .aphead 之后,一旦叠到弹层上面就把「高危命令,
864
+ 确认执行?」整块盖住——真机 elementFromPoint 打到 PRE.apcode,用户看到的
865
+ 就是「点执行按钮没反应」。卡头 z 3 > 正文 z 1,弹层再 z 10。 */
866
+ .ot-flow-host .aphead { position: relative; z-index: 3; }
867
+ .ot-flow-host .apcode,
868
+ .ot-flow-host .cmdstat,
869
+ .ot-flow-host .apreasons,
870
+ .ot-flow-host .atext,
871
+ .ot-flow-host .areason { position: relative; z-index: 1; }
872
+ .ot-flow-host .apconfirm { position: absolute; z-index: 10; }
873
+ /* 整条卡头链不许裁切:确认弹层挂在 .apexec 下方,一半落在 .aphead 盒外,
874
+ 被裁掉后命中测试打到 .aphead 自己(Playwright:「.aphead intercepts
875
+ pointer events」)——真机「点执行按钮没反应」的最终成因。块布局下卡片
876
+ 高度自撑,本来就没有任何裁切的必要。 */
877
+ .ot-flow-host .aphead,
878
+ .ot-flow-host .cmdhead,
879
+ .ot-flow-host .cmdacts,
880
+ .ot-flow-host .apbtns,
881
+ .ot-flow-host .apexec,
882
+ .ot-flow-host .adot,
883
+ .ot-flow-host .alabel,
884
+ .ot-flow-host .apstate {
885
+ overflow: visible;
886
+ }
887
+
888
+ /* 块流不受网格预留行限制:完整展示命令,操作区自然换行并撑高卡头。
889
+ 仅覆盖 flow host,保留 decoration 回退路径原有的量高/裁切约定。 */
890
+ .ot-flow-host .cmdcode { height: auto; overflow: visible; }
891
+ .ot-flow-host .cmdhead { flex-wrap: wrap; }
892
+ .ot-flow-host .cmdhead .alabel {
893
+ max-width: 100%; white-space: normal; overflow-wrap: anywhere;
894
+ }
895
+ .ot-flow-host .cmdhead .apstate { flex: none; white-space: nowrap; }
896
+ .ot-flow-host .cmdacts { height: auto; min-width: 0; max-width: 100%; }
897
+ .ot-flow-host .cmdacts .apbtns { flex-wrap: wrap; min-width: 0; }
898
+ .ot-flow-host .cmdacts button { flex: none; white-space: nowrap; }
899
+ /* 保留原来的小确认框:以执行按钮为锚点,边缘避让由组件实测平移。 */
900
+ .ot-flow-host .cmdhead .apexec { position: relative; flex: none; }
901
+ .ot-flow-host .cmdhead .apconfirm {
902
+ left: auto; right: 0; top: calc(100% + 5px);
903
+ width: max-content; flex-wrap: wrap; white-space: normal;
904
+ }
905
+
906
+ /* 工具卡状态配色:完成绿 / 失败橙——与命令卡口径统一(真机反馈:
907
+ read_file 这类非 execute 工具卡一直是橙色,和「已完成」的语义对不上) */
908
+ .tcard.done {
909
+ border-color: var(--ok);
910
+ border-left-color: var(--p-warn);
911
+ background: #101a13;
912
+ }
913
+ .tcard.done .aphead { background: rgba(63, 185, 80, 0.13); }
914
+ .tcard.failed { border-color: var(--warn); background: #1c1608; }
915
+ .tcard.failed .aphead { background: rgba(210, 153, 34, 0.14); }