psyclaw 0.29.10 → 0.29.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +4 -4
  2. package/dist/apps/panel/index.html +643 -273
  3. package/dist/src/adapters/pi/extension.js +40 -1
  4. package/dist/src/adapters/pi/extension.js.map +1 -1
  5. package/dist/src/adapters/pi/rpc.js +1 -1
  6. package/dist/src/adapters/pi/rpc.js.map +1 -1
  7. package/dist/src/ars/mode-editor.d.ts +1 -5
  8. package/dist/src/ars/mode-editor.js +1 -9
  9. package/dist/src/ars/mode-editor.js.map +1 -1
  10. package/dist/src/branding.d.ts +4 -3
  11. package/dist/src/branding.js +8 -5
  12. package/dist/src/branding.js.map +1 -1
  13. package/dist/src/chat.d.ts +9 -0
  14. package/dist/src/chat.js +26 -3
  15. package/dist/src/chat.js.map +1 -1
  16. package/dist/src/cli-args.d.ts +1 -0
  17. package/dist/src/cli-args.js +1 -0
  18. package/dist/src/cli-args.js.map +1 -1
  19. package/dist/src/cli.js +12 -7
  20. package/dist/src/cli.js.map +1 -1
  21. package/dist/src/index.d.ts +2 -0
  22. package/dist/src/index.js +2 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/literature/knowledge-map.d.ts +4 -0
  25. package/dist/src/literature/knowledge-map.js +4 -0
  26. package/dist/src/literature/knowledge-map.js.map +1 -1
  27. package/dist/src/network-routing.js +6 -3
  28. package/dist/src/network-routing.js.map +1 -1
  29. package/dist/src/panel/server.js +1 -106
  30. package/dist/src/panel/server.js.map +1 -1
  31. package/dist/src/project/bootstrap.js +0 -2
  32. package/dist/src/project/bootstrap.js.map +1 -1
  33. package/dist/src/research/choice.d.ts +16 -0
  34. package/dist/src/research/choice.js +25 -0
  35. package/dist/src/research/choice.js.map +1 -0
  36. package/dist/src/session/continuously-work.d.ts +16 -0
  37. package/dist/src/session/continuously-work.js +44 -0
  38. package/dist/src/session/continuously-work.js.map +1 -0
  39. package/dist/src/session/help.js +39 -22
  40. package/dist/src/session/help.js.map +1 -1
  41. package/dist/src/style/cli-ui.js +1 -0
  42. package/dist/src/style/cli-ui.js.map +1 -1
  43. package/dist/src/verify/checklist.d.ts +2 -0
  44. package/dist/src/verify/checklist.js +36 -9
  45. package/dist/src/verify/checklist.js.map +1 -1
  46. package/dist/src/wake-options/runtime.d.ts +1 -0
  47. package/dist/src/wake-options/runtime.js +11 -1
  48. package/dist/src/wake-options/runtime.js.map +1 -1
  49. package/package.json +1 -1
  50. package/scripts/psyclaw.mjs +6 -2
@@ -160,20 +160,29 @@
160
160
  display: grid;
161
161
  grid-template-columns: 210px minmax(0, 1fr);
162
162
  min-height: 0;
163
+ transition: grid-template-columns 0.2s cubic-bezier(0.4, 0, 0.2, 1);
164
+ }
165
+ .layout.nav-collapsed {
166
+ grid-template-columns: 58px minmax(0, 1fr);
163
167
  }
164
168
 
165
169
  .nav {
166
- padding: 16px 10px;
170
+ padding: 14px 8px;
167
171
  background: var(--bg-sidebar);
168
172
  border-right: 1px solid var(--border-subtle);
169
173
  display: flex;
170
174
  flex-direction: column;
171
175
  justify-content: space-between;
176
+ overflow: hidden;
177
+ transition: padding 0.2s ease;
178
+ position: relative;
172
179
  }
173
- .nav-group {
180
+ .nav-header {
174
181
  display: flex;
175
- flex-direction: column;
176
- gap: 3px;
182
+ align-items: center;
183
+ justify-content: space-between;
184
+ padding: 4px 6px 8px;
185
+ margin-bottom: 4px;
177
186
  }
178
187
  .nav-label {
179
188
  font-size: 10.5px;
@@ -181,26 +190,67 @@
181
190
  color: var(--text-tertiary);
182
191
  letter-spacing: 0.05em;
183
192
  text-transform: uppercase;
184
- padding: 6px 10px 4px;
185
- margin-bottom: 2px;
193
+ white-space: nowrap;
194
+ overflow: hidden;
195
+ text-overflow: ellipsis;
196
+ transition: opacity 0.15s ease;
197
+ }
198
+ .nav-toggle-btn {
199
+ width: 26px;
200
+ height: 26px;
201
+ display: grid;
202
+ place-items: center;
203
+ border-radius: var(--radius-sm);
204
+ color: var(--text-muted);
205
+ background: transparent;
206
+ cursor: pointer;
207
+ transition: all 0.15s ease;
208
+ flex-shrink: 0;
209
+ }
210
+ .nav-toggle-btn:hover {
211
+ background: var(--bg-surface-subtle);
212
+ color: var(--text-main);
213
+ }
214
+ .layout.nav-collapsed .nav-header {
215
+ justify-content: center;
216
+ padding: 2px 0 10px;
217
+ }
218
+ .layout.nav-collapsed .nav-label {
219
+ display: none;
220
+ }
221
+ .layout.nav-collapsed .nav-toggle-btn svg {
222
+ transform: rotate(180deg);
223
+ }
224
+
225
+ .nav-group {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 4px;
186
229
  }
187
230
  .nav button {
188
231
  width: 100%;
189
232
  display: flex;
190
233
  align-items: center;
191
- gap: 9px;
192
- padding: 8px 10px;
234
+ gap: 10px;
235
+ padding: 9px 10px;
193
236
  border-radius: var(--radius-sm);
194
237
  color: var(--text-secondary);
195
238
  font-weight: 500;
196
239
  font-size: 12.5px;
197
240
  text-align: left;
198
- transition: all 0.15s ease;
241
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
199
242
  position: relative;
243
+ white-space: nowrap;
244
+ }
245
+ .nav button .nav-btn-text {
246
+ flex: 1;
247
+ overflow: hidden;
248
+ text-overflow: ellipsis;
249
+ transition: opacity 0.15s ease;
200
250
  }
201
251
  .nav button svg {
202
- width: 16px;
203
- height: 16px;
252
+ width: 17px;
253
+ height: 17px;
204
254
  color: var(--text-muted);
205
255
  flex-shrink: 0;
206
256
  transition: color 0.15s ease;
@@ -222,12 +272,29 @@
222
272
  .nav button.active svg {
223
273
  color: var(--brand-primary);
224
274
  }
275
+
276
+ /* Collapsed Nav Styles */
277
+ .layout.nav-collapsed .nav {
278
+ padding: 12px 6px;
279
+ }
280
+ .layout.nav-collapsed .nav button {
281
+ justify-content: center;
282
+ padding: 9px 0;
283
+ }
284
+ .layout.nav-collapsed .nav button .nav-btn-text {
285
+ display: none;
286
+ }
287
+ .layout.nav-collapsed .nav-footer {
288
+ display: none;
289
+ }
290
+
225
291
  .nav-footer {
226
292
  padding: 12px 10px;
227
293
  border-top: 1px solid var(--border-subtle);
228
294
  font-size: 11px;
229
295
  color: var(--text-muted);
230
296
  line-height: 1.4;
297
+ transition: opacity 0.15s ease;
231
298
  }
232
299
  .nav-footer code {
233
300
  font-family: var(--font-mono);
@@ -939,89 +1006,47 @@
939
1006
  background: var(--brand-primary-hover);
940
1007
  }
941
1008
 
942
- /* Chat drawer — light app chrome (not terminal) */
943
- .console-fab {
944
- position: fixed;
945
- bottom: 24px;
946
- right: 28px;
947
- z-index: 100;
948
- display: flex;
949
- align-items: center;
950
- gap: 8px;
951
- padding: 10px 18px;
952
- background: var(--bg-surface);
953
- color: var(--brand-primary);
954
- border: 1px solid var(--brand-primary-border);
955
- border-radius: var(--radius-lg);
956
- font-size: 13px;
957
- font-weight: 600;
958
- cursor: pointer;
959
- box-shadow: var(--shadow-md);
960
- transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
961
- }
962
- .console-fab:hover {
963
- background: var(--brand-primary-light);
964
- border-color: var(--brand-primary);
965
- transform: translateY(-1px);
966
- }
1009
+ /* In-page chat: Modern Codex / Gemini style */
967
1010
  .console-drawer {
968
- position: fixed;
969
- bottom: 0;
970
- right: 28px;
971
- width: 480px;
972
- max-width: calc(100vw - 40px);
973
- height: 560px;
974
- max-height: calc(100vh - 80px);
1011
+ position: relative;
1012
+ width: 100%;
1013
+ height: 100%;
1014
+ min-height: 72vh;
1015
+ flex: 1;
975
1016
  background: var(--bg-surface);
976
1017
  color: var(--text-main);
977
- border: 1px solid var(--border-subtle);
978
- border-bottom: 0;
979
- border-radius: var(--radius-lg) var(--radius-lg) 0 0;
980
- box-shadow: var(--shadow-md);
1018
+ border: none;
981
1019
  display: flex;
982
1020
  flex-direction: column;
983
- z-index: 1000;
984
- transform: translateY(105%);
985
- transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
986
1021
  font-family: var(--font-sans);
987
1022
  }
988
- .console-drawer.open {
989
- transform: translateY(0);
990
- }
991
- .console-drawer.embedded {
992
- border: none;
993
- border-radius: 0;
994
- box-shadow: none;
995
- transform: none;
996
- }
997
1023
  .console-header {
998
1024
  display: flex;
999
1025
  align-items: center;
1000
1026
  justify-content: space-between;
1001
- padding: 12px 16px;
1027
+ padding: 12px 20px;
1002
1028
  border-bottom: 1px solid var(--border-subtle);
1003
1029
  background: var(--bg-surface);
1004
- border-radius: var(--radius-lg) var(--radius-lg) 0 0;
1005
- }
1006
- .console-drawer.embedded .console-header {
1007
- border-radius: 0;
1008
1030
  }
1009
1031
  .console-title {
1010
- font-size: 13px;
1032
+ font-size: 13.5px;
1011
1033
  font-weight: 650;
1012
1034
  color: var(--text-main);
1013
1035
  display: flex;
1014
1036
  align-items: center;
1015
- gap: 8px;
1037
+ gap: 9px;
1016
1038
  }
1017
1039
  .console-status-tag {
1018
- font-size: 10px;
1040
+ font-size: 10.5px;
1019
1041
  font-weight: 600;
1020
- padding: 1px 6px;
1021
- border-radius: var(--radius-xs);
1042
+ padding: 1px 7px;
1043
+ border-radius: 999px;
1022
1044
  background: var(--ok-bg);
1023
1045
  color: var(--ok-text);
1024
1046
  border: 1px solid var(--ok-border);
1047
+ display: inline-flex;
1048
+ align-items: center;
1049
+ gap: 4px;
1025
1050
  }
1026
1051
  .console-actions {
1027
1052
  display: flex;
@@ -1033,140 +1058,372 @@
1033
1058
  border: none;
1034
1059
  color: var(--text-muted);
1035
1060
  cursor: pointer;
1036
- padding: 4px;
1061
+ padding: 5px;
1037
1062
  border-radius: var(--radius-sm);
1038
1063
  display: flex;
1064
+ align-items: center;
1065
+ justify-content: center;
1066
+ transition: all 0.15s ease;
1039
1067
  }
1040
1068
  .console-btn-icon:hover {
1041
1069
  color: var(--text-main);
1042
1070
  background: var(--bg-surface-subtle);
1043
1071
  }
1044
- .console-quick-chips {
1045
- display: flex;
1046
- align-items: center;
1047
- gap: 6px;
1048
- padding: 8px 14px;
1049
- background: var(--bg-surface-subtle);
1050
- border-bottom: 1px solid var(--border-subtle);
1051
- overflow-x: auto;
1052
- }
1053
- .chip {
1054
- font-family: var(--font-mono);
1055
- font-size: 11px;
1056
- background: var(--bg-surface);
1057
- color: var(--text-secondary);
1058
- border: 1px solid var(--border-default);
1059
- padding: 3px 8px;
1060
- border-radius: var(--radius-sm);
1061
- cursor: pointer;
1062
- white-space: nowrap;
1063
- transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
1064
- }
1065
- .chip:hover {
1066
- background: var(--brand-primary-light);
1067
- border-color: var(--brand-primary-border);
1068
- color: var(--brand-primary);
1069
- }
1072
+
1070
1073
  .console-log {
1071
1074
  flex: 1;
1072
1075
  overflow-y: auto;
1073
- padding: 16px;
1076
+ padding: 24px 20px;
1074
1077
  font-family: var(--font-sans);
1075
- font-size: 13px;
1076
- line-height: 1.55;
1078
+ font-size: 13.5px;
1079
+ line-height: 1.6;
1077
1080
  display: flex;
1078
1081
  flex-direction: column;
1079
- gap: 12px;
1082
+ gap: 18px;
1080
1083
  background: var(--bg-app);
1084
+ scroll-behavior: smooth;
1081
1085
  }
1086
+
1087
+ /* Message Rows */
1082
1088
  .log-entry {
1083
1089
  display: flex;
1084
- flex-direction: column;
1085
- gap: 4px;
1086
- max-width: 92%;
1090
+ width: 100%;
1091
+ max-width: 860px;
1092
+ margin: 0 auto;
1093
+ gap: 12px;
1094
+ animation: fadeIn 0.18s ease;
1087
1095
  }
1088
1096
  .log-entry.user {
1089
- align-self: flex-end;
1090
- align-items: flex-end;
1097
+ justify-content: flex-end;
1091
1098
  }
1092
1099
  .log-entry.agent,
1093
1100
  .log-entry.sys {
1094
- align-self: flex-start;
1095
- align-items: flex-start;
1101
+ justify-content: flex-start;
1102
+ }
1103
+
1104
+ /* User Message Bubble */
1105
+ .log-user-wrap {
1106
+ max-width: 82%;
1107
+ display: flex;
1108
+ flex-direction: column;
1109
+ align-items: flex-end;
1110
+ gap: 4px;
1096
1111
  }
1097
1112
  .log-user {
1098
- color: #fff;
1099
- font-weight: 500;
1100
- background: var(--brand-primary);
1101
- padding: 10px 12px;
1102
- border-radius: 12px 12px 4px 12px;
1113
+ color: #ffffff;
1114
+ font-weight: 450;
1115
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
1116
+ padding: 10px 15px;
1117
+ border-radius: 16px 16px 4px 16px;
1103
1118
  white-space: pre-wrap;
1104
1119
  word-break: break-word;
1105
- box-shadow: var(--shadow-xs);
1120
+ box-shadow: var(--shadow-sm);
1121
+ line-height: 1.55;
1122
+ }
1123
+
1124
+ /* Agent Message: Codex/Gemini open conversational layout */
1125
+ .agent-avatar {
1126
+ width: 32px;
1127
+ height: 32px;
1128
+ border-radius: 50%;
1129
+ background: linear-gradient(135deg, #0d766e 0%, #0284c7 100%);
1130
+ color: #ffffff;
1131
+ display: grid;
1132
+ place-items: center;
1133
+ font-weight: 700;
1134
+ font-size: 15px;
1135
+ flex-shrink: 0;
1136
+ box-shadow: 0 2px 4px rgba(13, 118, 110, 0.2);
1137
+ }
1138
+ .agent-content-wrap {
1139
+ flex: 1;
1140
+ min-width: 0;
1141
+ display: flex;
1142
+ flex-direction: column;
1143
+ gap: 5px;
1144
+ }
1145
+ .agent-meta {
1146
+ display: flex;
1147
+ align-items: center;
1148
+ gap: 8px;
1149
+ font-size: 12px;
1150
+ font-weight: 600;
1151
+ color: var(--text-secondary);
1152
+ }
1153
+ .agent-role-pill {
1154
+ font-size: 10px;
1155
+ font-weight: 550;
1156
+ background: var(--brand-primary-light);
1157
+ color: var(--brand-primary);
1158
+ border: 1px solid var(--brand-primary-border);
1159
+ padding: 0 5px;
1160
+ border-radius: 4px;
1106
1161
  }
1107
1162
  .log-agent {
1108
1163
  color: var(--text-main);
1109
1164
  background: var(--bg-surface);
1110
- padding: 10px 12px;
1111
- border-radius: 12px 12px 12px 4px;
1165
+ padding: 14px 18px;
1166
+ border-radius: 4px 16px 16px 16px;
1112
1167
  border: 1px solid var(--border-subtle);
1113
- white-space: pre-wrap;
1114
1168
  word-break: break-word;
1115
- box-shadow: var(--shadow-xs);
1169
+ box-shadow: var(--shadow-card);
1170
+ line-height: 1.65;
1171
+ }
1172
+ .log-agent > :first-child { margin-top: 0; }
1173
+ .log-agent > :last-child { margin-bottom: 0; }
1174
+ .log-agent p { margin: 8px 0; }
1175
+ .log-agent h1, .log-agent h2, .log-agent h3 {
1176
+ margin: 14px 0 6px;
1177
+ color: var(--text-main);
1178
+ font-weight: 650;
1179
+ }
1180
+ .log-agent h1 { font-size: 16px; }
1181
+ .log-agent h2 { font-size: 14.5px; }
1182
+ .log-agent h3 { font-size: 13.5px; }
1183
+ .log-agent ul, .log-agent ol {
1184
+ margin: 6px 0 6px 20px;
1185
+ padding: 0;
1186
+ }
1187
+ .log-agent li { margin: 3px 0; }
1188
+ .log-agent blockquote {
1189
+ border-left: 3px solid var(--brand-primary);
1190
+ background: var(--brand-primary-light);
1191
+ padding: 6px 12px;
1192
+ margin: 8px 0;
1193
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
1194
+ color: var(--text-secondary);
1195
+ }
1196
+ .log-agent table {
1197
+ width: 100%;
1198
+ border-collapse: collapse;
1199
+ margin: 10px 0;
1200
+ font-size: 12.5px;
1201
+ }
1202
+ .log-agent th, .log-agent td {
1203
+ padding: 6px 10px;
1204
+ border: 1px solid var(--border-subtle);
1205
+ text-align: left;
1206
+ }
1207
+ .log-agent th {
1208
+ background: var(--bg-surface-subtle);
1209
+ font-weight: 600;
1116
1210
  }
1211
+
1212
+ /* Code Blocks with Copy Header */
1213
+ .code-container {
1214
+ position: relative;
1215
+ margin: 10px 0;
1216
+ border-radius: var(--radius-md);
1217
+ overflow: hidden;
1218
+ background: #0f172a;
1219
+ border: 1px solid #1e293b;
1220
+ }
1221
+ .code-header {
1222
+ display: flex;
1223
+ align-items: center;
1224
+ justify-content: space-between;
1225
+ padding: 5px 12px;
1226
+ background: #1e293b;
1227
+ color: #94a3b8;
1228
+ font-size: 11px;
1229
+ font-family: var(--font-mono);
1230
+ user-select: none;
1231
+ }
1232
+ .code-copy-btn {
1233
+ display: inline-flex;
1234
+ align-items: center;
1235
+ gap: 4px;
1236
+ font-size: 11px;
1237
+ color: #cbd5e1;
1238
+ background: transparent;
1239
+ border: none;
1240
+ padding: 2px 6px;
1241
+ border-radius: 4px;
1242
+ cursor: pointer;
1243
+ transition: background 0.12s ease, color 0.12s ease;
1244
+ }
1245
+ .code-copy-btn:hover {
1246
+ background: #334155;
1247
+ color: #ffffff;
1248
+ }
1249
+ .log-agent pre {
1250
+ margin: 0;
1251
+ padding: 12px 14px;
1252
+ overflow-x: auto;
1253
+ font-family: var(--font-mono);
1254
+ font-size: 12px;
1255
+ line-height: 1.5;
1256
+ color: #f1f5f9;
1257
+ background: transparent;
1258
+ }
1259
+ .log-agent code {
1260
+ font-family: var(--font-mono);
1261
+ font-size: 12px;
1262
+ }
1263
+ .log-agent p code, .log-agent li code {
1264
+ background: var(--bg-surface-subtle);
1265
+ color: #0f766e;
1266
+ border: 1px solid var(--border-subtle);
1267
+ padding: 1px 5px;
1268
+ border-radius: 4px;
1269
+ }
1270
+
1271
+ /* Shimmering Pulsing Dots for Streaming */
1272
+ .agent-typing-indicator {
1273
+ display: inline-flex;
1274
+ align-items: center;
1275
+ gap: 5px;
1276
+ padding: 6px 12px;
1277
+ background: var(--bg-surface);
1278
+ border: 1px solid var(--border-subtle);
1279
+ border-radius: 12px;
1280
+ margin-top: 4px;
1281
+ width: fit-content;
1282
+ }
1283
+ .typing-dot {
1284
+ width: 6px;
1285
+ height: 6px;
1286
+ border-radius: 50%;
1287
+ background: var(--brand-primary);
1288
+ animation: pulseDot 1.4s infinite ease-in-out both;
1289
+ }
1290
+ .typing-dot:nth-child(1) { animation-delay: -0.32s; }
1291
+ .typing-dot:nth-child(2) { animation-delay: -0.16s; }
1292
+ .typing-dot:nth-child(3) { animation-delay: 0s; }
1293
+ @keyframes pulseDot {
1294
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.35; }
1295
+ 40% { transform: scale(1.1); opacity: 1; }
1296
+ }
1297
+
1298
+ /* System & Info Messages */
1117
1299
  .log-sys {
1118
1300
  color: var(--text-muted);
1119
- font-style: normal;
1120
1301
  font-size: 12px;
1121
- background: transparent;
1122
- padding: 2px 0;
1302
+ background: var(--bg-surface-subtle);
1303
+ border: 1px dashed var(--border-default);
1304
+ padding: 8px 12px;
1305
+ border-radius: var(--radius-sm);
1306
+ text-align: center;
1307
+ width: 100%;
1123
1308
  }
1124
- .console-input-box {
1125
- padding: 12px 14px;
1309
+ .wake-checklist-card {
1310
+ background: var(--bg-surface);
1311
+ border: 1px solid var(--brand-primary-border);
1312
+ border-left: 3px solid var(--brand-primary);
1313
+ border-radius: var(--radius-sm);
1314
+ padding: 10px 14px;
1315
+ text-align: left;
1316
+ }
1317
+
1318
+ /* Quick Action Chips */
1319
+ .console-quick-chips {
1320
+ display: flex;
1321
+ align-items: center;
1322
+ gap: 6px;
1323
+ padding: 8px 16px;
1324
+ background: var(--bg-surface);
1325
+ border-top: 1px solid var(--border-subtle);
1326
+ overflow-x: auto;
1327
+ max-width: 860px;
1328
+ margin: 0 auto;
1329
+ width: 100%;
1330
+ }
1331
+ .chip {
1332
+ font-family: var(--font-mono);
1333
+ font-size: 11px;
1334
+ background: var(--bg-surface-subtle);
1335
+ color: var(--text-secondary);
1336
+ border: 1px solid var(--border-subtle);
1337
+ padding: 3px 9px;
1338
+ border-radius: 999px;
1339
+ cursor: pointer;
1340
+ white-space: nowrap;
1341
+ transition: all 0.15s ease;
1342
+ display: inline-flex;
1343
+ align-items: center;
1344
+ gap: 4px;
1345
+ }
1346
+ .chip:hover {
1347
+ background: var(--brand-primary-light);
1348
+ border-color: var(--brand-primary-border);
1349
+ color: var(--brand-primary);
1350
+ }
1351
+
1352
+ /* Modern Textarea Input Box (Gemini / Codex Style) */
1353
+ .console-input-wrap {
1126
1354
  background: var(--bg-surface);
1127
1355
  border-top: 1px solid var(--border-subtle);
1356
+ padding: 12px 16px 14px;
1357
+ }
1358
+ .console-input-container {
1359
+ max-width: 860px;
1360
+ margin: 0 auto;
1128
1361
  display: flex;
1362
+ flex-direction: column;
1363
+ gap: 6px;
1364
+ }
1365
+ .console-input-box {
1366
+ background: var(--bg-surface-subtle);
1367
+ border: 1.5px solid var(--border-default);
1368
+ border-radius: var(--radius-lg);
1369
+ padding: 8px 12px;
1370
+ display: flex;
1371
+ align-items: flex-end;
1129
1372
  gap: 8px;
1373
+ transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
1130
1374
  }
1131
- .console-input {
1375
+ .console-input-box:focus-within {
1376
+ background: var(--bg-surface);
1377
+ border-color: var(--brand-primary);
1378
+ box-shadow: 0 0 0 3px var(--brand-primary-border);
1379
+ }
1380
+ .console-textarea {
1132
1381
  flex: 1;
1133
- background: var(--bg-surface-subtle);
1134
- border: 1px solid var(--border-default);
1135
- color: var(--text-main);
1136
- padding: 10px 12px;
1137
- font-size: 13px;
1138
- border-radius: var(--radius-md);
1382
+ background: transparent;
1383
+ border: none;
1139
1384
  outline: none;
1385
+ resize: none;
1140
1386
  font-family: var(--font-sans);
1387
+ font-size: 13.5px;
1388
+ line-height: 1.5;
1389
+ color: var(--text-main);
1390
+ min-height: 24px;
1391
+ max-height: 160px;
1392
+ padding: 4px 0;
1141
1393
  }
1142
- .console-input:focus {
1143
- border-color: var(--brand-primary);
1144
- background: var(--bg-surface);
1145
- box-shadow: 0 0 0 3px var(--brand-primary-border);
1394
+ .console-textarea::placeholder {
1395
+ color: var(--text-muted);
1146
1396
  }
1147
1397
  .console-send-btn {
1148
1398
  background: var(--brand-primary);
1149
- color: #fff;
1399
+ color: #ffffff;
1150
1400
  border: none;
1151
- padding: 8px 14px;
1401
+ width: 32px;
1402
+ height: 32px;
1152
1403
  border-radius: var(--radius-md);
1153
- font-size: 13px;
1154
- font-weight: 600;
1155
1404
  cursor: pointer;
1156
- display: flex;
1157
- align-items: center;
1158
- gap: 4px;
1405
+ display: grid;
1406
+ place-items: center;
1407
+ flex-shrink: 0;
1408
+ transition: background 0.15s ease, transform 0.1s ease;
1409
+ margin-bottom: 2px;
1159
1410
  }
1160
- .console-send-btn:hover {
1411
+ .console-send-btn:hover:not(:disabled) {
1161
1412
  background: var(--brand-primary-hover);
1413
+ transform: translateY(-1px);
1162
1414
  }
1163
- .log-agent.streaming::after {
1164
- content: "▍";
1165
- animation: blink 1s steps(1) infinite;
1166
- color: var(--brand-primary);
1167
- margin-left: 2px;
1415
+ .console-send-btn:disabled {
1416
+ opacity: 0.45;
1417
+ cursor: not-allowed;
1418
+ }
1419
+ .console-input-footer {
1420
+ display: flex;
1421
+ align-items: center;
1422
+ justify-content: space-between;
1423
+ padding: 0 4px;
1424
+ font-size: 11px;
1425
+ color: var(--text-muted);
1168
1426
  }
1169
- @keyframes blink { 50% { opacity: 0; } }
1170
1427
  .wake-option-list {
1171
1428
  display: grid;
1172
1429
  gap: 8px;
@@ -1280,7 +1537,6 @@
1280
1537
  .viewer { min-height: 480px; }
1281
1538
  .provider-grid, .cost-metrics { grid-template-columns: 1fr; }
1282
1539
  .topbar { padding: 0 14px; }
1283
- .console-drawer { width: 100vw; right: 0; }
1284
1540
  }
1285
1541
  </style>
1286
1542
  </head>
@@ -1320,32 +1576,37 @@
1320
1576
  <!-- 主体工作区 Layout -->
1321
1577
  <div class="layout">
1322
1578
  <!-- 侧边导航栏 Sidebar -->
1323
- <nav class="nav" aria-label="Panel 导航">
1579
+ <nav class="nav" id="workbench-nav" aria-label="Panel 导航">
1324
1580
  <div class="nav-group">
1325
- <div class="nav-label">工作台导航</div>
1326
- <button class="active" data-view="project">
1581
+ <div class="nav-header">
1582
+ <div class="nav-label">工作台导航</div>
1583
+ <button id="nav-toggle-btn" class="nav-toggle-btn" title="折叠/展开导航栏" aria-label="折叠导航栏">
1584
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
1585
+ </button>
1586
+ </div>
1587
+ <button class="active" data-view="project" title="项目文件">
1327
1588
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
1328
- 项目文件
1589
+ <span class="nav-btn-text">项目文件</span>
1329
1590
  </button>
1330
- <button data-view="ecosystem">
1591
+ <button data-view="ecosystem" title="能力生态">
1331
1592
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
1332
- 能力生态
1593
+ <span class="nav-btn-text">能力生态</span>
1333
1594
  </button>
1334
- <button data-view="provider">
1595
+ <button data-view="provider" title="模型与成本">
1335
1596
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
1336
- 模型与成本
1597
+ <span class="nav-btn-text">模型与成本</span>
1337
1598
  </button>
1338
- <button data-view="help">
1599
+ <button data-view="help" title="使用速览">
1339
1600
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
1340
- 使用速览
1601
+ <span class="nav-btn-text">使用速览</span>
1341
1602
  </button>
1342
- <button data-view="checklist">
1603
+ <button data-view="checklist" title="核对清单">
1343
1604
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
1344
- 核对清单
1605
+ <span class="nav-btn-text">核对清单</span>
1345
1606
  </button>
1346
- <button data-view="chat">
1607
+ <button data-view="chat" title="对话">
1347
1608
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1348
- 对话
1609
+ <span class="nav-btn-text">对话</span>
1349
1610
  </button>
1350
1611
  </div>
1351
1612
  <div class="nav-footer">
@@ -1560,7 +1821,7 @@
1560
1821
  <ol id="help-steps" class="help-steps"></ol>
1561
1822
  </div>
1562
1823
  <div class="help-block">
1563
- <h2>常用命令</h2>
1824
+ <h2>可用命令</h2>
1564
1825
  <div id="help-commands" class="help-commands"></div>
1565
1826
  </div>
1566
1827
  <div class="help-block">
@@ -1593,62 +1854,79 @@
1593
1854
  </section>
1594
1855
 
1595
1856
  <section id="view-chat" class="view">
1596
- <div class="page-head">
1857
+ <div class="page-head" style="margin-bottom: 12px;">
1597
1858
  <div>
1598
1859
  <h1>交互对话</h1>
1599
- <p>提问会转入当前 PsyClaw 会话;助手回复经 SSE 同步到此处。模型可调用「唤醒选项」弹窗。</p>
1860
+ <p>提问将接入当前 PsyClaw 会话;助手回复经 SSE 流式同步,支持学术指令与「唤醒选项」决策。</p>
1600
1861
  </div>
1601
1862
  </div>
1602
- <div id="chat-console-host" class="panel section" style="min-height:62vh;padding:0;overflow:hidden;display:flex;flex-direction:column"></div>
1603
- </section>
1604
- </main>
1605
- </div>
1606
- </div>
1607
-
1608
- <!-- 悬浮对话入口 -->
1609
- <button id="console-fab" class="console-fab" title="打开/折叠对话">
1610
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1611
- <span>对话</span>
1612
- </button>
1613
-
1614
- <!-- 抽屉式对话 -->
1615
- <div id="console-drawer" class="console-drawer">
1616
- <div class="console-header">
1617
- <div class="console-title">
1618
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--brand-primary)"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1619
- <span>对话</span>
1620
- <span id="console-status-badge" class="console-status-tag">已连接</span>
1621
- </div>
1622
- <div class="console-actions">
1623
- <button id="console-clear-btn" class="console-btn-icon" title="清空对话">
1624
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
1625
- </button>
1626
- <button id="console-close-btn" class="console-btn-icon" title="最小化">
1627
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
1628
- </button>
1629
- </div>
1630
- </div>
1863
+ <div id="chat-console-host" class="panel section" style="min-height:74vh;padding:0;overflow:hidden;display:flex;flex-direction:column;border-radius:var(--radius-md);">
1864
+ <div id="console-drawer" class="console-drawer">
1865
+ <div class="console-header">
1866
+ <div class="console-title">
1867
+ <span class="agent-avatar" style="width:24px;height:24px;font-size:12px">ψ</span>
1868
+ <span>PsyClaw Research Assistant</span>
1869
+ <span id="console-status-badge" class="console-status-tag">
1870
+ <span class="pulse-dot" style="width:5px;height:5px;"></span>
1871
+ 已连接
1872
+ </span>
1873
+ </div>
1874
+ <div class="console-actions">
1875
+ <button id="console-clear-btn" class="console-btn-icon" title="清空对话记录">
1876
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
1877
+ </button>
1878
+ </div>
1879
+ </div>
1631
1880
 
1632
- <div class="console-quick-chips">
1633
- <span class="chip" data-cmd="/plan status">/plan status</span>
1634
- <span class="chip" data-cmd="/verify list">/verify list</span>
1635
- <span class="chip" data-cmd="/grill">/grill 压力追问</span>
1636
- <span class="chip" data-cmd="/review">/review 同行评审</span>
1637
- <span class="chip" data-cmd="/agents">/agents 角色</span>
1638
- </div>
1881
+ <div id="console-log" class="console-log">
1882
+ <div class="log-entry sys">
1883
+ <div class="log-sys">
1884
+ 💡 <strong>对话已就绪</strong>:与 PsyClaw 智能体及底层学术插件安全连接。支持 <code>/init</code>、<code>/plan</code>、<code>/grill</code>、<code>/verify</code> 等研究指令;随时探讨研究设想或分析数据。
1885
+ </div>
1886
+ </div>
1887
+ </div>
1639
1888
 
1640
- <div id="console-log" class="console-log">
1641
- <div class="log-entry sys">
1642
- <div class="log-sys">对话已就绪(SSE)。提问转入当前会话,助手回复会流式同步到此处。支持 /init、/plan、/grill 等指令;模型可弹出「唤醒选项」。</div>
1643
- </div>
1644
- </div>
1889
+ <div class="console-quick-chips">
1890
+ <span class="chip" data-cmd="/plan status">
1891
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
1892
+ /plan status
1893
+ </span>
1894
+ <span class="chip" data-cmd="/verify list">
1895
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/></svg>
1896
+ /verify list
1897
+ </span>
1898
+ <span class="chip" data-cmd="/grill">
1899
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/></svg>
1900
+ /grill 压力追问
1901
+ </span>
1902
+ <span class="chip" data-cmd="/review">
1903
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/></svg>
1904
+ /review 评审
1905
+ </span>
1906
+ <span class="chip" data-cmd="/agents">
1907
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
1908
+ /agents 角色
1909
+ </span>
1910
+ </div>
1645
1911
 
1646
- <div class="console-input-box">
1647
- <input type="text" id="console-input" class="console-input" placeholder="输入研究问题或指令,Enter 发送…" autocomplete="off" />
1648
- <button id="console-send-btn" class="console-send-btn">
1649
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
1650
- 发送
1651
- </button>
1912
+ <div class="console-input-wrap">
1913
+ <div class="console-input-container">
1914
+ <div class="console-input-box">
1915
+ <textarea id="console-input" class="console-textarea" placeholder="输入研究问题或指令(Enter 发送,Shift+Enter 换行)…" rows="1"></textarea>
1916
+ <button id="console-send-btn" class="console-send-btn" title="发送消息 (Enter)">
1917
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
1918
+ </button>
1919
+ </div>
1920
+ <div class="console-input-footer">
1921
+ <span>支持 Markdown 与学术指令</span>
1922
+ <span>PsyClaw 本地隔离环境</span>
1923
+ </div>
1924
+ </div>
1925
+ </div>
1926
+ </div>
1927
+ </div>
1928
+ </section>
1929
+ </main>
1652
1930
  </div>
1653
1931
  </div>
1654
1932
 
@@ -1750,8 +2028,7 @@ function setView(view) {
1750
2028
  if (view === "provider") loadProvider();
1751
2029
  if (view === "checklist") loadChecklist();
1752
2030
  if (view === "help") loadHelp();
1753
- if (view === "chat") mountConsoleInChat();
1754
- else restoreConsoleDrawer();
2031
+ if (view === "chat") queueMicrotask(() => $("console-input")?.focus());
1755
2032
  }
1756
2033
 
1757
2034
  async function loadHelp() {
@@ -1775,37 +2052,6 @@ async function loadHelp() {
1775
2052
  }
1776
2053
  }
1777
2054
 
1778
- function mountConsoleInChat() {
1779
- const host = $("chat-console-host");
1780
- const drawer = $("console-drawer");
1781
- if (!host || !drawer) return;
1782
- host.appendChild(drawer);
1783
- drawer.classList.add("open", "embedded");
1784
- drawer.style.position = "relative";
1785
- drawer.style.inset = "auto";
1786
- drawer.style.width = "100%";
1787
- drawer.style.height = "100%";
1788
- drawer.style.minHeight = "58vh";
1789
- drawer.style.boxShadow = "none";
1790
- drawer.style.borderRadius = "0";
1791
- drawer.style.transform = "none";
1792
- }
1793
-
1794
- function restoreConsoleDrawer() {
1795
- const drawer = $("console-drawer");
1796
- if (!drawer || drawer.parentElement === document.body) return;
1797
- document.body.appendChild(drawer);
1798
- drawer.classList.remove("embedded");
1799
- drawer.style.position = "";
1800
- drawer.style.inset = "";
1801
- drawer.style.width = "";
1802
- drawer.style.height = "";
1803
- drawer.style.minHeight = "";
1804
- drawer.style.boxShadow = "";
1805
- drawer.style.borderRadius = "";
1806
- drawer.style.transform = "";
1807
- }
1808
-
1809
2055
  async function loadChecklist() {
1810
2056
  const body = $("checklist-body");
1811
2057
  if (!body) return;
@@ -1854,7 +2100,7 @@ $("skip-checklist")?.addEventListener("click", async () => {
1854
2100
  await loadChecklist();
1855
2101
  });
1856
2102
 
1857
- document.querySelectorAll(".nav button").forEach(button => {
2103
+ document.querySelectorAll(".nav button[data-view]").forEach(button => {
1858
2104
  button.addEventListener("click", () => setView(button.dataset.view));
1859
2105
  });
1860
2106
 
@@ -1873,16 +2119,20 @@ function inlineMarkdown(text) {
1873
2119
 
1874
2120
  function renderMarkdown(markdown) {
1875
2121
  const lines = String(markdown).replace(/\r\n/g, "\n").split("\n"), out = [];
1876
- let i = 0, inCode = false, code = [];
2122
+ let i = 0, inCode = false, codeLang = "", code = [];
1877
2123
  while (i < lines.length) {
1878
2124
  const line = lines[i], trim = line.trim();
1879
2125
  if (trim.startsWith("```")) {
1880
2126
  if (inCode) {
1881
- out.push(`<pre><code>${esc(code.join("\n"))}</code></pre>`);
2127
+ const rawCode = code.join("\n");
2128
+ const langDisplay = codeLang || "code";
2129
+ out.push(`<div class="code-container"><div class="code-header"><span>${esc(langDisplay)}</span><button type="button" class="code-copy-btn" data-code="${esc(rawCode)}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span>复制</span></button></div><pre><code>${esc(rawCode)}</code></pre></div>`);
1882
2130
  code = [];
2131
+ codeLang = "";
1883
2132
  inCode = false;
1884
2133
  } else {
1885
2134
  inCode = true;
2135
+ codeLang = trim.slice(3).trim();
1886
2136
  }
1887
2137
  i++;
1888
2138
  continue;
@@ -1942,7 +2192,11 @@ function renderMarkdown(markdown) {
1942
2192
  }
1943
2193
  out.push(`<p>${inlineMarkdown(para.join(" "))}</p>`);
1944
2194
  }
1945
- if (inCode) out.push(`<pre><code>${esc(code.join("\n"))}</code></pre>`);
2195
+ if (inCode) {
2196
+ const rawCode = code.join("\n");
2197
+ const langDisplay = codeLang || "code";
2198
+ out.push(`<div class="code-container"><div class="code-header"><span>${esc(langDisplay)}</span><button type="button" class="code-copy-btn" data-code="${esc(rawCode)}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span>复制</span></button></div><pre><code>${esc(rawCode)}</code></pre></div>`);
2199
+ }
1946
2200
  return out.join("");
1947
2201
  }
1948
2202
 
@@ -2343,8 +2597,6 @@ document.querySelectorAll(".step-item").forEach(item => {
2343
2597
  });
2344
2598
 
2345
2599
  /* --- 交互对话 + SSE + 唤醒选项 --- */
2346
- const consoleDrawer = $("console-drawer");
2347
- const consoleFab = $("console-fab");
2348
2600
  const consoleLog = $("console-log");
2349
2601
  const consoleInput = $("console-input");
2350
2602
  const streamBubbles = new Map();
@@ -2352,12 +2604,8 @@ let lastLocalUserText = "";
2352
2604
  let wakeState = null;
2353
2605
 
2354
2606
  function openConsole() {
2355
- consoleDrawer.classList.add("open");
2356
- consoleInput.focus();
2357
- }
2358
-
2359
- function closeConsole() {
2360
- consoleDrawer.classList.remove("open");
2607
+ setView("chat");
2608
+ queueMicrotask(() => consoleInput?.focus());
2361
2609
  }
2362
2610
 
2363
2611
  function openConsoleWithMessage(text) {
@@ -2369,9 +2617,22 @@ function appendLog(role, text) {
2369
2617
  const entry = document.createElement("div");
2370
2618
  entry.className = `log-entry ${role}`;
2371
2619
  if (role === "user") {
2372
- entry.innerHTML = `<div class="log-user">${esc(text)}</div>`;
2620
+ entry.innerHTML = `
2621
+ <div class="log-user-wrap">
2622
+ <div class="log-user">${esc(text)}</div>
2623
+ </div>
2624
+ `;
2373
2625
  } else if (role === "agent") {
2374
- entry.innerHTML = `<div class="log-agent">${renderMarkdown(text)}</div>`;
2626
+ entry.innerHTML = `
2627
+ <div class="agent-avatar">ψ</div>
2628
+ <div class="agent-content-wrap">
2629
+ <div class="agent-meta">
2630
+ <span>PsyClaw Assistant</span>
2631
+ <span class="agent-role-pill">Core</span>
2632
+ </div>
2633
+ <div class="log-agent">${renderMarkdown(text)}</div>
2634
+ </div>
2635
+ `;
2375
2636
  } else {
2376
2637
  entry.innerHTML = `<div class="log-sys">${esc(text)}</div>`;
2377
2638
  }
@@ -2386,7 +2647,21 @@ function ensureStreamBubble(messageId) {
2386
2647
  entry = document.createElement("div");
2387
2648
  entry.className = "log-entry agent";
2388
2649
  entry.dataset.messageId = messageId;
2389
- entry.innerHTML = `<div class="log-agent streaming"></div>`;
2650
+ entry.innerHTML = `
2651
+ <div class="agent-avatar">ψ</div>
2652
+ <div class="agent-content-wrap">
2653
+ <div class="agent-meta">
2654
+ <span>PsyClaw Assistant</span>
2655
+ <span class="agent-role-pill">Streaming</span>
2656
+ </div>
2657
+ <div class="log-agent" style="display:none"></div>
2658
+ <div class="agent-typing-indicator">
2659
+ <span class="typing-dot"></span>
2660
+ <span class="typing-dot"></span>
2661
+ <span class="typing-dot"></span>
2662
+ </div>
2663
+ </div>
2664
+ `;
2390
2665
  consoleLog.appendChild(entry);
2391
2666
  streamBubbles.set(messageId, entry);
2392
2667
  consoleLog.scrollTop = consoleLog.scrollHeight;
@@ -2396,9 +2671,24 @@ function ensureStreamBubble(messageId) {
2396
2671
  function updateStreamBubble(messageId, text, done) {
2397
2672
  const entry = ensureStreamBubble(messageId);
2398
2673
  const bubble = entry.querySelector(".log-agent");
2674
+ const indicator = entry.querySelector(".agent-typing-indicator");
2675
+ const pill = entry.querySelector(".agent-role-pill");
2399
2676
  if (!bubble) return;
2400
- bubble.classList.toggle("streaming", !done);
2401
- bubble.innerHTML = renderMarkdown(text || (done ? "" : "…"));
2677
+
2678
+ if (text) {
2679
+ bubble.style.display = "block";
2680
+ bubble.innerHTML = renderMarkdown(text);
2681
+ }
2682
+
2683
+ if (done) {
2684
+ if (indicator) indicator.remove();
2685
+ if (pill) pill.textContent = "Answered";
2686
+ bubble.style.display = "block";
2687
+ } else {
2688
+ if (text && indicator) {
2689
+ indicator.style.display = "none";
2690
+ }
2691
+ }
2402
2692
  consoleLog.scrollTop = consoleLog.scrollHeight;
2403
2693
  }
2404
2694
 
@@ -2450,7 +2740,10 @@ function connectAssistantStream() {
2450
2740
  es.addEventListener("wake_options", (ev) => {
2451
2741
  try {
2452
2742
  const data = JSON.parse(ev.data);
2453
- if (data.prompt) showWakeModal(data.prompt);
2743
+ if (data.prompt) {
2744
+ appendWakeChecklist(data.prompt);
2745
+ showWakeModal(data.prompt, { switchChecklist: false });
2746
+ }
2454
2747
  } catch { /* ignore */ }
2455
2748
  });
2456
2749
  es.addEventListener("wake_options_resolved", (ev) => {
@@ -2466,8 +2759,32 @@ function connectAssistantStream() {
2466
2759
  es.onerror = () => setConsoleStatus("重连中", false);
2467
2760
  }
2468
2761
 
2469
- function showWakeModal(prompt, { switchChecklist = true } = {}) {
2470
- const firstOpen = !wakeState || wakeState.id !== prompt.id;
2762
+ function appendWakeChecklist(prompt) {
2763
+ if (!prompt || prompt.mode !== "checklist") return;
2764
+ const existing = document.querySelector(`[data-wake-checklist-id="${CSS.escape(prompt.id)}"]`);
2765
+ if (existing) return;
2766
+ const entry = document.createElement("div");
2767
+ entry.className = "log-entry sys";
2768
+ entry.dataset.wakeChecklistId = prompt.id;
2769
+ const items = (prompt.options || []).map(option => `
2770
+ <li data-option-id="${esc(option.id)}"><span class="wake-checklist-mark">${option.checked ? "[x]" : "[ ]"}</span> <span>${esc(option.label)}</span>${option.description ? `<small>${esc(option.description)}</small>` : ""}</li>
2771
+ `).join("");
2772
+ entry.innerHTML = `<div class="log-sys wake-checklist-card"><strong>${esc(prompt.title || "交叉核验清单")}</strong>${prompt.prompt ? `<p>${esc(prompt.prompt)}</p>` : ""}<ul>${items}</ul><span class="wake-checklist-hint">请在当前对话中的弹窗完成勾选。</span></div>`;
2773
+ consoleLog.appendChild(entry);
2774
+ consoleLog.scrollTop = consoleLog.scrollHeight;
2775
+ }
2776
+
2777
+ function updateWakeChecklist(promptId, selectedIds) {
2778
+ const entry = document.querySelector(`[data-wake-checklist-id="${CSS.escape(promptId)}"]`);
2779
+ if (!entry) return;
2780
+ const selected = new Set(selectedIds || []);
2781
+ entry.querySelectorAll("li[data-option-id]").forEach(item => {
2782
+ const mark = item.querySelector(".wake-checklist-mark");
2783
+ if (mark) mark.textContent = selected.has(item.dataset.optionId) ? "[x]" : "[ ]";
2784
+ });
2785
+ }
2786
+
2787
+ function showWakeModal(prompt) {
2471
2788
  wakeState = {
2472
2789
  id: prompt.id,
2473
2790
  mode: prompt.mode,
@@ -2502,13 +2819,12 @@ function showWakeModal(prompt, { switchChecklist = true } = {}) {
2502
2819
  } else {
2503
2820
  wakeState.selected.add(option.id);
2504
2821
  }
2505
- showWakeModal(prompt, { switchChecklist: false });
2822
+ showWakeModal(prompt);
2506
2823
  });
2507
2824
  list.appendChild(btn);
2508
2825
  }
2509
2826
  $("wake-modal").classList.remove("hidden");
2510
2827
  openConsole();
2511
- if (firstOpen && switchChecklist && prompt.mode === "checklist") setView("checklist");
2512
2828
  }
2513
2829
 
2514
2830
  function hideWakeModal() {
@@ -2527,8 +2843,8 @@ async function submitWake(cancel = false) {
2527
2843
  body: JSON.stringify({ promptId, selectedIds, cancel: Boolean(cancel) }),
2528
2844
  });
2529
2845
  hideWakeModal();
2846
+ updateWakeChecklist(promptId, selectedIds);
2530
2847
  appendLog("sys", cancel ? "已取消唤醒选项" : `已提交选择:${selectedIds.join(", ") || "(空)"}`);
2531
- await loadChecklist().catch(() => {});
2532
2848
  } catch (err) {
2533
2849
  appendLog("sys", `[唤醒选项] ${err.message}`);
2534
2850
  }
@@ -2538,23 +2854,55 @@ $("wake-modal-close")?.addEventListener("click", () => submitWake(true));
2538
2854
  $("wake-cancel-btn")?.addEventListener("click", () => submitWake(true));
2539
2855
  $("wake-submit-btn")?.addEventListener("click", () => submitWake(false));
2540
2856
 
2541
- consoleFab.addEventListener("click", () => {
2542
- if (consoleDrawer.classList.contains("open")) closeConsole();
2543
- else openConsole();
2544
- });
2545
- $("console-close-btn").addEventListener("click", closeConsole);
2546
2857
  $("console-clear-btn").addEventListener("click", () => {
2547
2858
  consoleLog.innerHTML = `<div class="log-entry sys"><div class="log-sys">对话已清空。输入问题或 /help 即可继续。</div></div>`;
2548
2859
  streamBubbles.clear();
2549
2860
  });
2550
2861
 
2862
+ // 自适应高度调整函数
2863
+ function autoResizeConsoleInput() {
2864
+ if (!consoleInput) return;
2865
+ consoleInput.style.height = "auto";
2866
+ const nextHeight = Math.min(Math.max(consoleInput.scrollHeight, 24), 160);
2867
+ consoleInput.style.height = `${nextHeight}px`;
2868
+ }
2869
+
2870
+ consoleInput.addEventListener("input", autoResizeConsoleInput);
2871
+
2872
+ // 代码块全局点击复制事件代理
2873
+ document.addEventListener("click", async (e) => {
2874
+ const btn = e.target.closest(".code-copy-btn");
2875
+ if (!btn) return;
2876
+ const rawCode = btn.dataset.code;
2877
+ if (!rawCode) return;
2878
+ try {
2879
+ await navigator.clipboard.writeText(rawCode);
2880
+ const textSpan = btn.querySelector("span");
2881
+ if (textSpan) {
2882
+ const originalText = textSpan.textContent;
2883
+ textSpan.textContent = "已复制";
2884
+ btn.style.color = "var(--ok-text)";
2885
+ setTimeout(() => {
2886
+ textSpan.textContent = originalText;
2887
+ btn.style.color = "";
2888
+ }, 1800);
2889
+ }
2890
+ } catch {
2891
+ alert("无法写入剪贴板,请手动选中文本复制。");
2892
+ }
2893
+ });
2894
+
2551
2895
  async function sendConsoleMessage(msg) {
2552
2896
  const text = (msg || consoleInput.value).trim();
2553
2897
  if (!text) return;
2554
2898
  consoleInput.value = "";
2899
+ autoResizeConsoleInput();
2555
2900
  lastLocalUserText = text;
2556
2901
  appendLog("user", text);
2557
2902
 
2903
+ const sendBtn = $("console-send-btn");
2904
+ if (sendBtn) sendBtn.disabled = true;
2905
+
2558
2906
  try {
2559
2907
  const res = await json("/api/assistant", {
2560
2908
  method: "POST",
@@ -2565,6 +2913,8 @@ async function sendConsoleMessage(msg) {
2565
2913
  if (res.text) appendLog("sys", res.text);
2566
2914
  } catch (err) {
2567
2915
  appendLog("sys", `[提示] ${err.message}`);
2916
+ } finally {
2917
+ if (sendBtn) sendBtn.disabled = false;
2568
2918
  }
2569
2919
  }
2570
2920
 
@@ -2583,11 +2933,31 @@ document.querySelectorAll(".chip").forEach(chip => {
2583
2933
  const cmd = chip.dataset.cmd;
2584
2934
  if (cmd) {
2585
2935
  consoleInput.value = cmd;
2936
+ autoResizeConsoleInput();
2586
2937
  sendConsoleMessage(cmd);
2587
2938
  }
2588
2939
  });
2589
2940
  });
2590
2941
 
2942
+ /* --- 侧边栏折叠与展开交互 --- */
2943
+ const layoutEl = document.querySelector(".layout");
2944
+ const navToggleBtn = $("nav-toggle-btn");
2945
+
2946
+ function initNavCollapse() {
2947
+ const saved = localStorage.getItem("psyclaw_nav_collapsed");
2948
+ if (saved === "true") {
2949
+ layoutEl?.classList.add("nav-collapsed");
2950
+ }
2951
+ }
2952
+
2953
+ navToggleBtn?.addEventListener("click", () => {
2954
+ if (!layoutEl) return;
2955
+ const isCollapsed = layoutEl.classList.toggle("nav-collapsed");
2956
+ localStorage.setItem("psyclaw_nav_collapsed", isCollapsed ? "true" : "false");
2957
+ });
2958
+
2959
+ initNavCollapse();
2960
+
2591
2961
  /* --- 顶栏快捷命令 Quick Actions --- */
2592
2962
  const initModal = $("init-modal");
2593
2963
  const initParadigm = $("init-paradigm");