dsh-single-terminal 0.1.0

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 (72) hide show
  1. package/README.md +144 -0
  2. package/README.zh.md +129 -0
  3. package/cordis.patch.yml +11 -0
  4. package/lib/client.js +11438 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +588 -0
  7. package/lib/types/client/controller.d.ts +86 -0
  8. package/lib/types/client/controller.d.ts.map +1 -0
  9. package/lib/types/client/controller.js +235 -0
  10. package/lib/types/client/drawer.d.ts +10 -0
  11. package/lib/types/client/drawer.d.ts.map +1 -0
  12. package/lib/types/client/drawer.js +84 -0
  13. package/lib/types/client/i18n.d.ts +8 -0
  14. package/lib/types/client/i18n.d.ts.map +1 -0
  15. package/lib/types/client/i18n.js +51 -0
  16. package/lib/types/client/index.d.ts +13 -0
  17. package/lib/types/client/index.d.ts.map +1 -0
  18. package/lib/types/client/index.js +13 -0
  19. package/lib/types/client/plugin.d.ts +23 -0
  20. package/lib/types/client/plugin.d.ts.map +1 -0
  21. package/lib/types/client/plugin.js +60 -0
  22. package/lib/types/client/protocol.d.ts +81 -0
  23. package/lib/types/client/protocol.d.ts.map +1 -0
  24. package/lib/types/client/protocol.js +5 -0
  25. package/lib/types/client/storage.d.ts +6 -0
  26. package/lib/types/client/storage.d.ts.map +1 -0
  27. package/lib/types/client/storage.js +21 -0
  28. package/lib/types/client/styles.d.ts +5 -0
  29. package/lib/types/client/styles.d.ts.map +1 -0
  30. package/lib/types/client/styles.js +103 -0
  31. package/lib/types/client/term.d.ts +15 -0
  32. package/lib/types/client/term.d.ts.map +1 -0
  33. package/lib/types/client/term.js +86 -0
  34. package/lib/types/client/toggle.d.ts +9 -0
  35. package/lib/types/client/toggle.d.ts.map +1 -0
  36. package/lib/types/client/toggle.js +19 -0
  37. package/lib/types/client/ws.d.ts +25 -0
  38. package/lib/types/client/ws.d.ts.map +1 -0
  39. package/lib/types/client/ws.js +79 -0
  40. package/lib/types/client/xterm-css.d.ts +7 -0
  41. package/lib/types/client/xterm-css.d.ts.map +1 -0
  42. package/lib/types/client/xterm-css.js +224 -0
  43. package/lib/types/host/hub.d.ts +39 -0
  44. package/lib/types/host/hub.d.ts.map +1 -0
  45. package/lib/types/host/hub.js +291 -0
  46. package/lib/types/host/index.d.ts +17 -0
  47. package/lib/types/host/index.d.ts.map +1 -0
  48. package/lib/types/host/index.js +66 -0
  49. package/lib/types/host/shells.d.ts +30 -0
  50. package/lib/types/host/shells.d.ts.map +1 -0
  51. package/lib/types/host/shells.js +202 -0
  52. package/lib/types/host/types.d.ts +109 -0
  53. package/lib/types/host/types.d.ts.map +1 -0
  54. package/lib/types/host/types.js +4 -0
  55. package/package.json +102 -0
  56. package/src/client/controller.ts +293 -0
  57. package/src/client/drawer.tsx +182 -0
  58. package/src/client/i18n.ts +58 -0
  59. package/src/client/index.ts +17 -0
  60. package/src/client/plugin.tsx +92 -0
  61. package/src/client/protocol.ts +40 -0
  62. package/src/client/storage.ts +21 -0
  63. package/src/client/styles.ts +106 -0
  64. package/src/client/term.tsx +95 -0
  65. package/src/client/toggle.tsx +43 -0
  66. package/src/client/ws.ts +81 -0
  67. package/src/client/xterm-css.ts +224 -0
  68. package/src/host/cordis-augment.d.ts +22 -0
  69. package/src/host/hub.ts +302 -0
  70. package/src/host/index.ts +78 -0
  71. package/src/host/shells.ts +216 -0
  72. package/src/host/types.ts +79 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * dsh-single-terminal —— 抽屉全局样式 + 内嵌 xterm.css(构建期生成注入)。
3
+ */
4
+ import { XTERM_CSS } from "./xterm-css.js";
5
+ const DRAWER_CSS = `
6
+ .dst-drawer {
7
+ position: absolute;
8
+ left: 0; right: 0; bottom: 0;
9
+ height: var(--dst-height, 360px);
10
+ min-height: 140px;
11
+ display: flex;
12
+ flex-direction: column;
13
+ background: #1f2126;
14
+ border-top: 1px solid #3a3d44;
15
+ box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.25);
16
+ z-index: 5;
17
+ }
18
+ .dst-drag { height: 5px; flex: none; cursor: row-resize; touch-action: none; }
19
+ .dst-drag:hover { background: #4a4d55; }
20
+ .dst-head {
21
+ display: flex; align-items: center; gap: 4px;
22
+ height: 36px; flex: none; padding: 0 8px;
23
+ border-bottom: 1px solid #3a3d44;
24
+ color: #c9cdd4;
25
+ }
26
+ .dst-tabs { display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; }
27
+ .dst-tabs::-webkit-scrollbar { display: none; }
28
+ .dst-tab {
29
+ display: inline-flex; align-items: center; gap: 6px;
30
+ padding: 4px 8px; border: none; border-radius: 6px;
31
+ background: transparent; color: #9aa0aa; font-size: 12px;
32
+ cursor: pointer; white-space: nowrap;
33
+ }
34
+ .dst-tab:hover { background: rgba(255, 255, 255, 0.06); }
35
+ .dst-tab.active { background: rgba(255, 255, 255, 0.1); color: #eceef1; }
36
+ .dst-dot { width: 6px; height: 6px; border-radius: 50%; background: #3fb950; flex: none; }
37
+ .dst-dot.dead { background: #6e7278; }
38
+ .dst-tabclose {
39
+ display: inline-flex; align-items: center; justify-content: center;
40
+ width: 16px; height: 16px; border: none; border-radius: 4px;
41
+ background: transparent; color: inherit; font-size: 12px; line-height: 1;
42
+ cursor: pointer; padding: 0;
43
+ }
44
+ .dst-tabclose:hover { background: rgba(255, 255, 255, 0.14); }
45
+ .dst-actions { display: flex; align-items: center; gap: 2px; flex: none; }
46
+ .dst-conn { width: 8px; height: 8px; border-radius: 50%; flex: none; margin: 0 4px; }
47
+ .dst-conn.connected { background: #3fb950; }
48
+ .dst-conn.connecting, .dst-conn.reconnecting { background: #d29922; }
49
+ .dst-iconbtn {
50
+ display: inline-flex; align-items: center; justify-content: center;
51
+ width: 26px; height: 26px; border: none; border-radius: 6px;
52
+ background: transparent; color: inherit; cursor: pointer; padding: 0;
53
+ }
54
+ .dst-iconbtn:hover { background: rgba(255, 255, 255, 0.1); }
55
+ .dst-modebtn {
56
+ display: inline-flex; align-items: center; gap: 4px;
57
+ height: 26px; padding: 0 8px; border: none; border-radius: 6px;
58
+ background: transparent; color: #9aa0aa; font-size: 12px; cursor: pointer;
59
+ }
60
+ .dst-modebtn:hover { background: rgba(255, 255, 255, 0.1); color: #eceef1; }
61
+ .dst-menu {
62
+ position: absolute; right: 8px; bottom: 44px; min-width: 170px;
63
+ background: #26282e; border: 1px solid #3a3d44; border-radius: 8px;
64
+ padding: 4px; z-index: 10; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
65
+ }
66
+ .dst-menuitem {
67
+ display: flex; width: 100%; align-items: center; gap: 8px;
68
+ padding: 6px 10px; border: none; border-radius: 6px;
69
+ background: transparent; color: #d5d8dd; font-size: 13px;
70
+ cursor: pointer; text-align: left;
71
+ }
72
+ .dst-menuitem:hover { background: rgba(255, 255, 255, 0.08); }
73
+ .dst-body { flex: 1; min-height: 0; position: relative; padding: 2px 6px 6px; background: #1a1c20; }
74
+ .dst-term { width: 100%; height: 100%; }
75
+ .dst-empty {
76
+ position: absolute; inset: 0; display: flex;
77
+ align-items: center; justify-content: center;
78
+ color: #6e7278; font-size: 13px;
79
+ }
80
+ .dst-notice {
81
+ position: absolute; left: 50%; transform: translateX(-50%); bottom: 12px;
82
+ max-width: 70%; padding: 6px 12px; border-radius: 8px; z-index: 8;
83
+ background: #4a2226; color: #ffb4b4; font-size: 12px;
84
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
85
+ }
86
+ .dst-headbtn {
87
+ display: inline-flex; align-items: center; justify-content: center;
88
+ width: 26px; height: 26px; padding: 0;
89
+ border: none; border-radius: 6px; background: transparent;
90
+ color: inherit; cursor: pointer;
91
+ }
92
+ .dst-headbtn:hover, .dst-headbtn.active { background: rgba(128, 128, 128, 0.18); }
93
+ `;
94
+ let injected = false;
95
+ export function injectStyles() {
96
+ if (injected)
97
+ return;
98
+ injected = true;
99
+ const style = document.createElement('style');
100
+ style.setAttribute('data-plugin', 'dsh-single-terminal');
101
+ style.textContent = XTERM_CSS + DRAWER_CSS;
102
+ document.head.appendChild(style);
103
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * dsh-single-terminal —— 单标签终端视图(xterm.js)。
3
+ *
4
+ * - Webgl 渲染器加载失败时自动回退到默认渲染;
5
+ * - ResizeObserver → FitAddon.fit() → onResize 帧上报宿主 resize PTY;
6
+ * - 输出经 controller sink 通道写入(挂载前的字节积压在 controller.pending)。
7
+ */
8
+ import { type TabState } from './controller.ts';
9
+ export declare function TerminalView(props: {
10
+ tab: TabState;
11
+ active: boolean;
12
+ fontSize: number;
13
+ fontFamily: string;
14
+ }): import("react").JSX.Element;
15
+ //# sourceMappingURL=term.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"term.d.ts","sourceRoot":"","sources":["../../../src/client/term.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AA0BzD,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,GAAG,EAAE,QAAQ,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB,+BAmDA"}
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * dsh-single-terminal —— 单标签终端视图(xterm.js)。
4
+ *
5
+ * - Webgl 渲染器加载失败时自动回退到默认渲染;
6
+ * - ResizeObserver → FitAddon.fit() → onResize 帧上报宿主 resize PTY;
7
+ * - 输出经 controller sink 通道写入(挂载前的字节积压在 controller.pending)。
8
+ */
9
+ import { useEffect, useRef } from 'react';
10
+ import { Terminal } from '@xterm/xterm';
11
+ import { FitAddon } from '@xterm/addon-fit';
12
+ import { WebglAddon } from '@xterm/addon-webgl';
13
+ import { terminal } from "./controller.js";
14
+ const THEME = {
15
+ background: '#1a1c20',
16
+ foreground: '#d4d4d4',
17
+ cursor: '#aeafad',
18
+ cursorAccent: '#1a1c20',
19
+ selectionBackground: '#264f78',
20
+ black: '#000000',
21
+ red: '#cd3131',
22
+ green: '#0dbc79',
23
+ yellow: '#e5e510',
24
+ blue: '#2472c8',
25
+ magenta: '#bc3fbc',
26
+ cyan: '#11a8cd',
27
+ white: '#e5e5e5',
28
+ brightBlack: '#666666',
29
+ brightRed: '#f14c4c',
30
+ brightGreen: '#23d18b',
31
+ brightYellow: '#f5f543',
32
+ brightBlue: '#3b8eea',
33
+ brightMagenta: '#d670d6',
34
+ brightCyan: '#29b8db',
35
+ brightWhite: '#ffffff',
36
+ };
37
+ export function TerminalView(props) {
38
+ const containerRef = useRef(null);
39
+ useEffect(() => {
40
+ const container = containerRef.current;
41
+ if (container === null)
42
+ return;
43
+ const id = props.tab.id;
44
+ const instance = new Terminal({
45
+ fontSize: props.fontSize,
46
+ fontFamily: props.fontFamily,
47
+ cursorBlink: true,
48
+ scrollback: 2000,
49
+ theme: THEME,
50
+ });
51
+ const fit = new FitAddon();
52
+ instance.loadAddon(fit);
53
+ try {
54
+ instance.loadAddon(new WebglAddon());
55
+ }
56
+ catch { /* no webgl: default canvas renderer */ }
57
+ instance.open(container);
58
+ instance.onData((data) => { terminal.sendInput(id, data); });
59
+ instance.onResize(({ cols, rows }) => { terminal.sendResize(id, cols, rows); });
60
+ const unregister = terminal.registerSink(id, { write: (data) => { instance.write(data); } });
61
+ const observer = new ResizeObserver(() => {
62
+ if (container.clientWidth < 10 || container.clientHeight < 10)
63
+ return;
64
+ try {
65
+ fit.fit();
66
+ }
67
+ catch { /* not visible */ }
68
+ });
69
+ observer.observe(container);
70
+ requestAnimationFrame(() => {
71
+ if (container.clientWidth < 10 || container.clientHeight < 10)
72
+ return;
73
+ try {
74
+ fit.fit();
75
+ }
76
+ catch { /* not ready */ }
77
+ });
78
+ return () => {
79
+ observer.disconnect();
80
+ unregister();
81
+ instance.dispose();
82
+ };
83
+ // fontSize/fontFamily 变化不重建(配置改动后刷新页面生效)
84
+ }, [props.tab.id]);
85
+ return (_jsx("div", { ref: containerRef, className: "dst-term", style: { display: props.active ? 'block' : 'none' } }));
86
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * dsh-single-terminal —— 会话头部开关按钮(conversation.session.header.utilities 插槽)。
3
+ *
4
+ * 图标为 panel-bottom(面板底部条填充)样式,几何取自宿主 IconPanelLeftOutline16
5
+ * 旋转 -90°,风格与宿主图标一致;hover 气泡用宿主 Tooltip(label 传 thunk,
6
+ * 气泡显示时求值,语言切换即生效)。
7
+ */
8
+ export declare function TerminalToggle(): import("react").JSX.Element;
9
+ //# sourceMappingURL=toggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toggle.d.ts","sourceRoot":"","sources":["../../../src/client/toggle.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAsBH,wBAAgB,cAAc,gCAc7B"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * dsh-single-terminal —— 会话头部开关按钮(conversation.session.header.utilities 插槽)。
4
+ *
5
+ * 图标为 panel-bottom(面板底部条填充)样式,几何取自宿主 IconPanelLeftOutline16
6
+ * 旋转 -90°,风格与宿主图标一致;hover 气泡用宿主 Tooltip(label 传 thunk,
7
+ * 气泡显示时求值,语言切换即生效)。
8
+ */
9
+ import { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
10
+ import { terminal, useDrawerState } from "./controller.js";
11
+ import { t } from "./i18n.js";
12
+ const PANEL_LEFT_PATH = 'M9.67272 0.522841C10.8339 0.522841 11.76 0.522714 12.4963 0.602493C13.2453 0.683657 13.8789 0.854248 14.4264 1.25197C14.7504 1.48739 15.0355 1.77247 15.2709 2.0965C15.6686 2.64394 15.8392 3.27758 15.9204 4.02655C16.0002 4.7629 16 5.68895 16 6.85014V9.14986C16 10.3111 16.0002 11.2371 15.9204 11.9735C15.8392 12.7224 15.6686 13.3561 15.2709 13.9035C15.0355 14.2275 14.7504 14.5126 14.4264 14.748C13.8789 15.1458 13.2453 15.3163 12.4963 15.3975C11.76 15.4773 10.8339 15.4772 9.67272 15.4772H6.3273C5.16611 15.4772 4.24006 15.4773 3.50371 15.3975C2.75474 15.3163 2.1211 15.1458 1.57366 14.748C1.24963 14.5126 0.964549 14.2275 0.729131 13.9035C0.331407 13.3561 0.160817 12.7224 0.0796529 11.9735C-0.000126137 11.2371 1.25338e-09 10.3111 1.25338e-09 9.14986V6.85014C1.25329e-09 5.68895 -0.000126137 4.7629 0.0796529 4.02655C0.160817 3.27758 0.331407 2.64394 0.729131 2.0965C0.964549 1.77247 1.24963 1.48739 1.57366 1.25197C2.1211 0.854248 2.75474 0.683657 3.50371 0.602493C4.24006 0.522714 5.16611 0.522841 6.3273 0.522841H9.67272ZM5.54303 1.88715V14.1118C5.78636 14.1128 6.04709 14.1169 6.3273 14.1169H9.67272C10.8639 14.1169 11.7032 14.1164 12.3493 14.0465C12.9824 13.9779 13.3497 13.8494 13.6268 13.6482C13.8354 13.4966 14.0195 13.3125 14.1711 13.1039C14.3723 12.8268 14.5007 12.4595 14.5693 11.8264C14.6393 11.1803 14.6398 10.341 14.6398 9.14986V6.85014C14.6398 5.65896 14.6393 4.81967 14.5693 4.1736C14.5007 3.54048 14.3723 3.17318 14.1711 2.89609C14.0195 2.68747 13.8354 2.50337 13.6268 2.35179C13.3497 2.1506 12.9824 2.02212 12.3493 1.95353C11.7032 1.88358 10.8639 1.88307 9.67272 1.88307H6.3273C6.04709 1.88307 5.78636 1.8862 5.54303 1.88715ZM4.1828 1.91166C3.99125 1.9216 3.8148 1.93577 3.65076 1.95353C3.01764 2.02212 2.65034 2.1506 2.37325 2.35179C2.16463 2.50337 1.98052 2.68747 1.82895 2.89609C1.62776 3.17318 1.49928 3.54048 1.43069 4.1736C1.36074 4.81967 1.36023 5.65896 1.36023 6.85014V9.14986C1.36023 10.341 1.36074 11.1803 1.43069 11.8264C1.49928 12.4595 1.62776 12.8268 1.82895 13.1039C1.98052 13.3125 2.16463 13.4966 2.37325 13.6482C2.65034 13.8494 3.01764 13.9779 3.65076 14.0465C3.81478 14.0642 3.99127 14.0774 4.1828 14.0873V1.91166Z';
13
+ function PanelBottomIcon(props) {
14
+ return (_jsx("svg", { width: props.size ?? 16, height: props.size ?? 16, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: PANEL_LEFT_PATH, transform: "rotate(-90 8 8)", fillRule: "evenodd", clipRule: "evenodd", fill: "currentColor" }) }));
15
+ }
16
+ export function TerminalToggle() {
17
+ const state = useDrawerState();
18
+ return (_jsx(Tooltip, { label: () => t('terminal.toggleTip'), side: "bottom", children: _jsx("button", { type: "button", className: `dst-headbtn${state.open ? ' active' : ''}`, "aria-label": t('terminal.toggleTip'), onClick: () => { terminal.toggle(); }, children: _jsx(PanelBottomIcon, { size: 16 }) }) }));
19
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * dsh-single-terminal —— 同源 WebSocket 客户端(自动重连,指数退避)。
3
+ *
4
+ * URL 由 location.origin 推导(http→ws / https→wss),浏览器自动携带
5
+ * dsh-auth cookie(与宿主 /api/remote.mux 相同的鉴权路径)。
6
+ */
7
+ import type { ClientFrame, HostFrame } from './protocol.ts';
8
+ export type ConnectionState = 'connecting' | 'connected' | 'reconnecting';
9
+ export interface TerminalSocketHandlers {
10
+ onFrame(frame: HostFrame): void;
11
+ onState(state: ConnectionState): void;
12
+ }
13
+ export declare class TerminalSocket {
14
+ private readonly handlers;
15
+ private socket;
16
+ private retry;
17
+ private timer;
18
+ private disposed;
19
+ constructor(handlers: TerminalSocketHandlers);
20
+ connect(): void;
21
+ send(frame: ClientFrame): void;
22
+ dispose(): void;
23
+ private scheduleReconnect;
24
+ }
25
+ //# sourceMappingURL=ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../../src/client/ws.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE3D,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAA;AAEzE,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAC/B,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;CACtC;AAED,qBAAa,cAAc;IAMb,OAAO,CAAC,QAAQ,CAAC,QAAQ;IALrC,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,QAAQ,CAAQ;gBAEK,QAAQ,EAAE,sBAAsB;IAE7D,OAAO,IAAI,IAAI;IA+Bf,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAM9B,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,iBAAiB;CAS1B"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * dsh-single-terminal —— 同源 WebSocket 客户端(自动重连,指数退避)。
3
+ *
4
+ * URL 由 location.origin 推导(http→ws / https→wss),浏览器自动携带
5
+ * dsh-auth cookie(与宿主 /api/remote.mux 相同的鉴权路径)。
6
+ */
7
+ export class TerminalSocket {
8
+ handlers;
9
+ socket = null;
10
+ retry = 0;
11
+ timer = null;
12
+ disposed = false;
13
+ constructor(handlers) {
14
+ this.handlers = handlers;
15
+ }
16
+ connect() {
17
+ if (this.disposed || this.socket !== null)
18
+ return;
19
+ this.handlers.onState(this.retry === 0 ? 'connecting' : 'reconnecting');
20
+ const origin = globalThis.location?.origin;
21
+ const base = origin !== undefined && origin !== 'null' ? origin : 'http://localhost:3000';
22
+ const url = base.replace(/^http/, 'ws') + '/api/dsh-single-terminal.ws';
23
+ const socket = new WebSocket(url);
24
+ this.socket = socket;
25
+ socket.onopen = () => {
26
+ this.retry = 0;
27
+ this.handlers.onState('connected');
28
+ };
29
+ socket.onmessage = (event) => {
30
+ let frame;
31
+ try {
32
+ frame = JSON.parse(String(event.data));
33
+ }
34
+ catch {
35
+ return;
36
+ }
37
+ this.handlers.onFrame(frame);
38
+ };
39
+ socket.onclose = () => {
40
+ this.socket = null;
41
+ if (this.disposed)
42
+ return;
43
+ this.scheduleReconnect();
44
+ };
45
+ socket.onerror = () => {
46
+ try {
47
+ socket.close();
48
+ }
49
+ catch { /* closing */ }
50
+ };
51
+ }
52
+ send(frame) {
53
+ if (this.socket !== null && this.socket.readyState === WebSocket.OPEN) {
54
+ this.socket.send(JSON.stringify(frame));
55
+ }
56
+ }
57
+ dispose() {
58
+ this.disposed = true;
59
+ if (this.timer !== null)
60
+ clearTimeout(this.timer);
61
+ this.timer = null;
62
+ if (this.socket !== null) {
63
+ try {
64
+ this.socket.close();
65
+ }
66
+ catch { /* closing */ }
67
+ this.socket = null;
68
+ }
69
+ }
70
+ scheduleReconnect() {
71
+ this.retry += 1;
72
+ const delay = Math.min(1000 * 2 ** Math.min(this.retry - 1, 4), 15_000);
73
+ this.handlers.onState('reconnecting');
74
+ this.timer = setTimeout(() => {
75
+ this.timer = null;
76
+ this.connect();
77
+ }, delay);
78
+ }
79
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * xterm.css(@xterm/xterm 5.x 自带样式)· 生成期内嵌。
3
+ * 来源:node_modules/@xterm/xterm/css/xterm.css(MIT,xterm.js authors)。
4
+ * 升级 @xterm/xterm 后重跑 scripts/gen-xterm-css.mjs 再生成本文件。
5
+ */
6
+ export declare const XTERM_CSS = "/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */\n\n/**\n * Default styles for xterm.js\n */\n\n.xterm {\n cursor: text;\n position: relative;\n user-select: none;\n -ms-user-select: none;\n -webkit-user-select: none;\n}\n\n.xterm.focus,\n.xterm:focus {\n outline: none;\n}\n\n.xterm .xterm-helpers {\n position: absolute;\n top: 0;\n /**\n * The z-index of the helpers must be higher than the canvases in order for\n * IMEs to appear on top.\n */\n z-index: 5;\n}\n\n.xterm .xterm-helper-textarea {\n padding: 0;\n border: 0;\n margin: 0;\n /* Move textarea out of the screen to the far left, so that the cursor is not visible */\n position: absolute;\n opacity: 0;\n left: -9999em;\n top: 0;\n width: 0;\n height: 0;\n z-index: -5;\n /** Prevent wrapping so the IME appears against the textarea at the correct position */\n white-space: nowrap;\n overflow: hidden;\n resize: none;\n}\n\n.xterm .composition-view {\n /* TODO: Composition position got messed up somewhere */\n background: #000;\n color: #FFF;\n display: none;\n position: absolute;\n white-space: nowrap;\n z-index: 1;\n}\n\n.xterm .composition-view.active {\n display: block;\n}\n\n.xterm .xterm-viewport {\n /* On OS X this is required in order for the scroll bar to appear fully opaque */\n background-color: #000;\n overflow-y: scroll;\n cursor: default;\n position: absolute;\n right: 0;\n left: 0;\n top: 0;\n bottom: 0;\n}\n\n.xterm .xterm-screen {\n position: relative;\n}\n\n.xterm .xterm-screen canvas {\n position: absolute;\n left: 0;\n top: 0;\n}\n\n.xterm .xterm-scroll-area {\n visibility: hidden;\n}\n\n.xterm-char-measure-element {\n display: inline-block;\n visibility: hidden;\n position: absolute;\n top: 0;\n left: -9999em;\n line-height: normal;\n}\n\n.xterm.enable-mouse-events {\n /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */\n cursor: default;\n}\n\n.xterm.xterm-cursor-pointer,\n.xterm .xterm-cursor-pointer {\n cursor: pointer;\n}\n\n.xterm.column-select.focus {\n /* Column selection mode */\n cursor: crosshair;\n}\n\n.xterm .xterm-accessibility:not(.debug),\n.xterm .xterm-message {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n right: 0;\n z-index: 10;\n color: transparent;\n pointer-events: none;\n}\n\n.xterm .xterm-accessibility-tree:not(.debug) *::selection {\n color: transparent;\n}\n\n.xterm .xterm-accessibility-tree {\n user-select: text;\n white-space: pre;\n}\n\n.xterm .live-region {\n position: absolute;\n left: -9999px;\n width: 1px;\n height: 1px;\n overflow: hidden;\n}\n\n.xterm-dim {\n /* Dim should not apply to background, so the opacity of the foreground color is applied\n * explicitly in the generated class and reset to 1 here */\n opacity: 1 !important;\n}\n\n.xterm-underline-1 { text-decoration: underline; }\n.xterm-underline-2 { text-decoration: double underline; }\n.xterm-underline-3 { text-decoration: wavy underline; }\n.xterm-underline-4 { text-decoration: dotted underline; }\n.xterm-underline-5 { text-decoration: dashed underline; }\n\n.xterm-overline {\n text-decoration: overline;\n}\n\n.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }\n.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }\n.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }\n.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }\n.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }\n\n.xterm-strikethrough {\n text-decoration: line-through;\n}\n\n.xterm-screen .xterm-decoration-container .xterm-decoration {\n\tz-index: 6;\n\tposition: absolute;\n}\n\n.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {\n\tz-index: 7;\n}\n\n.xterm-decoration-overview-ruler {\n z-index: 8;\n position: absolute;\n top: 0;\n right: 0;\n pointer-events: none;\n}\n\n.xterm-decoration-top {\n z-index: 2;\n position: relative;\n}\n";
7
+ //# sourceMappingURL=xterm-css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xterm-css.d.ts","sourceRoot":"","sources":["../../../src/client/xterm-css.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,SAAS,6pLA0NrB,CAAA"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * xterm.css(@xterm/xterm 5.x 自带样式)· 生成期内嵌。
3
+ * 来源:node_modules/@xterm/xterm/css/xterm.css(MIT,xterm.js authors)。
4
+ * 升级 @xterm/xterm 后重跑 scripts/gen-xterm-css.mjs 再生成本文件。
5
+ */
6
+ export const XTERM_CSS = `/**
7
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
8
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
9
+ * https://github.com/chjj/term.js
10
+ * @license MIT
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in
20
+ * all copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
+ * THE SOFTWARE.
29
+ *
30
+ * Originally forked from (with the author's permission):
31
+ * Fabrice Bellard's javascript vt100 for jslinux:
32
+ * http://bellard.org/jslinux/
33
+ * Copyright (c) 2011 Fabrice Bellard
34
+ * The original design remains. The terminal itself
35
+ * has been extended to include xterm CSI codes, among
36
+ * other features.
37
+ */
38
+
39
+ /**
40
+ * Default styles for xterm.js
41
+ */
42
+
43
+ .xterm {
44
+ cursor: text;
45
+ position: relative;
46
+ user-select: none;
47
+ -ms-user-select: none;
48
+ -webkit-user-select: none;
49
+ }
50
+
51
+ .xterm.focus,
52
+ .xterm:focus {
53
+ outline: none;
54
+ }
55
+
56
+ .xterm .xterm-helpers {
57
+ position: absolute;
58
+ top: 0;
59
+ /**
60
+ * The z-index of the helpers must be higher than the canvases in order for
61
+ * IMEs to appear on top.
62
+ */
63
+ z-index: 5;
64
+ }
65
+
66
+ .xterm .xterm-helper-textarea {
67
+ padding: 0;
68
+ border: 0;
69
+ margin: 0;
70
+ /* Move textarea out of the screen to the far left, so that the cursor is not visible */
71
+ position: absolute;
72
+ opacity: 0;
73
+ left: -9999em;
74
+ top: 0;
75
+ width: 0;
76
+ height: 0;
77
+ z-index: -5;
78
+ /** Prevent wrapping so the IME appears against the textarea at the correct position */
79
+ white-space: nowrap;
80
+ overflow: hidden;
81
+ resize: none;
82
+ }
83
+
84
+ .xterm .composition-view {
85
+ /* TODO: Composition position got messed up somewhere */
86
+ background: #000;
87
+ color: #FFF;
88
+ display: none;
89
+ position: absolute;
90
+ white-space: nowrap;
91
+ z-index: 1;
92
+ }
93
+
94
+ .xterm .composition-view.active {
95
+ display: block;
96
+ }
97
+
98
+ .xterm .xterm-viewport {
99
+ /* On OS X this is required in order for the scroll bar to appear fully opaque */
100
+ background-color: #000;
101
+ overflow-y: scroll;
102
+ cursor: default;
103
+ position: absolute;
104
+ right: 0;
105
+ left: 0;
106
+ top: 0;
107
+ bottom: 0;
108
+ }
109
+
110
+ .xterm .xterm-screen {
111
+ position: relative;
112
+ }
113
+
114
+ .xterm .xterm-screen canvas {
115
+ position: absolute;
116
+ left: 0;
117
+ top: 0;
118
+ }
119
+
120
+ .xterm .xterm-scroll-area {
121
+ visibility: hidden;
122
+ }
123
+
124
+ .xterm-char-measure-element {
125
+ display: inline-block;
126
+ visibility: hidden;
127
+ position: absolute;
128
+ top: 0;
129
+ left: -9999em;
130
+ line-height: normal;
131
+ }
132
+
133
+ .xterm.enable-mouse-events {
134
+ /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
135
+ cursor: default;
136
+ }
137
+
138
+ .xterm.xterm-cursor-pointer,
139
+ .xterm .xterm-cursor-pointer {
140
+ cursor: pointer;
141
+ }
142
+
143
+ .xterm.column-select.focus {
144
+ /* Column selection mode */
145
+ cursor: crosshair;
146
+ }
147
+
148
+ .xterm .xterm-accessibility:not(.debug),
149
+ .xterm .xterm-message {
150
+ position: absolute;
151
+ left: 0;
152
+ top: 0;
153
+ bottom: 0;
154
+ right: 0;
155
+ z-index: 10;
156
+ color: transparent;
157
+ pointer-events: none;
158
+ }
159
+
160
+ .xterm .xterm-accessibility-tree:not(.debug) *::selection {
161
+ color: transparent;
162
+ }
163
+
164
+ .xterm .xterm-accessibility-tree {
165
+ user-select: text;
166
+ white-space: pre;
167
+ }
168
+
169
+ .xterm .live-region {
170
+ position: absolute;
171
+ left: -9999px;
172
+ width: 1px;
173
+ height: 1px;
174
+ overflow: hidden;
175
+ }
176
+
177
+ .xterm-dim {
178
+ /* Dim should not apply to background, so the opacity of the foreground color is applied
179
+ * explicitly in the generated class and reset to 1 here */
180
+ opacity: 1 !important;
181
+ }
182
+
183
+ .xterm-underline-1 { text-decoration: underline; }
184
+ .xterm-underline-2 { text-decoration: double underline; }
185
+ .xterm-underline-3 { text-decoration: wavy underline; }
186
+ .xterm-underline-4 { text-decoration: dotted underline; }
187
+ .xterm-underline-5 { text-decoration: dashed underline; }
188
+
189
+ .xterm-overline {
190
+ text-decoration: overline;
191
+ }
192
+
193
+ .xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
194
+ .xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
195
+ .xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
196
+ .xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
197
+ .xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
198
+
199
+ .xterm-strikethrough {
200
+ text-decoration: line-through;
201
+ }
202
+
203
+ .xterm-screen .xterm-decoration-container .xterm-decoration {
204
+ z-index: 6;
205
+ position: absolute;
206
+ }
207
+
208
+ .xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
209
+ z-index: 7;
210
+ }
211
+
212
+ .xterm-decoration-overview-ruler {
213
+ z-index: 8;
214
+ position: absolute;
215
+ top: 0;
216
+ right: 0;
217
+ pointer-events: none;
218
+ }
219
+
220
+ .xterm-decoration-top {
221
+ z-index: 2;
222
+ position: relative;
223
+ }
224
+ `;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * dsh-single-terminal —— 终端 Hub:PTY 会话管理 + WebSocket 帧协议。
3
+ *
4
+ * - PTY 经插件自身依赖 node-pty(原生模块,external)直连 ConPTY/POSIX pty,
5
+ * 获得完整 resize/kill/数据流控制(宿主 ctx.subprocess 的 TerminalHandle
6
+ * 接口不暴露 resize,无法满足抽屉尺寸跟随);
7
+ * - 会话与 WebSocket 连接解耦:页面刷新/抽屉关闭后会话保活,重连后 attach
8
+ * 回放环形缓冲(容量 scrollbackLimit);
9
+ * - 多浏览器标签页可同时 attach 同一会话:输出广播、输入合并。
10
+ */
11
+ import type { IncomingMessage } from 'node:http';
12
+ import type { Duplex } from 'node:stream';
13
+ import type { ShellRegistry } from './shells.ts';
14
+ import type { TerminalPluginConfig } from './types.ts';
15
+ export declare class TerminalHub {
16
+ private readonly config;
17
+ private readonly registry;
18
+ private readonly wss;
19
+ private readonly sockets;
20
+ private readonly sessions;
21
+ private nextSocketId;
22
+ constructor(config: TerminalPluginConfig, registry: ShellRegistry);
23
+ /** webServer.registerUpgrade 的 handler 入口(鉴权由插件入口完成后调用)。 */
24
+ handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void;
25
+ /** 插件卸载:终止全部会话并断开所有连接。 */
26
+ dispose(): void;
27
+ private handleConnection;
28
+ private dispatch;
29
+ private open;
30
+ private close;
31
+ private killSession;
32
+ private resolveCwd;
33
+ private appendBuffer;
34
+ private snapshotFrame;
35
+ private sessionInfo;
36
+ private send;
37
+ private broadcast;
38
+ }
39
+ //# sourceMappingURL=hub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub.d.ts","sourceRoot":"","sources":["../../../src/host/hub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAuC,oBAAoB,EAAE,MAAM,YAAY,CAAA;AA2B3F,qBAAa,WAAW;IAOpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAP3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0C;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,YAAY,CAAI;gBAGL,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,aAAa;IAK1C,4DAA4D;IAC5D,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMvE,0BAA0B;IAC1B,OAAO,IAAI,IAAI;IAcf,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,QAAQ;IAsDhB,OAAO,CAAC,IAAI;IA2DZ,OAAO,CAAC,KAAK;IAcb,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,SAAS;CAKlB"}