@@ -190,12 +190,76 @@ function parseJsonOutput(stdout) {
|
190 | 190 | if (!text) { |
191 | 191 | throw new Error("expected JSON output, got empty stdout"); |
192 | 192 | } |
193 | | -const first = text.indexOf("{"); |
194 | | -const last = text.lastIndexOf("}"); |
195 | | -if (first < 0 || last < first) { |
| 193 | +const parsed = parseJsonObjectsFromMixedOutput(text).at(-1); |
| 194 | +if (parsed === undefined) { |
196 | 195 | throw new Error(`expected JSON object output, got: ${scrub(text.slice(0, 500))}`); |
197 | 196 | } |
198 | | -return JSON.parse(text.slice(first, last + 1)); |
| 197 | +return parsed; |
| 198 | +} |
| 199 | + |
| 200 | +function isJsonRecordStart(text, index) { |
| 201 | +for (let cursor = index - 1; cursor >= 0; cursor -= 1) { |
| 202 | +const char = text[cursor]; |
| 203 | +if (char === "\n" || char === "\r") { |
| 204 | +return true; |
| 205 | +} |
| 206 | +if (char !== " " && char !== "\t") { |
| 207 | +return false; |
| 208 | +} |
| 209 | +} |
| 210 | +return true; |
| 211 | +} |
| 212 | + |
| 213 | +function parseJsonObjectsFromMixedOutput(text) { |
| 214 | +const objects = []; |
| 215 | +let start = -1; |
| 216 | +let depth = 0; |
| 217 | +let inString = false; |
| 218 | +let escaped = false; |
| 219 | + |
| 220 | +for (let index = 0; index < text.length; index += 1) { |
| 221 | +const char = text[index]; |
| 222 | +if (start === -1) { |
| 223 | +if (char === "{" && isJsonRecordStart(text, index)) { |
| 224 | +start = index; |
| 225 | +depth = 1; |
| 226 | +inString = false; |
| 227 | +escaped = false; |
| 228 | +} |
| 229 | +continue; |
| 230 | +} |
| 231 | + |
| 232 | +if (inString) { |
| 233 | +if (escaped) { |
| 234 | +escaped = false; |
| 235 | +} else if (char === "\\") { |
| 236 | +escaped = true; |
| 237 | +} else if (char === '"') { |
| 238 | +inString = false; |
| 239 | +} |
| 240 | +continue; |
| 241 | +} |
| 242 | +if (char === '"') { |
| 243 | +inString = true; |
| 244 | +continue; |
| 245 | +} |
| 246 | +if (char === "{") { |
| 247 | +depth += 1; |
| 248 | +continue; |
| 249 | +} |
| 250 | +if (char !== "}") { |
| 251 | +continue; |
| 252 | +} |
| 253 | + |
| 254 | +depth -= 1; |
| 255 | +if (depth === 0) { |
| 256 | +try { |
| 257 | +objects.push(JSON.parse(text.slice(start, index + 1))); |
| 258 | +} catch {} |
| 259 | +start = -1; |
| 260 | +} |
| 261 | +} |
| 262 | +return objects; |
199 | 263 | } |
200 | 264 | |
201 | 265 | function resolveOpenClawRunner() { |
@@ -2056,6 +2120,7 @@ export {
|
2056 | 2120 | cleanupEnv, |
2057 | 2121 | expectGatewayStartupFails, |
2058 | 2122 | gatewayCall, |
| 2123 | +parseJsonOutput, |
2059 | 2124 | runPtySecretsConfigurePreset, |
2060 | 2125 | runWithProof, |
2061 | 2126 | runCommand, |
|