fix(terminal): WebSSH xterm SSH 交互区 ANSI 彩色输出

PTY 环境变量注入 TERM=xterm-256color,DATA 帧 base64 保 ESC;前端解码写入 xterm;命令栏 Shell 高亮 CSS 与黑屏分离。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Agent
2026-06-08 12:35:27 +08:00
parent 1d1fb6285b
commit 8312a6cf81
11 changed files with 457 additions and 52 deletions
+210
View File
@@ -606,3 +606,213 @@
{"ts":"2026-06-07T23:10:50+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T23:10:50+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T23:10:50+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-servers-live-sort-fix.md 28lines"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-servers-live-sort-fix.md"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T07:38:32+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-servers-live-sort-fix.md 28lines"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-servers-live-sort-fix.md"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T07:54:01+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-login-gate-seamless-nav.md 42lines"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-login-gate-seamless-nav.md"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T07:54:43+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-login-gate-seamless-nav.md 42lines"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-login-gate-seamless-nav.md"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T07:54:52+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-credentials-merge-servers-page.md 28lines"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-servers-batch-execution-records-credentials.md"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T09:31:41+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-credentials-merge-servers-page.md 28lines"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-servers-batch-execution-records-credentials.md"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T09:32:26+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-picker.md 42lines"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-schedule-cycle-picker.md"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T09:40:48+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-picker.md 42lines"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-schedule-cycle-picker.md"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T09:45:05+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-time-merge.md 26lines"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-gate7-diff-fix.md"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T10:30:55+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-time-merge.md 26lines"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-gate7-diff-fix.md"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T10:31:58+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-time-merge.md 26lines"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-schedule-form-deploy-batch.md"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T10:32:55+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-time-merge.md 26lines"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-schedule-form-deploy-batch.md"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:15:19+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-day-label-once-ui.md 26lines"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-schedule-form-deploy-batch.md"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:48:30+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-schedule-cycle-day-label-once-ui.md 26lines"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-schedule-cycle-day-label-once-ui.md"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:49:26+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-script-exec-results-mediumtext.md 31lines"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"test","result":"BLOCK","detail":"tests failed"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:56:37+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"4/7"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-script-exec-results-mediumtext.md 31lines"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:56:55+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-script-exec-results-mediumtext.md 31lines"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:57:05+08:00","date":"2026-06-08","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-script-exec-results-mediumtext.md 31lines"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-script-exec-results-mediumtext.md"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T11:57:20+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-webssh-classic-terminal-theme.md 24lines"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-webssh-classic-terminal-theme.md"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T12:09:00+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-terminal-quickbar-disconnect.md 18lines"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-terminal-quickbar-disconnect.md"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T12:14:47+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"changelog","result":"PASS","detail":"2026-06-08-webssh-xterm-ansi-output-fix.md 35lines"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"audit","result":"PASS","detail":"2026-06-08-terminal-quickbar-disconnect.md"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-08T12:33:55+08:00","date":"2026-06-08","gate":"summary","result":"PASS","detail":"7/7"}
@@ -0,0 +1,35 @@
# 审计 — WebSSH xterm SSH 交互区 ANSI 彩色输出(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-webssh-xterm-ansi-output-fix.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| WebSocket relay | `server/api/webssh.py` |
| 前端解码 | `frontend/src/utils/terminalPayload.ts` |
| 会话 | `frontend/src/composables/terminal/useTerminalSessions.ts` |
| 样式分离 | `frontend/src/pages/TerminalPage.vue`, `frontend/src/components/terminal/TerminalCmdBar.vue` |
| 单测 | `tests/test_webssh_terminal_payload.py` |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | ANSI 在 xterm 输出区,非命令栏冒充 | PASS — PTY env + data_b64 + CSS 分离 |
| H2 | 无静默吞错 | PASS — relay 异常仍走现有 teardown |
| H3 | 向后兼容 | PASS — 前端仍可读 legacy `data` 字段 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] pytest 3/3
- [x] local_verify 26/26
- [x] frontend build
- [x] pre_deploy_check 7/7
- [ ] 生产浏览器 xterm 内 printf/ls(部署后终验)
@@ -0,0 +1,35 @@
# 2026-06-08 — WebSSH xterm SSH 交互区 ANSI 彩色输出修正
## 摘要
修正 835ef2c 偏差:ANSI 彩色应出现在**上方 xterm SSH 输出区**,而非底部命令栏语法高亮。后端 PTY 环境变量 + base64 DATA;前端解码写入 xtermShell 高亮 CSS 移至命令栏组件。
## 动机
用户反馈:WebSSH 对话框/命令栏有颜色,但 SSH 交互黑屏区仍无远程 ANSI 彩色(`ls --color` 等)。
## 涉及文件
- `server/api/webssh.py``create_process(env=...)``data_b64`
- `frontend/src/composables/terminal/useTerminalSessions.ts` — 解码 `data_b64`
- `frontend/src/pages/TerminalPage.vue` — 移除 `.sh-*` 样式
- `frontend/src/components/terminal/TerminalCmdBar.vue` — 承接 `.sh-*` 样式
- `docs/design/specs/2026-06-08-webssh-xterm-ansi-output-fix-design.md`
## 迁移 / 重启
- 后端需重启 APIWebSocket relay 变更)
- 前端需 build + 部署;已有会话需**重连**以应用新 DATA 格式
## 验证
```bash
pytest tests/test_webssh_terminal_payload.py -q
cd frontend && npm run build
```
浏览器(xterm 黑屏内):`printf '\033[31mred\033[0m\n'``ls --color=auto`
## 回滚
恢复 stdin `export TERM=...`;前端仅用 `msg.data`;去掉 `data_b64`
@@ -0,0 +1,30 @@
# 设计 — WebSSH xterm SSH 交互区 ANSI 彩色输出修正
**日期**: 2026-06-08
**状态**: 已实施
**关联**: `835ef2c` WebSSH 经典主题(误把命令栏语法高亮当作 ANSI 修复)
## 背景与目标
用户要求:**上方 xterm 黑屏**(SSH 远程输出)黑底白字且带 ANSI 字符颜色;不是底部命令栏 Shell 语法高亮,也不是选择服务器对话框。
## 方案
| 层 | 变更 |
|----|------|
| 后端 `webssh.py` | `create_process(env=...)` 注入 `TERM=xterm-256color` 等;DATA 用 `data_b64` 二进制安全传输 |
| 前端 `useTerminalSessions.ts` | 优先解码 `data_b64` 写入 xterm |
| 前端样式 | `.sh-*``TerminalPage` 移至 `TerminalCmdBar` scoped |
## 验收
在 xterm 黑屏内:
1. `printf '\033[31mred \033[32mgreen \033[0m\n'` → 红/绿字
2. `ls --color=auto` → 目录/文件分色
3. 底部命令栏 Enter 发送后,彩色输出仍出现在黑屏区
## 安全与性能
- base64 仅用于 WebSocket 帧内转义,不改变鉴权
- 仍 4096 字节分块 read,延迟特性不变
@@ -0,0 +1,43 @@
# WebSSH xterm ANSI 彩色输出 — 验证报告
**日期**: 2026-06-08
**变更**: `docs/changelog/2026-06-08-webssh-xterm-ansi-output-fix.md`
## 自动化
| 检查 | 结果 |
|------|------|
| `.venv/bin/python -m pytest tests/test_webssh_terminal_payload.py -q` | 3/3 PASS |
| `bash scripts/local_verify.sh` | 26/26 PASS |
| `cd frontend && npm run build` | PASS |
| `bash deploy/pre_deploy_check.sh` | 7/7 PASS |
## 代码要点
1. **后端** `create_process(env=WEBSSH_PTY_ENV)` — PTY 级 `TERM=xterm-256color`,不再依赖 stdin export 竞态
2. **后端** DATA 帧使用 `data_b64`,保留 ANSI ESC 字节
3. **前端** `decodeTerminalPayload()``term.write(Uint8Array)`
4. **样式** Shell 语法高亮 `.sh-*` 仅在 `TerminalCmdBar.vue`,与 xterm 黑屏分离
## 浏览器验收步骤(生产/本地均需 **重连 SSH 会话**)
1. 打开 `#/terminal`,新建或重连一台服务器
2. 在**上方黑屏 xterm**(非底部命令栏)执行:
```bash
printf '\033[31mred \033[32mgreen \033[33myellow \033[0m\n'
```
预期:红 / 绿 / 黄 字
3. 执行 `ls --color=auto` 或 `grep --color=always root /etc/passwd | head`
预期:目录、匹配词分色
4. 在底部命令栏输入 `ls` + Enter
预期:输出出现在黑屏区且带颜色
## 部署
- 后端 WebSocket relay 变更 → **需重启 API**
- 前端 → `vite build` 已产出 `web/app/`
- 待用户批准后执行 `bash deploy/deploy-production.sh`
## 回滚
恢复 `webssh.py` stdin export + `data` 字段;前端移除 `data_b64` 解码。
@@ -85,4 +85,50 @@ defineExpose({
.font-mono {
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Menlo, monospace;
}
/* Shell 语法高亮(仅命令栏,与 xterm SSH 输出 ANSI 无关) */
:deep(.sh-keyword) {
color: #2563eb;
font-weight: 500;
}
:deep(.sh-builtin) {
color: #0284c7;
}
:deep(.sh-sudo) {
color: #dc2626;
font-weight: 600;
}
:deep(.sh-pipe),
:deep(.sh-operator) {
color: #ca8a04;
font-weight: 600;
}
:deep(.sh-redirect) {
color: #ea580c;
}
:deep(.sh-string) {
color: #16a34a;
}
:deep(.sh-flag) {
color: #9333ea;
}
:deep(.sh-variable) {
color: #db2777;
}
:deep(.sh-backtick) {
color: #7c3aed;
}
:deep(.sh-comment) {
color: #94a3b8;
font-style: italic;
}
:deep(.sh-number) {
color: #d97706;
}
:deep(.sh-path) {
color: #0d9488;
}
:deep(.sh-text) {
color: rgb(var(--v-theme-on-surface));
}
</style>
@@ -25,6 +25,7 @@ import {
import { useTerminalSettings } from './useTerminalSettings'
import { useTerminalCmdBar } from './useTerminalCmdBar'
import { XTERM_CLASSIC_THEME } from './xtermTheme'
import { decodeTerminalPayload } from '@/utils/terminalPayload'
const MAX_TABS = 10
@@ -302,7 +303,7 @@ export function useTerminalSessions(nexusDrawer: Ref<boolean>) {
}
break
case 'DATA':
native.term?.write(msg.data)
native.term?.write(decodeTerminalPayload(msg))
break
case 'ERROR':
native.term?.write(`\r\n\x1b[31m⚠ ${msg.message}\x1b[0m\r\n`)
-44
View File
@@ -317,48 +317,4 @@ onBeforeUnmount(() => {
:deep(.terminal-ctx-menu .v-icon) {
font-size: 1.35rem;
}
.terminal-root--classic :deep(.sh-keyword) {
color: #60a5fa;
font-weight: 500;
}
.terminal-root--classic :deep(.sh-builtin) {
color: #38bdf8;
}
.terminal-root--classic :deep(.sh-sudo) {
color: #f87171;
font-weight: 600;
}
.terminal-root--classic :deep(.sh-pipe),
.terminal-root--classic :deep(.sh-operator) {
color: #fbbf24;
font-weight: 600;
}
.terminal-root--classic :deep(.sh-redirect) {
color: #fb923c;
}
.terminal-root--classic :deep(.sh-string) {
color: #4ade80;
}
.terminal-root--classic :deep(.sh-flag) {
color: #c084fc;
}
.terminal-root--classic :deep(.sh-variable) {
color: #f472b6;
}
.terminal-root--classic :deep(.sh-backtick) {
color: #a78bfa;
}
.terminal-root--classic :deep(.sh-comment) {
color: #64748b;
font-style: italic;
}
.terminal-root--classic :deep(.sh-number) {
color: #fbbf24;
}
.terminal-root--classic :deep(.sh-path) {
color: #2dd4bf;
}
.terminal-root--classic :deep(.sh-text) {
color: #e2e8f0;
}
</style>
+13
View File
@@ -0,0 +1,13 @@
/** Decode WebSSH DATA frame payload for xterm.write (base64 preserves ANSI ESC bytes). */
export function decodeTerminalPayload(msg: {
data_b64?: string
data?: string
}): string | Uint8Array {
if (typeof msg.data_b64 === 'string' && msg.data_b64.length > 0) {
const bin = atob(msg.data_b64)
const bytes = new Uint8Array(bin.length)
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
return bytes
}
return msg.data ?? ''
}
+20 -7
View File
@@ -2,10 +2,11 @@
"""
import asyncio
import base64
import json
import logging
import uuid
from typing import Optional
from typing import Optional, Union
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query, Path
@@ -34,6 +35,20 @@ MSG_PONG = "PONG"
# Command logging configuration
COMMAND_LOG_MAX_LENGTH = 2000
# PTY environment for ANSI / 256-color remote programs (ls, grep, vim, etc.)
WEBSSH_PTY_ENV = {
"TERM": "xterm-256color",
"COLORTERM": "truecolor",
"FORCE_COLOR": "1",
"CLICOLOR_FORCE": "1",
}
def _terminal_payload_b64(data: Union[str, bytes]) -> str:
"""Base64-encode shell stdout for JSON WebSocket (preserves ANSI ESC bytes)."""
raw = data.encode("utf-8", errors="surrogateescape") if isinstance(data, str) else data
return base64.b64encode(raw).decode("ascii")
async def _verify_webssh_token(token: str):
"""Verify JWT token for WebSSH — returns (admin, server_id) or (None, None)
@@ -186,6 +201,7 @@ async def terminal_ws(
term_type="xterm-256color",
term_size=(24, 80),
bufsize=4096, # Small buffer for low-latency interactive echo
env=WEBSSH_PTY_ENV,
)
except Exception as e:
# Stale pooled connection — force-close and retry with fresh connection
@@ -197,6 +213,7 @@ async def terminal_ws(
term_type="xterm-256color",
term_size=(24, 80),
bufsize=4096, # Small buffer for low-latency interactive echo
env=WEBSSH_PTY_ENV,
)
except Exception as e2:
logger.error(f"WebSSH shell creation failed on fresh connection (server={server.id}): {type(e2).__name__}: {e2!r}")
@@ -221,11 +238,7 @@ async def terminal_ws(
"server_id": server.id,
})
# Color + CWD: ANSI/256/truecolor for ls, grep, vim, etc.
shell.stdin.write(
"export TERM=xterm-256color COLORTERM=truecolor "
"FORCE_COLOR=1 CLICOLOR_FORCE=1\n"
)
# Window title via OSC sequence (TERM/color env set on PTY via create_process)
shell.stdin.write('export PROMPT_COMMAND=\'echo -ne "\\033]0;${PWD}\\a"\'\n')
# ── Bidirectional relay ──
@@ -245,7 +258,7 @@ async def terminal_ws(
if data:
await websocket.send_json({
"type": MSG_DATA,
"data": data,
"data_b64": _terminal_payload_b64(data),
})
except Exception: # noqa: S110 — SSH read loop, connection teardown
pass
+23
View File
@@ -0,0 +1,23 @@
"""WebSSH terminal DATA payload encoding (ANSI-safe base64)."""
import base64
from server.api.webssh import WEBSSH_PTY_ENV, _terminal_payload_b64
def test_pty_env_has_color_term():
assert WEBSSH_PTY_ENV["TERM"] == "xterm-256color"
assert WEBSSH_PTY_ENV["COLORTERM"] == "truecolor"
assert WEBSSH_PTY_ENV["FORCE_COLOR"] == "1"
def test_terminal_payload_b64_preserves_ansi_escapes():
raw = "\x1b[31mred\x1b[32mgreen\x1b[0m\n"
b64 = _terminal_payload_b64(raw)
decoded = base64.b64decode(b64)
assert decoded == raw.encode("utf-8")
def test_terminal_payload_b64_from_bytes():
raw = b"\x1b[33myellow\x1b[0m"
b64 = _terminal_payload_b64(raw)
assert base64.b64decode(b64) == raw