@yeaft/webchat-agent 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/terminal.js ADDED
@@ -0,0 +1,176 @@
1
+ import { platform } from 'os';
2
+ import { existsSync } from 'fs';
3
+ import ctx from './context.js';
4
+
5
+ // 动态加载 node-pty (optionalDependency)
6
+ export async function loadNodePty() {
7
+ if (ctx.nodePty !== null) return ctx.nodePty;
8
+ try {
9
+ let pty = await import('node-pty');
10
+ if (pty.default) pty = pty.default;
11
+ ctx.nodePty = pty;
12
+ console.log('[PTY] node-pty loaded successfully');
13
+ return pty;
14
+ } catch (e) {
15
+ console.warn('[PTY] node-pty not available:', e.message);
16
+ ctx.nodePty = false;
17
+ return false;
18
+ }
19
+ }
20
+
21
+ export async function handleTerminalCreate(msg) {
22
+ const { conversationId, cols, rows } = msg;
23
+ const terminalId = msg.terminalId || conversationId;
24
+ const conv = ctx.conversations.get(conversationId);
25
+ const workDir = conv?.workDir || ctx.CONFIG.workDir;
26
+
27
+ // 如果已存在终端,先关闭
28
+ if (ctx.terminals.has(terminalId)) {
29
+ const existing = ctx.terminals.get(terminalId);
30
+ if (existing.pty) {
31
+ try { existing.pty.kill(); } catch {}
32
+ }
33
+ if (existing.timer) clearTimeout(existing.timer);
34
+ ctx.terminals.delete(terminalId);
35
+ }
36
+
37
+ const pty = await loadNodePty();
38
+ if (!pty) {
39
+ ctx.sendToServer({
40
+ type: 'terminal_error',
41
+ conversationId,
42
+ terminalId,
43
+ message: 'node-pty is not installed. Run: npm install node-pty'
44
+ });
45
+ return;
46
+ }
47
+
48
+ try {
49
+ const shell = platform() === 'win32'
50
+ ? (existsSync('C:\\Program Files\\PowerShell\\7\\pwsh.exe')
51
+ ? 'C:\\Program Files\\PowerShell\\7\\pwsh.exe'
52
+ : (existsSync(`${process.env.SystemRoot || 'C:\\Windows'}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`)
53
+ ? `${process.env.SystemRoot || 'C:\\Windows'}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
54
+ : (process.env.COMSPEC || 'cmd.exe')))
55
+ : (process.env.SHELL || 'bash');
56
+ const ptyProcess = pty.spawn(shell, [], {
57
+ name: 'xterm-256color',
58
+ cols: cols || 80,
59
+ rows: rows || 24,
60
+ cwd: workDir,
61
+ env: process.env
62
+ });
63
+
64
+ // 输出缓冲 - 每 16ms 批量发送
65
+ let buffer = '';
66
+ let timer = null;
67
+
68
+ ptyProcess.onData(data => {
69
+ buffer += data;
70
+ if (!timer) {
71
+ timer = setTimeout(() => {
72
+ ctx.sendToServer({
73
+ type: 'terminal_output',
74
+ conversationId,
75
+ terminalId,
76
+ data: buffer
77
+ });
78
+ buffer = '';
79
+ timer = null;
80
+ }, 16);
81
+ }
82
+ });
83
+
84
+ ptyProcess.onExit(({ exitCode }) => {
85
+ // 发送剩余缓冲
86
+ if (buffer) {
87
+ ctx.sendToServer({
88
+ type: 'terminal_output',
89
+ conversationId,
90
+ terminalId,
91
+ data: buffer
92
+ });
93
+ buffer = '';
94
+ }
95
+ if (timer) clearTimeout(timer);
96
+
97
+ console.log(`[PTY] Process exited for ${terminalId}, code: ${exitCode}`);
98
+ ctx.terminals.delete(terminalId);
99
+ ctx.sendToServer({
100
+ type: 'terminal_closed',
101
+ conversationId,
102
+ terminalId
103
+ });
104
+ });
105
+
106
+ ctx.terminals.set(terminalId, {
107
+ pty: ptyProcess,
108
+ conversationId,
109
+ cols: cols || 80,
110
+ rows: rows || 24,
111
+ buffer: '',
112
+ timer: null
113
+ });
114
+
115
+ console.log(`[PTY] Created terminal ${terminalId} for ${conversationId} in ${workDir}`);
116
+ ctx.sendToServer({
117
+ type: 'terminal_created',
118
+ conversationId,
119
+ terminalId,
120
+ success: true
121
+ });
122
+ } catch (e) {
123
+ console.error(`[PTY] Failed to create terminal:`, e.message);
124
+ ctx.sendToServer({
125
+ type: 'terminal_error',
126
+ conversationId,
127
+ terminalId,
128
+ message: `Failed to create terminal: ${e.message}`
129
+ });
130
+ }
131
+ }
132
+
133
+ export function handleTerminalInput(msg) {
134
+ const terminalId = msg.terminalId || msg.conversationId;
135
+ const term = ctx.terminals.get(terminalId);
136
+ if (term?.pty) {
137
+ try {
138
+ term.pty.write(msg.data);
139
+ } catch (e) {
140
+ console.error(`[PTY] Write error for ${terminalId}:`, e.message);
141
+ }
142
+ }
143
+ }
144
+
145
+ export function handleTerminalResize(msg) {
146
+ const terminalId = msg.terminalId || msg.conversationId;
147
+ const { cols, rows } = msg;
148
+ const term = ctx.terminals.get(terminalId);
149
+ if (term?.pty && cols > 0 && rows > 0) {
150
+ try {
151
+ term.pty.resize(cols, rows);
152
+ term.cols = cols;
153
+ term.rows = rows;
154
+ } catch (e) {
155
+ console.error(`[PTY] Resize error for ${terminalId}:`, e.message);
156
+ }
157
+ }
158
+ }
159
+
160
+ export function handleTerminalClose(msg) {
161
+ const terminalId = msg.terminalId || msg.conversationId;
162
+ const term = ctx.terminals.get(terminalId);
163
+ if (term) {
164
+ if (term.pty) {
165
+ try { term.pty.kill(); } catch {}
166
+ }
167
+ if (term.timer) clearTimeout(term.timer);
168
+ ctx.terminals.delete(terminalId);
169
+ console.log(`[PTY] Closed terminal ${terminalId}`);
170
+ ctx.sendToServer({
171
+ type: 'terminal_closed',
172
+ conversationId: term.conversationId || msg.conversationId,
173
+ terminalId
174
+ });
175
+ }
176
+ }