@prmichaelsen/acp-mcp 0.5.1 → 0.7.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.
- package/CHANGELOG.md +66 -0
- package/README.md +15 -6
- package/agent/design/local.progress-streaming.md +940 -0
- package/agent/milestones/milestone-4-progress-streaming-server.md +84 -0
- package/agent/milestones/milestone-5-progress-streaming-wrapper.md +71 -0
- package/agent/milestones/milestone-6-progress-streaming-client.md +79 -0
- package/agent/progress.yaml +145 -16
- package/agent/tasks/milestone-4-progress-streaming-server/task-6-add-ssh-stream-execution.md +149 -0
- package/agent/tasks/milestone-4-progress-streaming-server/task-7-implement-progress-streaming.md +191 -0
- package/agent/tasks/milestone-4-progress-streaming-server/task-8-update-server-handlers.md +109 -0
- package/agent/tasks/milestone-4-progress-streaming-server/task-9-testing-documentation.md +192 -0
- package/agent/tasks/task-5-fix-incomplete-directory-listings.md +170 -0
- package/dist/server-factory.js +299 -28
- package/dist/server-factory.js.map +4 -4
- package/dist/server.js +299 -28
- package/dist/server.js.map +4 -4
- package/dist/tools/acp-remote-execute-command.d.ts +4 -1
- package/dist/types/file-entry.d.ts +88 -0
- package/dist/utils/ssh-connection.d.ts +26 -5
- package/package.json +1 -1
- package/src/server-factory.ts +3 -2
- package/src/server.ts +3 -2
- package/src/tools/acp-remote-execute-command.ts +116 -7
- package/src/tools/acp-remote-list-files.ts +27 -21
- package/src/types/file-entry.ts +123 -0
- package/src/utils/ssh-connection.ts +189 -5
package/dist/server.js
CHANGED
|
@@ -436,7 +436,7 @@ function loadSSHPrivateKey() {
|
|
|
436
436
|
// src/tools/acp-remote-list-files.ts
|
|
437
437
|
var acpRemoteListFilesTool = {
|
|
438
438
|
name: "acp_remote_list_files",
|
|
439
|
-
description: "List files and directories in a specified path on the remote machine via SSH",
|
|
439
|
+
description: "List files and directories in a specified path on the remote machine via SSH. Returns comprehensive metadata including permissions, timestamps, size, and ownership. Includes hidden files by default.",
|
|
440
440
|
inputSchema: {
|
|
441
441
|
type: "object",
|
|
442
442
|
properties: {
|
|
@@ -448,20 +448,26 @@ var acpRemoteListFilesTool = {
|
|
|
448
448
|
type: "boolean",
|
|
449
449
|
description: "Whether to list files recursively",
|
|
450
450
|
default: false
|
|
451
|
+
},
|
|
452
|
+
includeHidden: {
|
|
453
|
+
type: "boolean",
|
|
454
|
+
description: "Whether to include hidden files (starting with .)",
|
|
455
|
+
default: true
|
|
451
456
|
}
|
|
452
457
|
},
|
|
453
458
|
required: ["path"]
|
|
454
459
|
}
|
|
455
460
|
};
|
|
456
461
|
async function handleAcpRemoteListFiles(args, sshConnection) {
|
|
457
|
-
const { path, recursive = false } = args;
|
|
462
|
+
const { path, recursive = false, includeHidden = true } = args;
|
|
458
463
|
try {
|
|
459
|
-
const
|
|
464
|
+
const entries = await listRemoteFiles(sshConnection, path, recursive, includeHidden);
|
|
465
|
+
const output = JSON.stringify(entries, null, 2);
|
|
460
466
|
return {
|
|
461
467
|
content: [
|
|
462
468
|
{
|
|
463
469
|
type: "text",
|
|
464
|
-
text:
|
|
470
|
+
text: output
|
|
465
471
|
}
|
|
466
472
|
]
|
|
467
473
|
};
|
|
@@ -477,22 +483,18 @@ async function handleAcpRemoteListFiles(args, sshConnection) {
|
|
|
477
483
|
};
|
|
478
484
|
}
|
|
479
485
|
}
|
|
480
|
-
async function listRemoteFiles(ssh, dirPath, recursive) {
|
|
481
|
-
const entries = await ssh.listFiles(dirPath);
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const subFiles = await listRemoteFiles(ssh, fullPath, recursive);
|
|
489
|
-
files.push(...subFiles);
|
|
486
|
+
async function listRemoteFiles(ssh, dirPath, recursive, includeHidden) {
|
|
487
|
+
const entries = await ssh.listFiles(dirPath, includeHidden);
|
|
488
|
+
const allEntries = [...entries];
|
|
489
|
+
if (recursive) {
|
|
490
|
+
for (const entry of entries) {
|
|
491
|
+
if (entry.type === "directory") {
|
|
492
|
+
const subEntries = await listRemoteFiles(ssh, entry.path, recursive, includeHidden);
|
|
493
|
+
allEntries.push(...subEntries);
|
|
490
494
|
}
|
|
491
|
-
} else {
|
|
492
|
-
files.push(fullPath);
|
|
493
495
|
}
|
|
494
496
|
}
|
|
495
|
-
return
|
|
497
|
+
return allEntries;
|
|
496
498
|
}
|
|
497
499
|
|
|
498
500
|
// src/utils/logger.ts
|
|
@@ -605,7 +607,7 @@ var logger = new Logger();
|
|
|
605
607
|
// src/tools/acp-remote-execute-command.ts
|
|
606
608
|
var acpRemoteExecuteCommandTool = {
|
|
607
609
|
name: "acp_remote_execute_command",
|
|
608
|
-
description: "Execute a shell command on the remote machine via SSH",
|
|
610
|
+
description: "Execute a shell command on the remote machine via SSH. Supports real-time progress streaming if client provides progressToken.",
|
|
609
611
|
inputSchema: {
|
|
610
612
|
type: "object",
|
|
611
613
|
properties: {
|
|
@@ -619,17 +621,21 @@ var acpRemoteExecuteCommandTool = {
|
|
|
619
621
|
},
|
|
620
622
|
timeout: {
|
|
621
623
|
type: "number",
|
|
622
|
-
description: "Timeout in seconds (default: 30)",
|
|
624
|
+
description: "Timeout in seconds (default: 30). Ignored if progress streaming is used.",
|
|
623
625
|
default: 30
|
|
624
626
|
}
|
|
625
627
|
},
|
|
626
628
|
required: ["command"]
|
|
627
629
|
}
|
|
628
630
|
};
|
|
629
|
-
async function handleAcpRemoteExecuteCommand(args, sshConnection) {
|
|
631
|
+
async function handleAcpRemoteExecuteCommand(args, sshConnection, extra, server) {
|
|
630
632
|
const { command, cwd, timeout = 30 } = args;
|
|
631
|
-
|
|
633
|
+
const progressToken = extra?._meta?.progressToken;
|
|
634
|
+
logger.debug("Executing remote command", { command, cwd, timeout, hasProgressToken: !!progressToken });
|
|
632
635
|
try {
|
|
636
|
+
if (progressToken && server) {
|
|
637
|
+
return await executeWithProgress(command, cwd, sshConnection, progressToken, server);
|
|
638
|
+
}
|
|
633
639
|
const fullCommand = cwd ? `cd ${cwd} && ${command}` : command;
|
|
634
640
|
const result = await sshConnection.execWithTimeout(fullCommand, timeout);
|
|
635
641
|
logger.debug("Command execution result", {
|
|
@@ -670,6 +676,75 @@ async function handleAcpRemoteExecuteCommand(args, sshConnection) {
|
|
|
670
676
|
};
|
|
671
677
|
}
|
|
672
678
|
}
|
|
679
|
+
async function executeWithProgress(command, cwd, sshConnection, progressToken, server) {
|
|
680
|
+
logger.debug("Starting streaming execution", { command, cwd, progressToken });
|
|
681
|
+
const { stream, stderr: stderrStream, exitCode } = await sshConnection.execStream(command, cwd);
|
|
682
|
+
let stdout = "";
|
|
683
|
+
let stderr = "";
|
|
684
|
+
let bytesReceived = 0;
|
|
685
|
+
let lastProgressTime = 0;
|
|
686
|
+
const MIN_PROGRESS_INTERVAL = 100;
|
|
687
|
+
stream.on("data", (chunk) => {
|
|
688
|
+
const text = chunk.toString();
|
|
689
|
+
stdout += text;
|
|
690
|
+
bytesReceived += chunk.length;
|
|
691
|
+
const now = Date.now();
|
|
692
|
+
if (now - lastProgressTime >= MIN_PROGRESS_INTERVAL) {
|
|
693
|
+
try {
|
|
694
|
+
server.notification({
|
|
695
|
+
method: "notifications/progress",
|
|
696
|
+
params: {
|
|
697
|
+
progressToken,
|
|
698
|
+
progress: bytesReceived,
|
|
699
|
+
total: void 0,
|
|
700
|
+
// Unknown total for streaming
|
|
701
|
+
message: text
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
lastProgressTime = now;
|
|
705
|
+
logger.debug("Progress notification sent", {
|
|
706
|
+
progressToken,
|
|
707
|
+
bytes: bytesReceived,
|
|
708
|
+
chunkSize: chunk.length
|
|
709
|
+
});
|
|
710
|
+
} catch (error) {
|
|
711
|
+
logger.warn("Failed to send progress notification", {
|
|
712
|
+
error: error instanceof Error ? error.message : String(error)
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
stderrStream.on("data", (chunk) => {
|
|
718
|
+
stderr += chunk.toString();
|
|
719
|
+
});
|
|
720
|
+
stream.on("error", (error) => {
|
|
721
|
+
logger.error("Stream error during execution", {
|
|
722
|
+
command,
|
|
723
|
+
error: error.message
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
const finalExitCode = await exitCode;
|
|
727
|
+
logger.debug("Streaming execution completed", {
|
|
728
|
+
command,
|
|
729
|
+
exitCode: finalExitCode,
|
|
730
|
+
stdoutBytes: stdout.length,
|
|
731
|
+
stderrBytes: stderr.length
|
|
732
|
+
});
|
|
733
|
+
const output = {
|
|
734
|
+
stdout,
|
|
735
|
+
stderr,
|
|
736
|
+
exitCode: finalExitCode,
|
|
737
|
+
timedOut: false,
|
|
738
|
+
streamed: true
|
|
739
|
+
// Indicate this was streamed
|
|
740
|
+
};
|
|
741
|
+
return {
|
|
742
|
+
content: [{
|
|
743
|
+
type: "text",
|
|
744
|
+
text: JSON.stringify(output, null, 2)
|
|
745
|
+
}]
|
|
746
|
+
};
|
|
747
|
+
}
|
|
673
748
|
|
|
674
749
|
// src/tools/acp-remote-read-file.ts
|
|
675
750
|
var acpRemoteReadFileTool = {
|
|
@@ -812,6 +887,54 @@ async function handleAcpRemoteWriteFile(args, sshConnection) {
|
|
|
812
887
|
|
|
813
888
|
// src/utils/ssh-connection.ts
|
|
814
889
|
import { Client } from "ssh2";
|
|
890
|
+
|
|
891
|
+
// src/types/file-entry.ts
|
|
892
|
+
function modeToPermissionString(mode) {
|
|
893
|
+
const perms = [
|
|
894
|
+
mode & 256 ? "r" : "-",
|
|
895
|
+
mode & 128 ? "w" : "-",
|
|
896
|
+
mode & 64 ? "x" : "-",
|
|
897
|
+
mode & 32 ? "r" : "-",
|
|
898
|
+
mode & 16 ? "w" : "-",
|
|
899
|
+
mode & 8 ? "x" : "-",
|
|
900
|
+
mode & 4 ? "r" : "-",
|
|
901
|
+
mode & 2 ? "w" : "-",
|
|
902
|
+
mode & 1 ? "x" : "-"
|
|
903
|
+
];
|
|
904
|
+
return perms.join("");
|
|
905
|
+
}
|
|
906
|
+
function parsePermissions(mode) {
|
|
907
|
+
return {
|
|
908
|
+
mode,
|
|
909
|
+
string: modeToPermissionString(mode),
|
|
910
|
+
owner: {
|
|
911
|
+
read: (mode & 256) !== 0,
|
|
912
|
+
write: (mode & 128) !== 0,
|
|
913
|
+
execute: (mode & 64) !== 0
|
|
914
|
+
},
|
|
915
|
+
group: {
|
|
916
|
+
read: (mode & 32) !== 0,
|
|
917
|
+
write: (mode & 16) !== 0,
|
|
918
|
+
execute: (mode & 8) !== 0
|
|
919
|
+
},
|
|
920
|
+
others: {
|
|
921
|
+
read: (mode & 4) !== 0,
|
|
922
|
+
write: (mode & 2) !== 0,
|
|
923
|
+
execute: (mode & 1) !== 0
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function getFileType(stats) {
|
|
928
|
+
if (stats.isDirectory())
|
|
929
|
+
return "directory";
|
|
930
|
+
if (stats.isFile())
|
|
931
|
+
return "file";
|
|
932
|
+
if (stats.isSymbolicLink())
|
|
933
|
+
return "symlink";
|
|
934
|
+
return "other";
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/utils/ssh-connection.ts
|
|
815
938
|
var SSHConnectionManager = class {
|
|
816
939
|
client;
|
|
817
940
|
config;
|
|
@@ -937,6 +1060,57 @@ var SSHConnectionManager = class {
|
|
|
937
1060
|
throw error;
|
|
938
1061
|
}
|
|
939
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Execute a command on the remote server with streaming output
|
|
1065
|
+
* Returns streams instead of buffered output for real-time progress
|
|
1066
|
+
*
|
|
1067
|
+
* @param command - Shell command to execute
|
|
1068
|
+
* @param cwd - Optional working directory
|
|
1069
|
+
* @returns Object with stdout stream, stderr stream, and exit code promise
|
|
1070
|
+
*/
|
|
1071
|
+
async execStream(command, cwd) {
|
|
1072
|
+
if (!this.connected) {
|
|
1073
|
+
await this.connect();
|
|
1074
|
+
}
|
|
1075
|
+
const fullCommand = cwd ? `cd "${cwd}" && ${command}` : command;
|
|
1076
|
+
const startTime = Date.now();
|
|
1077
|
+
logger.sshCommand(fullCommand, cwd);
|
|
1078
|
+
return new Promise((resolve, reject) => {
|
|
1079
|
+
this.client.exec(fullCommand, (err, stream) => {
|
|
1080
|
+
if (err) {
|
|
1081
|
+
logger.error("SSH exec failed", {
|
|
1082
|
+
command: fullCommand,
|
|
1083
|
+
error: err.message
|
|
1084
|
+
});
|
|
1085
|
+
reject(err);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
logger.debug("SSH stream started", { command: fullCommand });
|
|
1089
|
+
const exitCodePromise = new Promise((resolveExit) => {
|
|
1090
|
+
stream.on("close", (code) => {
|
|
1091
|
+
const duration = Date.now() - startTime;
|
|
1092
|
+
logger.debug("SSH stream closed", {
|
|
1093
|
+
command: fullCommand,
|
|
1094
|
+
exitCode: code,
|
|
1095
|
+
duration: `${duration}ms`
|
|
1096
|
+
});
|
|
1097
|
+
resolveExit(code);
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
stream.on("error", (error) => {
|
|
1101
|
+
logger.error("SSH stream error", {
|
|
1102
|
+
command: fullCommand,
|
|
1103
|
+
error: error.message
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
resolve({
|
|
1107
|
+
stream,
|
|
1108
|
+
stderr: stream.stderr,
|
|
1109
|
+
exitCode: exitCodePromise
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
940
1114
|
/**
|
|
941
1115
|
* Get SFTP wrapper for file operations
|
|
942
1116
|
*/
|
|
@@ -955,21 +1129,118 @@ var SSHConnectionManager = class {
|
|
|
955
1129
|
});
|
|
956
1130
|
}
|
|
957
1131
|
/**
|
|
958
|
-
* List files in a directory
|
|
1132
|
+
* List files in a directory with comprehensive metadata
|
|
1133
|
+
* Uses hybrid approach: shell ls for filenames (includes hidden), SFTP stat for metadata
|
|
1134
|
+
*
|
|
1135
|
+
* @param path - Directory path to list
|
|
1136
|
+
* @param includeHidden - Whether to include hidden files (default: true)
|
|
1137
|
+
* @returns Array of FileEntry objects with complete metadata
|
|
959
1138
|
*/
|
|
960
|
-
async listFiles(path) {
|
|
1139
|
+
async listFiles(path, includeHidden = true) {
|
|
1140
|
+
const startTime = Date.now();
|
|
1141
|
+
logger.debug("Listing files", { path, includeHidden });
|
|
1142
|
+
try {
|
|
1143
|
+
const lsFlag = includeHidden ? "-A" : "";
|
|
1144
|
+
const command = `ls ${lsFlag} -1 "${path}" 2>/dev/null`;
|
|
1145
|
+
const result = await this.execWithTimeout(command, 10);
|
|
1146
|
+
if (result.exitCode !== 0) {
|
|
1147
|
+
throw new Error(`ls command failed: ${result.stderr}`);
|
|
1148
|
+
}
|
|
1149
|
+
const filenames = result.stdout.split("\n").map((f) => f.trim()).filter((f) => f !== "" && f !== "." && f !== "..");
|
|
1150
|
+
logger.debug("Filenames retrieved via shell", {
|
|
1151
|
+
path,
|
|
1152
|
+
count: filenames.length,
|
|
1153
|
+
method: "shell"
|
|
1154
|
+
});
|
|
1155
|
+
const sftp = await this.getSFTP();
|
|
1156
|
+
const entries = [];
|
|
1157
|
+
for (const filename of filenames) {
|
|
1158
|
+
const fullPath = `${path}/${filename}`.replace(/\/+/g, "/");
|
|
1159
|
+
try {
|
|
1160
|
+
const stats = await new Promise((resolve, reject) => {
|
|
1161
|
+
sftp.stat(fullPath, (err, stats2) => {
|
|
1162
|
+
if (err)
|
|
1163
|
+
reject(err);
|
|
1164
|
+
else
|
|
1165
|
+
resolve(stats2);
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
entries.push({
|
|
1169
|
+
name: filename,
|
|
1170
|
+
path: fullPath,
|
|
1171
|
+
type: getFileType(stats),
|
|
1172
|
+
size: stats.size,
|
|
1173
|
+
permissions: parsePermissions(stats.mode),
|
|
1174
|
+
owner: {
|
|
1175
|
+
uid: stats.uid,
|
|
1176
|
+
gid: stats.gid
|
|
1177
|
+
},
|
|
1178
|
+
timestamps: {
|
|
1179
|
+
accessed: new Date(stats.atime * 1e3).toISOString(),
|
|
1180
|
+
modified: new Date(stats.mtime * 1e3).toISOString()
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
logger.warn("Failed to stat file, skipping", {
|
|
1185
|
+
path: fullPath,
|
|
1186
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
const duration = Date.now() - startTime;
|
|
1191
|
+
logger.debug("Files listed successfully", {
|
|
1192
|
+
path,
|
|
1193
|
+
count: entries.length,
|
|
1194
|
+
duration: `${duration}ms`,
|
|
1195
|
+
method: "hybrid"
|
|
1196
|
+
});
|
|
1197
|
+
return entries;
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
logger.warn("Shell ls command failed, falling back to SFTP readdir", {
|
|
1200
|
+
path,
|
|
1201
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1202
|
+
});
|
|
1203
|
+
return this.listFilesViaSFTP(path, includeHidden);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Fallback method: List files using SFTP readdir (may miss hidden files)
|
|
1208
|
+
* @private
|
|
1209
|
+
*/
|
|
1210
|
+
async listFilesViaSFTP(path, includeHidden) {
|
|
961
1211
|
const sftp = await this.getSFTP();
|
|
962
1212
|
return new Promise((resolve, reject) => {
|
|
963
1213
|
sftp.readdir(path, (err, list) => {
|
|
964
1214
|
if (err) {
|
|
1215
|
+
logger.error("SFTP readdir failed", { path, error: err.message });
|
|
965
1216
|
reject(err);
|
|
966
1217
|
return;
|
|
967
1218
|
}
|
|
968
|
-
|
|
1219
|
+
let entries = list.map((item) => ({
|
|
969
1220
|
name: item.filename,
|
|
970
|
-
|
|
1221
|
+
path: `${path}/${item.filename}`.replace(/\/+/g, "/"),
|
|
1222
|
+
type: getFileType(item.attrs),
|
|
1223
|
+
size: item.attrs.size,
|
|
1224
|
+
permissions: parsePermissions(item.attrs.mode),
|
|
1225
|
+
owner: {
|
|
1226
|
+
uid: item.attrs.uid,
|
|
1227
|
+
gid: item.attrs.gid
|
|
1228
|
+
},
|
|
1229
|
+
timestamps: {
|
|
1230
|
+
accessed: new Date(item.attrs.atime * 1e3).toISOString(),
|
|
1231
|
+
modified: new Date(item.attrs.mtime * 1e3).toISOString()
|
|
1232
|
+
}
|
|
971
1233
|
}));
|
|
972
|
-
|
|
1234
|
+
if (!includeHidden) {
|
|
1235
|
+
entries = entries.filter((e) => !e.name.startsWith("."));
|
|
1236
|
+
}
|
|
1237
|
+
logger.debug("Files listed via SFTP fallback", {
|
|
1238
|
+
path,
|
|
1239
|
+
count: entries.length,
|
|
1240
|
+
method: "sftp",
|
|
1241
|
+
note: "Hidden files may be missing (SFTP limitation)"
|
|
1242
|
+
});
|
|
1243
|
+
resolve(entries);
|
|
973
1244
|
});
|
|
974
1245
|
});
|
|
975
1246
|
}
|
|
@@ -1126,7 +1397,7 @@ async function main() {
|
|
|
1126
1397
|
logger.debug(`Returning ${tools.length} tools`, { tools: tools.map((t) => t.name) });
|
|
1127
1398
|
return { tools };
|
|
1128
1399
|
});
|
|
1129
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1400
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
1130
1401
|
const startTime = Date.now();
|
|
1131
1402
|
logger.toolInvoked(request.params.name, request.params.arguments);
|
|
1132
1403
|
try {
|
|
@@ -1134,7 +1405,7 @@ async function main() {
|
|
|
1134
1405
|
if (request.params.name === "acp_remote_list_files") {
|
|
1135
1406
|
result = await handleAcpRemoteListFiles(request.params.arguments, sshConnection);
|
|
1136
1407
|
} else if (request.params.name === "acp_remote_execute_command") {
|
|
1137
|
-
result = await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection);
|
|
1408
|
+
result = await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection, extra, server);
|
|
1138
1409
|
} else if (request.params.name === "acp_remote_read_file") {
|
|
1139
1410
|
result = await handleAcpRemoteReadFile(request.params.arguments, sshConnection);
|
|
1140
1411
|
} else if (request.params.name === "acp_remote_write_file") {
|