autodev-cli 1.4.48 → 1.4.50
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/out/commands/mcpOperate.js +150 -2
- package/out/commands/mcpOperate.js.map +1 -1
- package/out/fileBrowser.d.ts +21 -0
- package/out/fileBrowser.js +205 -0
- package/out/fileBrowser.js.map +1 -0
- package/out/git/gitRequest.d.ts +22 -0
- package/out/git/gitRequest.js +146 -0
- package/out/git/gitRequest.js.map +1 -0
- package/out/officeSocket.d.ts +6 -0
- package/out/officeSocket.js +6 -0
- package/out/officeSocket.js.map +1 -1
- package/out/rdp/manager.d.ts +30 -0
- package/out/rdp/manager.js +134 -0
- package/out/rdp/manager.js.map +1 -0
- package/out/vnc/manager.d.ts +22 -0
- package/out/vnc/manager.js +99 -0
- package/out/vnc/manager.js.map +1 -0
- package/out/webSocketPoller.d.ts +3 -10
- package/out/webSocketPoller.js +47 -417
- package/out/webSocketPoller.js.map +1 -1
- package/package.json +1 -1
package/out/webSocketPoller.js
CHANGED
|
@@ -39,14 +39,14 @@ const tls = __importStar(require("tls"));
|
|
|
39
39
|
const crypto = __importStar(require("crypto"));
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
-
const
|
|
43
|
-
const
|
|
42
|
+
const manager_1 = require("./vnc/manager");
|
|
43
|
+
const manager_2 = require("./rdp/manager");
|
|
44
44
|
const messageBuilder_1 = require("./messageBuilder");
|
|
45
45
|
const todo_1 = require("./todo");
|
|
46
46
|
const todoWriteManager_1 = require("./todoWriteManager");
|
|
47
47
|
const commands_1 = require("./core/commands");
|
|
48
|
-
const
|
|
49
|
-
const
|
|
48
|
+
const fileBrowser_1 = require("./fileBrowser");
|
|
49
|
+
const gitRequest_1 = require("./git/gitRequest");
|
|
50
50
|
const wsHandshake_1 = require("./wsHandshake");
|
|
51
51
|
// ---------------------------------------------------------------------------
|
|
52
52
|
// WebSocketPoller — persistent WS connection for ws:// / wss:// endpoints
|
|
@@ -64,14 +64,12 @@ class WebSocketPoller {
|
|
|
64
64
|
_reconnectTimer = null;
|
|
65
65
|
_log = () => { };
|
|
66
66
|
static RECONNECT_DELAY_MS = 5_000;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
// VNC / RDP remote-desktop sessions are managed by shared session managers so
|
|
68
|
+
// the MCP-only bridge (mcp-operate) can reuse the exact same machinery.
|
|
69
|
+
_vncManager = new manager_1.VncSessionManager((frame) => { this.sendFrame(frame); }, (m) => this._log(m));
|
|
70
|
+
_rdpManager = new manager_2.RdpSessionManager((frame) => { this.sendFrame(frame); }, (m) => this._log(m));
|
|
71
71
|
_gitEnabled = false;
|
|
72
72
|
_fileBrowserEnabled = false;
|
|
73
|
-
_vncEnabled = false;
|
|
74
|
-
_rdpEnabled = false;
|
|
75
73
|
// WebSocket message reassembly (fragmented frames: FIN=0 start + 0x0 continuations).
|
|
76
74
|
_fragOpcode = 0;
|
|
77
75
|
_fragChunks = [];
|
|
@@ -178,8 +176,8 @@ class WebSocketPoller {
|
|
|
178
176
|
destroy() {
|
|
179
177
|
this._destroyed = true;
|
|
180
178
|
this._stopHeartbeat();
|
|
181
|
-
this.
|
|
182
|
-
this.
|
|
179
|
+
this._vncManager.stopAll();
|
|
180
|
+
this._rdpManager.stopAll();
|
|
183
181
|
if (this._reconnectTimer) {
|
|
184
182
|
clearTimeout(this._reconnectTimer);
|
|
185
183
|
this._reconnectTimer = null;
|
|
@@ -303,8 +301,8 @@ class WebSocketPoller {
|
|
|
303
301
|
}
|
|
304
302
|
_scheduleReconnect() {
|
|
305
303
|
this._stopHeartbeat();
|
|
306
|
-
this.
|
|
307
|
-
this.
|
|
304
|
+
this._vncManager.stopAll();
|
|
305
|
+
this._rdpManager.stopAll();
|
|
308
306
|
if (this._destroyed) {
|
|
309
307
|
return;
|
|
310
308
|
}
|
|
@@ -511,133 +509,13 @@ class WebSocketPoller {
|
|
|
511
509
|
}
|
|
512
510
|
return;
|
|
513
511
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
const action = msg['action'];
|
|
520
|
-
if (action === 'start') {
|
|
521
|
-
const sessionId = msg['sessionId'];
|
|
522
|
-
const port = Number(msg['port'] ?? 5900);
|
|
523
|
-
// Prefer password from server frame; fall back to locally-configured password
|
|
524
|
-
const password = msg['password'] || this._vncPassword;
|
|
525
|
-
this._log(`VNC session start: ${sessionId} → port ${port}`);
|
|
526
|
-
const session = new vnc_1.VncSession(sessionId, (frame) => this.sendFrame(frame));
|
|
527
|
-
this._vncSessions.set(sessionId, session);
|
|
528
|
-
session.start(port, password).catch((err) => {
|
|
529
|
-
this._log(`VNC session ${sessionId} failed to start: ${err.message}`);
|
|
530
|
-
this._vncSessions.delete(sessionId);
|
|
531
|
-
this.sendFrame({ type: 'vnc_close', sessionId, reason: err.message });
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
if (msgType === 'vnc_input') {
|
|
537
|
-
const sessionId = msg['sessionId'];
|
|
538
|
-
const event = msg['event'];
|
|
539
|
-
if (sessionId && event) {
|
|
540
|
-
this._vncSessions.get(sessionId)?.handleInput(event);
|
|
541
|
-
}
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
if (msgType === 'vnc_close') {
|
|
545
|
-
const sessionId = msg['sessionId'];
|
|
546
|
-
if (sessionId) {
|
|
547
|
-
this._log(`VNC session closed: ${sessionId}`);
|
|
548
|
-
this._vncSessions.get(sessionId)?.stop();
|
|
549
|
-
this._vncSessions.delete(sessionId);
|
|
550
|
-
}
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
// ── RDP frames from pixel-office ─────────────────────────────────────────
|
|
554
|
-
if (msgType === 'rdp_session') {
|
|
555
|
-
if (!this._rdpEnabled) {
|
|
556
|
-
this._log('rdp_session ignored — RDP not enabled');
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
const action = msg['action'];
|
|
560
|
-
if (action === 'start') {
|
|
561
|
-
const sessionId = msg['sessionId'];
|
|
562
|
-
const opts = {
|
|
563
|
-
// Host/port come ONLY from local settings — never from the WS frame.
|
|
564
|
-
// A frame-supplied host/port would let a remote party open an outbound
|
|
565
|
-
// RDP bridge to an arbitrary target (SSRF / internal-network pivot).
|
|
566
|
-
// Default to loopback (xrdp runs on the same machine as the extension).
|
|
567
|
-
host: this._rdpSettings.host || '127.0.0.1',
|
|
568
|
-
port: this._rdpSettings.port ?? 3389,
|
|
569
|
-
// credentials never sent from server — always use local settings
|
|
570
|
-
username: this._rdpSettings.username || msg['username'],
|
|
571
|
-
password: this._rdpSettings.password || msg['password'],
|
|
572
|
-
domain: this._rdpSettings.domain || msg['domain'],
|
|
573
|
-
width: msg['width'] ? Number(msg['width']) : undefined,
|
|
574
|
-
height: msg['height'] ? Number(msg['height']) : undefined,
|
|
575
|
-
colorDepth: msg['colorDepth'] ? Number(msg['colorDepth']) : undefined,
|
|
576
|
-
};
|
|
577
|
-
this._log(`RDP session start: ${sessionId} → ${opts.host}:${opts.port ?? 3389}`);
|
|
578
|
-
// Send Guacamole token to browser so it can connect via guacamole-lite
|
|
579
|
-
// (guacd + guacamole-lite must be running on the same host as xrdp, port 4567)
|
|
580
|
-
if (opts.username || opts.password) {
|
|
581
|
-
const guacSettings = {
|
|
582
|
-
hostname: opts.host,
|
|
583
|
-
port: String(opts.port ?? 3389),
|
|
584
|
-
'ignore-cert': true,
|
|
585
|
-
};
|
|
586
|
-
if (opts.username) {
|
|
587
|
-
guacSettings['username'] = opts.username;
|
|
588
|
-
}
|
|
589
|
-
if (opts.password) {
|
|
590
|
-
guacSettings['password'] = opts.password;
|
|
591
|
-
}
|
|
592
|
-
if (opts.domain) {
|
|
593
|
-
guacSettings['domain'] = opts.domain;
|
|
594
|
-
}
|
|
595
|
-
if (opts.width) {
|
|
596
|
-
guacSettings['width'] = opts.width;
|
|
597
|
-
}
|
|
598
|
-
if (opts.height) {
|
|
599
|
-
guacSettings['height'] = opts.height;
|
|
600
|
-
}
|
|
601
|
-
guacSettings['color-depth'] = opts.colorDepth ?? 24;
|
|
602
|
-
const tokenPayload = JSON.stringify({ connection: { type: 'rdp', settings: guacSettings } });
|
|
603
|
-
const token = Buffer.from(tokenPayload).toString('base64');
|
|
604
|
-
// Use configured WSS URL (for HTTPS frontends), else fall back to plain WS on port 4567
|
|
605
|
-
const guacWsUrl = this._rdpSettings.guacWsUrl || `ws://${opts.host}:4567`;
|
|
606
|
-
this.sendFrame({
|
|
607
|
-
type: 'rdp_guac_token',
|
|
608
|
-
sessionId,
|
|
609
|
-
wsUrl: guacWsUrl,
|
|
610
|
-
token,
|
|
611
|
-
width: opts.width ?? 1280,
|
|
612
|
-
height: opts.height ?? 800,
|
|
613
|
-
});
|
|
614
|
-
this._log(`RDP guac token sent for session ${sessionId} → ${guacWsUrl}`);
|
|
615
|
-
}
|
|
616
|
-
const session = new rdp_1.RdpSession(sessionId, (frame) => this.sendFrame(frame), (msg) => this._log(msg));
|
|
617
|
-
this._rdpSessions.set(sessionId, session);
|
|
618
|
-
session.start(opts).catch((err) => {
|
|
619
|
-
this._log(`RDP session ${sessionId} failed to start: ${err.message}`);
|
|
620
|
-
this._rdpSessions.delete(sessionId);
|
|
621
|
-
this.sendFrame({ type: 'rdp_close', sessionId, reason: err.message });
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (msgType === 'rdp_input') {
|
|
627
|
-
const sessionId = msg['sessionId'];
|
|
628
|
-
const event = msg['event'];
|
|
629
|
-
if (sessionId && event) {
|
|
630
|
-
this._rdpSessions.get(sessionId)?.handleInput(event);
|
|
631
|
-
}
|
|
512
|
+
// ── VNC / RDP remote-desktop frames from pixel-office ────────────────────
|
|
513
|
+
// Delegated to shared session managers (same machinery the mcp-operate
|
|
514
|
+
// bridge uses). Each returns true if it consumed the frame.
|
|
515
|
+
if (msgType && this._vncManager.handleFrame(msgType, msg)) {
|
|
632
516
|
return;
|
|
633
517
|
}
|
|
634
|
-
if (msgType
|
|
635
|
-
const sessionId = msg['sessionId'];
|
|
636
|
-
if (sessionId) {
|
|
637
|
-
this._log(`RDP session closed: ${sessionId}`);
|
|
638
|
-
this._rdpSessions.get(sessionId)?.stop();
|
|
639
|
-
this._rdpSessions.delete(sessionId);
|
|
640
|
-
}
|
|
518
|
+
if (msgType && this._rdpManager.handleFrame(msgType, msg)) {
|
|
641
519
|
return;
|
|
642
520
|
}
|
|
643
521
|
// ── A2A task frame ────────────────────────────────────────────────────────
|
|
@@ -773,286 +651,38 @@ class WebSocketPoller {
|
|
|
773
651
|
}
|
|
774
652
|
/** Handle a file-browser request from the server (originated by the browser UI). */
|
|
775
653
|
_handleFbRequest(requestId, action, relPath, content, newPath, query) {
|
|
776
|
-
|
|
777
|
-
this.
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
// Resolve and validate path is within workspace root. The root itself is
|
|
789
|
-
// permitted for read-only actions (list/search) but never for mutations.
|
|
790
|
-
// Containment is lexical AND canonical (realpath) — a workspace symlink
|
|
791
|
-
// pointing outside must not let a remote fb_request read/write host files.
|
|
792
|
-
const resolveSafe = (rel, allowRoot) => (0, pathSafe_1.resolveWithinRoot)(root, rel, allowRoot);
|
|
793
|
-
const MUTATING = new Set(['write', 'delete', 'rename', 'mkdir']);
|
|
794
|
-
const allowRoot = !MUTATING.has(action);
|
|
795
|
-
const absPath = resolveSafe(relPath, allowRoot);
|
|
796
|
-
if (!absPath) {
|
|
797
|
-
respond(false, {
|
|
798
|
-
error: allowRoot ? 'Path outside workspace' : 'Refusing to modify workspace root',
|
|
799
|
-
});
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
try {
|
|
803
|
-
switch (action) {
|
|
804
|
-
case 'list': {
|
|
805
|
-
const entries = fs.readdirSync(absPath, { withFileTypes: true }).map(e => {
|
|
806
|
-
const stat = (() => { try {
|
|
807
|
-
return fs.statSync(path.join(absPath, e.name));
|
|
808
|
-
}
|
|
809
|
-
catch {
|
|
810
|
-
return null;
|
|
811
|
-
} })();
|
|
812
|
-
return {
|
|
813
|
-
name: e.name,
|
|
814
|
-
type: e.isDirectory() ? 'dir' : 'file',
|
|
815
|
-
size: stat?.size ?? 0,
|
|
816
|
-
mtime: stat?.mtimeMs ?? 0,
|
|
817
|
-
};
|
|
818
|
-
});
|
|
819
|
-
// Dirs first, then files; both alphabetical
|
|
820
|
-
entries.sort((a, b) => {
|
|
821
|
-
if (a.type !== b.type) {
|
|
822
|
-
return a.type === 'dir' ? -1 : 1;
|
|
823
|
-
}
|
|
824
|
-
return a.name.localeCompare(b.name);
|
|
825
|
-
});
|
|
826
|
-
respond(true, { entries });
|
|
827
|
-
break;
|
|
828
|
-
}
|
|
829
|
-
case 'read': {
|
|
830
|
-
const stat = fs.statSync(absPath);
|
|
831
|
-
const MAX_BYTES = 1_048_576; // 1 MB
|
|
832
|
-
if (stat.size > MAX_BYTES) {
|
|
833
|
-
respond(false, { error: `File too large (${stat.size} bytes, limit 1 MB)` });
|
|
834
|
-
break;
|
|
835
|
-
}
|
|
836
|
-
// Binary detection: read first 512 bytes and check for null bytes
|
|
837
|
-
const sample = Buffer.allocUnsafe(Math.min(512, stat.size));
|
|
838
|
-
const fd = fs.openSync(absPath, 'r');
|
|
839
|
-
fs.readSync(fd, sample, 0, sample.length, 0);
|
|
840
|
-
fs.closeSync(fd);
|
|
841
|
-
const isBinary = sample.includes(0x00);
|
|
842
|
-
if (isBinary) {
|
|
843
|
-
respond(false, { error: 'Binary file — cannot display' });
|
|
844
|
-
break;
|
|
845
|
-
}
|
|
846
|
-
const fileContent = fs.readFileSync(absPath, 'utf8');
|
|
847
|
-
respond(true, { content: fileContent });
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
case 'write': {
|
|
851
|
-
if (content === undefined) {
|
|
852
|
-
respond(false, { error: 'No content provided' });
|
|
853
|
-
break;
|
|
854
|
-
}
|
|
855
|
-
fs.writeFileSync(absPath, content, 'utf8');
|
|
856
|
-
respond(true);
|
|
857
|
-
break;
|
|
858
|
-
}
|
|
859
|
-
case 'delete': {
|
|
860
|
-
fs.rmSync(absPath, { recursive: true, force: true });
|
|
861
|
-
respond(true);
|
|
862
|
-
break;
|
|
863
|
-
}
|
|
864
|
-
case 'rename': {
|
|
865
|
-
if (!newPath) {
|
|
866
|
-
respond(false, { error: 'No newPath provided' });
|
|
867
|
-
break;
|
|
868
|
-
}
|
|
869
|
-
const absNewPath = resolveSafe(newPath, false);
|
|
870
|
-
if (!absNewPath) {
|
|
871
|
-
respond(false, { error: 'newPath outside workspace' });
|
|
872
|
-
break;
|
|
873
|
-
}
|
|
874
|
-
fs.renameSync(absPath, absNewPath);
|
|
875
|
-
respond(true);
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
case 'download': {
|
|
879
|
-
const stat = fs.statSync(absPath);
|
|
880
|
-
const MAX_DOWNLOAD_BYTES = 25 * 1_048_576; // 25 MB — base64 ~1.33x in heap
|
|
881
|
-
if (stat.size > MAX_DOWNLOAD_BYTES) {
|
|
882
|
-
respond(false, { error: `File too large (${stat.size} bytes, limit 25 MB)` });
|
|
883
|
-
break;
|
|
884
|
-
}
|
|
885
|
-
const buf = fs.readFileSync(absPath);
|
|
886
|
-
respond(true, { base64: buf.toString('base64') });
|
|
887
|
-
break;
|
|
888
|
-
}
|
|
889
|
-
case 'mkdir': {
|
|
890
|
-
fs.mkdirSync(absPath, { recursive: true });
|
|
891
|
-
respond(true);
|
|
892
|
-
break;
|
|
893
|
-
}
|
|
894
|
-
case 'search': {
|
|
895
|
-
const rawQuery = (query ?? '').toLowerCase().trim();
|
|
896
|
-
if (!rawQuery) {
|
|
897
|
-
respond(true, { results: [] });
|
|
898
|
-
break;
|
|
899
|
-
}
|
|
900
|
-
const results = [];
|
|
901
|
-
const walk = (dir, relDir, depth) => {
|
|
902
|
-
if (depth > 8 || results.length >= 300)
|
|
903
|
-
return;
|
|
904
|
-
let dirents;
|
|
905
|
-
try {
|
|
906
|
-
dirents = fs.readdirSync(dir, { withFileTypes: true });
|
|
907
|
-
}
|
|
908
|
-
catch {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
for (const e of dirents) {
|
|
912
|
-
if (results.length >= 300)
|
|
913
|
-
break;
|
|
914
|
-
const rel = relDir ? `${relDir}/${e.name}` : e.name;
|
|
915
|
-
if (e.name.toLowerCase().includes(rawQuery)) {
|
|
916
|
-
results.push({ path: rel, name: e.name, type: e.isDirectory() ? 'dir' : 'file' });
|
|
917
|
-
}
|
|
918
|
-
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && e.name !== 'vendor') {
|
|
919
|
-
walk(path.join(dir, e.name), rel, depth + 1);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
walk(absPath, '', 0);
|
|
924
|
-
respond(true, { results });
|
|
925
|
-
break;
|
|
926
|
-
}
|
|
927
|
-
default:
|
|
928
|
-
respond(false, { error: `Unknown action: ${action}` });
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
catch (err) {
|
|
932
|
-
respond(false, { error: String(err) });
|
|
933
|
-
}
|
|
654
|
+
(0, fileBrowser_1.handleFbRequest)({
|
|
655
|
+
root: this._workspaceRoot ?? null,
|
|
656
|
+
enabled: this._fileBrowserEnabled,
|
|
657
|
+
requestId,
|
|
658
|
+
action,
|
|
659
|
+
relPath,
|
|
660
|
+
content,
|
|
661
|
+
newPath,
|
|
662
|
+
query,
|
|
663
|
+
sendFrame: (frame) => { this.sendFrame(frame); },
|
|
664
|
+
log: (m) => this._log(m),
|
|
665
|
+
});
|
|
934
666
|
}
|
|
667
|
+
/** Handle a git-panel request from the server (originated by the browser UI). */
|
|
935
668
|
_handleGitRequest(requestId, action, filePath, staged, message, branch, hash) {
|
|
936
|
-
|
|
937
|
-
this.
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
// (filePath) must resolve inside the workspace root both lexically AND after
|
|
950
|
-
// resolving symlinks; otherwise a git_request could read arbitrary host
|
|
951
|
-
// files (e.g. path '../../.claude/.credentials.json' via the readFileSync
|
|
952
|
-
// fallback in getDiff, or leak them through `git diff -- <path>`). An empty
|
|
953
|
-
// filePath means "whole repo" and is permitted (allowRoot).
|
|
954
|
-
if (filePath !== undefined && filePath !== '') {
|
|
955
|
-
if ((0, pathSafe_1.resolveWithinRoot)(root, filePath, true) === null) {
|
|
956
|
-
respond(false, undefined, 'Path outside workspace');
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
(async () => {
|
|
961
|
-
try {
|
|
962
|
-
switch (action) {
|
|
963
|
-
case 'status': {
|
|
964
|
-
const status = await gitService.getStatus(root);
|
|
965
|
-
respond(true, { status });
|
|
966
|
-
break;
|
|
967
|
-
}
|
|
968
|
-
case 'log': {
|
|
969
|
-
const commits = await gitService.getLog(root);
|
|
970
|
-
respond(true, { commits });
|
|
971
|
-
break;
|
|
972
|
-
}
|
|
973
|
-
case 'diff': {
|
|
974
|
-
const diff = await gitService.getDiff(root, filePath ?? '', staged ?? false);
|
|
975
|
-
respond(true, { diff });
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
|
-
case 'commit_diff': {
|
|
979
|
-
const diff = await gitService.getCommitDiff(root, hash ?? '', filePath);
|
|
980
|
-
respond(true, { diff });
|
|
981
|
-
break;
|
|
982
|
-
}
|
|
983
|
-
case 'stage': {
|
|
984
|
-
if (filePath)
|
|
985
|
-
await gitService.stageFile(root, filePath);
|
|
986
|
-
else
|
|
987
|
-
await gitService.stageAll(root);
|
|
988
|
-
respond(true);
|
|
989
|
-
break;
|
|
990
|
-
}
|
|
991
|
-
case 'unstage': {
|
|
992
|
-
if (!filePath) {
|
|
993
|
-
respond(false, undefined, 'path required');
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
await gitService.unstageFile(root, filePath);
|
|
997
|
-
respond(true);
|
|
998
|
-
break;
|
|
999
|
-
}
|
|
1000
|
-
case 'commit': {
|
|
1001
|
-
if (!message) {
|
|
1002
|
-
respond(false, undefined, 'message required');
|
|
1003
|
-
break;
|
|
1004
|
-
}
|
|
1005
|
-
const commitHash = await gitService.commit(root, message);
|
|
1006
|
-
respond(true, { hash: commitHash });
|
|
1007
|
-
break;
|
|
1008
|
-
}
|
|
1009
|
-
case 'fetch': {
|
|
1010
|
-
await gitService.fetchOrigin(root);
|
|
1011
|
-
respond(true);
|
|
1012
|
-
break;
|
|
1013
|
-
}
|
|
1014
|
-
case 'branches': {
|
|
1015
|
-
const branches = await gitService.getBranches(root);
|
|
1016
|
-
respond(true, { branches });
|
|
1017
|
-
break;
|
|
1018
|
-
}
|
|
1019
|
-
case 'checkout': {
|
|
1020
|
-
if (!branch) {
|
|
1021
|
-
respond(false, undefined, 'branch required');
|
|
1022
|
-
break;
|
|
1023
|
-
}
|
|
1024
|
-
await gitService.checkoutBranch(root, branch);
|
|
1025
|
-
respond(true);
|
|
1026
|
-
break;
|
|
1027
|
-
}
|
|
1028
|
-
default:
|
|
1029
|
-
respond(false, undefined, `Unknown git action: ${action}`);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
catch (err) {
|
|
1033
|
-
respond(false, undefined, String(err));
|
|
1034
|
-
}
|
|
1035
|
-
})();
|
|
1036
|
-
}
|
|
1037
|
-
/** Stop all active VNC sessions (called on destroy/reconnect). */
|
|
1038
|
-
_stopAllVncSessions() {
|
|
1039
|
-
for (const [id, session] of this._vncSessions) {
|
|
1040
|
-
this._log(`VNC session terminated (disconnect): ${id}`);
|
|
1041
|
-
session.stop();
|
|
1042
|
-
}
|
|
1043
|
-
this._vncSessions.clear();
|
|
1044
|
-
}
|
|
1045
|
-
/** Stop all active RDP sessions (called on destroy/reconnect). */
|
|
1046
|
-
_stopAllRdpSessions() {
|
|
1047
|
-
for (const [id, session] of this._rdpSessions) {
|
|
1048
|
-
this._log(`RDP session terminated (disconnect): ${id}`);
|
|
1049
|
-
session.stop();
|
|
1050
|
-
}
|
|
1051
|
-
this._rdpSessions.clear();
|
|
669
|
+
(0, gitRequest_1.handleGitRequest)({
|
|
670
|
+
root: this._workspaceRoot ?? null,
|
|
671
|
+
enabled: this._gitEnabled,
|
|
672
|
+
requestId,
|
|
673
|
+
action,
|
|
674
|
+
filePath,
|
|
675
|
+
staged,
|
|
676
|
+
message,
|
|
677
|
+
branch,
|
|
678
|
+
hash,
|
|
679
|
+
sendFrame: (frame) => { this.sendFrame(frame); },
|
|
680
|
+
log: (m) => this._log(m),
|
|
681
|
+
});
|
|
1052
682
|
}
|
|
1053
683
|
/** Update the VNC password used for incoming vnc_session requests. */
|
|
1054
684
|
setVncPassword(password) {
|
|
1055
|
-
this.
|
|
685
|
+
this._vncManager.setPassword(password);
|
|
1056
686
|
}
|
|
1057
687
|
setGitEnabled(enabled) {
|
|
1058
688
|
this._gitEnabled = enabled;
|
|
@@ -1061,13 +691,13 @@ class WebSocketPoller {
|
|
|
1061
691
|
this._fileBrowserEnabled = enabled;
|
|
1062
692
|
}
|
|
1063
693
|
setVncEnabled(enabled) {
|
|
1064
|
-
this.
|
|
694
|
+
this._vncManager.setEnabled(enabled);
|
|
1065
695
|
}
|
|
1066
696
|
setRdpEnabled(enabled) {
|
|
1067
|
-
this.
|
|
697
|
+
this._rdpManager.setEnabled(enabled);
|
|
1068
698
|
}
|
|
1069
699
|
setRdpSettings(s) {
|
|
1070
|
-
this.
|
|
700
|
+
this._rdpManager.setSettings(s);
|
|
1071
701
|
}
|
|
1072
702
|
/**
|
|
1073
703
|
* Send a JSON payload to the server over the WebSocket connection.
|