@@ -839,7 +839,7 @@ public class OpenClawLauncher: ObservableObject {
839839 }
840840
841841 private func ensureImage( ) async throws {
842- addStep ( . running, " Pulling latest image... this may take a moment " )
842+ addStep ( . running, " Pulling latest image... first time may take a few minutes " )
843843 pullProgressText = nil
844844
845845 let pullExitCode : Int
@@ -869,34 +869,35 @@ public class OpenClawLauncher: ObservableObject {
869869 }
870870
871871 /// Streams `docker pull` output and updates `pullProgressText` with download progress.
872+ /// Uses `--progress=plain` for predictable newline-delimited output across Docker versions.
873+ /// Progress text is purely cosmetic — exit code alone determines success/failure.
872874 private func pullImageWithProgress( _ image: String ) async -> Int32 {
873875 let process = Process ( )
874- let stderrPipe = Pipe ( )
876+ let outputPipe = Pipe ( )
875877
876878 process. executableURL = URL ( fileURLWithPath: " /usr/bin/env " )
877- process. arguments = [ " docker " , " pull " , image]
878- process. standardOutput = FileHandle . nullDevice
879- process. standardError = stderrPipe
879+ process. arguments = [ " docker " , " pull " , " --progress=plain " , image]
880+ process. standardOutput = outputPipe
881+ process. standardError = outputPipe // merge stderr into same pipe
880882 process. environment = DockerPaths . augmentedEnvironment ( )
881883
882884 do {
883885 try process. run ( )
884886 } catch {
885- logger. error ( " pullImageWithProgress: failed to start process : \( error. localizedDescription) " )
887+ logger. error ( " pullImageWithProgress: failed to start: \( error. localizedDescription) " )
886888 return - 1
887889 }
888890
889- // Read stderr incrementally in background and parse progress
891+ // Read output incrementally — purely cosmetic, failures are silent
890892 let progressTask = Task . detached { [ weak self] ( ) -> Void in
891- let handle = stderrPipe . fileHandleForReading
893+ let handle = outputPipe . fileHandleForReading
892894 var buffer = Data ( )
893895
894896 while true {
895897 let chunk = handle. availableData
896898 if chunk. isEmpty { break } // EOF
897899 buffer. append ( chunk)
898900
899- // Parse complete lines from buffer
900901 if let text = String ( data: buffer, encoding: . utf8) {
901902 let summary = Self . parsePullProgress ( text)
902903 if let summary = summary {
@@ -914,12 +915,13 @@ public class OpenClawLauncher: ObservableObject {
914915 return process. terminationStatus
915916 }
916917
917- /// Parse docker pull stderr output to produce a human-readable progress summary.
918- /// Docker pull output has lines like: "abc123: Downloading [===> ] 50.12MB/400MB"
919- /// We aggregate all "Downloading" layers into a total.
918+ /// Parse docker pull output to produce a human-readable progress summary.
919+ /// With `--progress=plain`, output is newline-delimited:
920+ /// "abc123: Downloading 50.12MB / 400MB"
921+ /// "abc123: Download complete"
922+ /// Returns nil if nothing parseable (graceful fallback to static hint).
920923 nonisolated static func parsePullProgress( _ output: String ) -> String ? {
921- let lines = output. split ( separator: " \r " ) . last. map { String ( $0) } ?? output
922- let allLines = lines. split ( separator: " \n " )
924+ let allLines = output. components ( separatedBy: . newlines)
923925
924926 var totalDownloaded : Double = 0
925927 var totalSize : Double = 0
@@ -928,17 +930,15 @@ public class OpenClawLauncher: ObservableObject {
928930 var extractingCount = 0
929931
930932 for line in allLines {
931- let s = String ( line)
932- if s. contains ( " Downloading " ) {
933+ if line. contains ( " Downloading " ) {
933934 downloadingCount += 1
934- // Parse "50.12MB/400MB" from the line
935- if let ( downloaded, size) = parseSizeFromLine ( s) {
935+ if let ( downloaded, size) = parseSizeFromLine ( line) {
936936 totalDownloaded += downloaded
937937 totalSize += size
938938 }
939- } else if s . contains ( " Download complete " ) || s . contains ( " Already exists " ) || s . contains ( " Pull complete " ) {
939+ } else if line . contains ( " Download complete " ) || line . contains ( " Already exists " ) || line . contains ( " Pull complete " ) {
940940 doneCount += 1
941- } else if s . contains ( " Extracting " ) {
941+ } else if line . contains ( " Extracting " ) {
942942 extractingCount += 1
943943 }
944944 }
0 commit comments