* Extract plain text from a replayed SDKUserMessage NDJSON line. Returns the * trimmed text if this looks like a real human-authored message, otherwise * undefined so the caller keeps waiting for the first real message.
( msg: Record<string, unknown>, )
| 205 | * undefined so the caller keeps waiting for the first real message. |
| 206 | */ |
| 207 | function extractUserMessageText( |
| 208 | msg: Record<string, unknown>, |
| 209 | ): string | undefined { |
| 210 | // Skip tool-result user messages (wrapped subagent results) and synthetic |
| 211 | // caveat messages — neither is human-authored. |
| 212 | if (msg.parent_tool_use_id != null || msg.isSynthetic || msg.isReplay) |
| 213 | return undefined |
| 214 | |
| 215 | const message = msg.message as Record<string, unknown> | undefined |
| 216 | const content = message?.content |
| 217 | let text: string | undefined |
| 218 | if (typeof content === 'string') { |
| 219 | text = content |
| 220 | } else if (Array.isArray(content)) { |
| 221 | for (const block of content) { |
| 222 | if ( |
| 223 | block && |
| 224 | typeof block === 'object' && |
| 225 | (block as Record<string, unknown>).type === 'text' |
| 226 | ) { |
| 227 | text = (block as Record<string, unknown>).text as string | undefined |
| 228 | break |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | text = text?.trim() |
| 233 | return text ? text : undefined |
| 234 | } |
| 235 | |
| 236 | /** Build a short preview of tool input for debug logging. */ |
| 237 | function inputPreview(input: Record<string, unknown>): string { |