| 1308 | } |
| 1309 | |
| 1310 | export function parsePiEventLine( |
| 1311 | line: string, |
| 1312 | ): |
| 1313 | | { ok: true; event: unknown } |
| 1314 | | { ok: false; error: string } |
| 1315 | | { ok: false; noise: true } { |
| 1316 | // Pi's --print JSON event stream emits one JSON OBJECT per line. Since |
| 1317 | // subagent children load the user's full extension set (v0.30.4 dropped |
| 1318 | // --no-extensions), any co-loaded extension that writes plain text to |
| 1319 | // stdout (e.g. "[Worker] Ready") interleaves with the event stream. |
| 1320 | // Such lines are noise to skip, not protocol corruption — only a line |
| 1321 | // that CLAIMS to be an event (starts with "{") but fails to parse is a |
| 1322 | // real error worth recording. |
| 1323 | if (!line.trimStart().startsWith("{")) { |
| 1324 | return { ok: false, noise: true }; |
| 1325 | } |
| 1326 | try { |
| 1327 | return { ok: true, event: JSON.parse(line) }; |
| 1328 | } catch (error) { |
| 1329 | return { |
| 1330 | ok: false, |
| 1331 | error: `failed to parse event: ${error instanceof Error ? error.message : String(error)} | line=${line.slice(0, 200)}`, |
| 1332 | }; |
| 1333 | } |
| 1334 | } |
| 1335 | |
| 1336 | function terminateChild(child: ReturnType<typeof childProcess.spawn>) { |
| 1337 | let exited = false; |