agentgui 1.0.334 → 1.0.336
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/package.json +1 -1
- package/server.js +30 -0
- package/static/index.html +4 -4
- package/static/js/terminal.js +105 -0
- package/static/js/voice.js +0 -3
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -3419,6 +3419,35 @@ wss.on('connection', (ws, req) => {
|
|
|
3419
3419
|
requestId: data.requestId,
|
|
3420
3420
|
timestamp: Date.now()
|
|
3421
3421
|
}));
|
|
3422
|
+
} else if (data.type === 'terminal_start') {
|
|
3423
|
+
if (ws.terminalProc) {
|
|
3424
|
+
try { ws.terminalProc.kill(); } catch(e) {}
|
|
3425
|
+
}
|
|
3426
|
+
const { spawn } = require('child_process');
|
|
3427
|
+
const shell = process.env.SHELL || '/bin/bash';
|
|
3428
|
+
const cwd = data.cwd || process.env.STARTUP_CWD || process.env.HOME || '/';
|
|
3429
|
+
const proc = spawn(shell, [], { cwd, env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' }, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
3430
|
+
ws.terminalProc = proc;
|
|
3431
|
+
proc.stdout.on('data', (chunk) => {
|
|
3432
|
+
if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'terminal_output', data: chunk.toString('base64'), encoding: 'base64' }));
|
|
3433
|
+
});
|
|
3434
|
+
proc.stderr.on('data', (chunk) => {
|
|
3435
|
+
if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'terminal_output', data: chunk.toString('base64'), encoding: 'base64' }));
|
|
3436
|
+
});
|
|
3437
|
+
proc.on('exit', (code) => {
|
|
3438
|
+
if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'terminal_exit', code }));
|
|
3439
|
+
ws.terminalProc = null;
|
|
3440
|
+
});
|
|
3441
|
+
ws.send(JSON.stringify({ type: 'terminal_started', timestamp: Date.now() }));
|
|
3442
|
+
} else if (data.type === 'terminal_input') {
|
|
3443
|
+
if (ws.terminalProc && ws.terminalProc.stdin.writable) {
|
|
3444
|
+
ws.terminalProc.stdin.write(Buffer.from(data.data, 'base64'));
|
|
3445
|
+
}
|
|
3446
|
+
} else if (data.type === 'terminal_stop') {
|
|
3447
|
+
if (ws.terminalProc) {
|
|
3448
|
+
try { ws.terminalProc.kill(); } catch(e) {}
|
|
3449
|
+
ws.terminalProc = null;
|
|
3450
|
+
}
|
|
3422
3451
|
}
|
|
3423
3452
|
} catch (e) {
|
|
3424
3453
|
console.error('WebSocket message parse error:', e.message);
|
|
@@ -3432,6 +3461,7 @@ wss.on('connection', (ws, req) => {
|
|
|
3432
3461
|
|
|
3433
3462
|
ws.on('pong', () => { ws.isAlive = true; });
|
|
3434
3463
|
ws.on('close', () => {
|
|
3464
|
+
if (ws.terminalProc) { try { ws.terminalProc.kill(); } catch(e) {} ws.terminalProc = null; }
|
|
3435
3465
|
syncClients.delete(ws);
|
|
3436
3466
|
for (const sub of ws.subscriptions) {
|
|
3437
3467
|
const idx = subscriptionIndex.get(sub);
|
package/static/index.html
CHANGED
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
.app-shell {
|
|
64
64
|
display: flex;
|
|
65
65
|
height: 100dvh;
|
|
66
|
-
height: 100vh;
|
|
67
66
|
overflow: hidden;
|
|
68
67
|
padding-top: env(safe-area-inset-top);
|
|
69
68
|
padding-bottom: env(safe-area-inset-bottom);
|
|
@@ -496,7 +495,7 @@
|
|
|
496
495
|
.terminal-container {
|
|
497
496
|
flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #1e1e1e;
|
|
498
497
|
}
|
|
499
|
-
.terminal-output { flex: 1; overflow: hidden; }
|
|
498
|
+
.terminal-output { flex: 1; overflow: hidden; position: relative; }
|
|
500
499
|
|
|
501
500
|
.models-container {
|
|
502
501
|
flex: 1; display: flex; flex-direction: column; overflow-y: auto;
|
|
@@ -3042,12 +3041,12 @@
|
|
|
3042
3041
|
</div>
|
|
3043
3042
|
|
|
3044
3043
|
<!-- View toggle bar (hidden by default) -->
|
|
3045
|
-
<div class="view-toggle-bar" id="viewToggleBar"
|
|
3044
|
+
<div class="view-toggle-bar" id="viewToggleBar">
|
|
3046
3045
|
<button class="view-toggle-btn active" data-view="chat">Chat</button>
|
|
3047
3046
|
<button class="view-toggle-btn" data-view="files">Files</button>
|
|
3048
3047
|
<button class="view-toggle-btn" data-view="voice" style="display:none;">Voice</button>
|
|
3049
3048
|
<button class="view-toggle-btn" data-view="models">Models</button>
|
|
3050
|
-
<button class="view-toggle-btn" data-view="terminal" id="terminalTabBtn"
|
|
3049
|
+
<button class="view-toggle-btn" data-view="terminal" id="terminalTabBtn">Terminal</button>
|
|
3051
3050
|
</div>
|
|
3052
3051
|
|
|
3053
3052
|
<!-- Messages scroll area -->
|
|
@@ -3189,6 +3188,7 @@
|
|
|
3189
3188
|
<script defer src="/gm/js/client.js"></script>
|
|
3190
3189
|
<script type="module" src="/gm/js/voice.js"></script>
|
|
3191
3190
|
<script defer src="/gm/js/features.js"></script>
|
|
3191
|
+
<script defer src="/gm/js/terminal.js"></script>
|
|
3192
3192
|
<script defer src="/gm/js/script-runner.js"></script>
|
|
3193
3193
|
<script defer src="/gm/js/agent-auth.js"></script>
|
|
3194
3194
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var ws = null;
|
|
3
|
+
var term = null;
|
|
4
|
+
var fitAddon = null;
|
|
5
|
+
var termActive = false;
|
|
6
|
+
var BASE = window.__BASE_URL || '';
|
|
7
|
+
|
|
8
|
+
function getWsUrl() {
|
|
9
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
10
|
+
return proto + '//' + location.host + BASE + '/sync';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ensureTerm() {
|
|
14
|
+
var output = document.getElementById('terminalOutput');
|
|
15
|
+
if (!output) return false;
|
|
16
|
+
if (term) return true;
|
|
17
|
+
if (!window.Terminal || !window.FitAddon) return false;
|
|
18
|
+
|
|
19
|
+
term = new Terminal({
|
|
20
|
+
cursorBlink: true,
|
|
21
|
+
fontSize: 14,
|
|
22
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
23
|
+
theme: {
|
|
24
|
+
background: '#0d1117',
|
|
25
|
+
foreground: '#e6edf3',
|
|
26
|
+
cursor: '#e6edf3',
|
|
27
|
+
selectionBackground: '#3b4455'
|
|
28
|
+
},
|
|
29
|
+
convertEol: false,
|
|
30
|
+
scrollback: 5000
|
|
31
|
+
});
|
|
32
|
+
fitAddon = new FitAddon.FitAddon();
|
|
33
|
+
term.loadAddon(fitAddon);
|
|
34
|
+
output.innerHTML = '';
|
|
35
|
+
term.open(output);
|
|
36
|
+
fitAddon.fit();
|
|
37
|
+
|
|
38
|
+
term.onData(function(data) {
|
|
39
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
40
|
+
var encoded = btoa(unescape(encodeURIComponent(data)));
|
|
41
|
+
ws.send(JSON.stringify({ type: 'terminal_input', data: encoded }));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
window.addEventListener('resize', function() {
|
|
46
|
+
if (fitAddon) try { fitAddon.fit(); } catch(_) {}
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function connectAndStart() {
|
|
52
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
53
|
+
ws.send(JSON.stringify({ type: 'terminal_start', cwd: window.__STARTUP_CWD || undefined }));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
ws = new WebSocket(getWsUrl());
|
|
57
|
+
ws.onopen = function() {
|
|
58
|
+
ws.send(JSON.stringify({ type: 'terminal_start', cwd: window.__STARTUP_CWD || undefined }));
|
|
59
|
+
};
|
|
60
|
+
ws.onmessage = function(e) {
|
|
61
|
+
try {
|
|
62
|
+
var msg = JSON.parse(e.data);
|
|
63
|
+
if (msg.type === 'terminal_output' && term) {
|
|
64
|
+
var raw = msg.encoding === 'base64'
|
|
65
|
+
? decodeURIComponent(escape(atob(msg.data)))
|
|
66
|
+
: msg.data;
|
|
67
|
+
term.write(raw);
|
|
68
|
+
} else if (msg.type === 'terminal_exit' && term) {
|
|
69
|
+
term.write('\r\n[Process exited with code ' + msg.code + ']\r\n');
|
|
70
|
+
if (termActive) setTimeout(connectAndStart, 2000);
|
|
71
|
+
}
|
|
72
|
+
} catch(_) {}
|
|
73
|
+
};
|
|
74
|
+
ws.onclose = function() {
|
|
75
|
+
if (termActive) setTimeout(connectAndStart, 2000);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function startTerminal() {
|
|
80
|
+
if (!ensureTerm()) {
|
|
81
|
+
setTimeout(startTerminal, 200);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
termActive = true;
|
|
85
|
+
connectAndStart();
|
|
86
|
+
setTimeout(function() { if (fitAddon) try { fitAddon.fit(); } catch(_) {} }, 100);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stopTerminal() {
|
|
90
|
+
termActive = false;
|
|
91
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
92
|
+
ws.send(JSON.stringify({ type: 'terminal_stop' }));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
window.addEventListener('view-switched', function(e) {
|
|
97
|
+
if (e.detail.view === 'terminal') {
|
|
98
|
+
startTerminal();
|
|
99
|
+
} else if (termActive) {
|
|
100
|
+
stopTerminal();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
window.terminalModule = { start: startTerminal, stop: stopTerminal };
|
|
105
|
+
})();
|
package/static/js/voice.js
CHANGED
|
@@ -741,7 +741,6 @@
|
|
|
741
741
|
}
|
|
742
742
|
} else if (block.type === 'result') {
|
|
743
743
|
_voiceBreakNext = true;
|
|
744
|
-
addVoiceResultBlock(block, isNew);
|
|
745
744
|
} else {
|
|
746
745
|
_voiceBreakNext = true;
|
|
747
746
|
}
|
|
@@ -780,8 +779,6 @@
|
|
|
780
779
|
hasContent = true;
|
|
781
780
|
} else if (block.type === 'result') {
|
|
782
781
|
_voiceBreakNext = true;
|
|
783
|
-
addVoiceResultBlock(block, false);
|
|
784
|
-
hasContent = true;
|
|
785
782
|
} else {
|
|
786
783
|
_voiceBreakNext = true;
|
|
787
784
|
}
|