{"version":3,"file":"child-protocol.d.ts","sourceRoot":"","sources":["../../../../src/runs/shared/child-protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAC7D,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAIjD,MAAM,WAAW,uBAAuB;IACvC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,MAAM,IAAI,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,MAAM,IAAI,uBAAuB,CAAC;CAClC;AAoRD;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,sBAK1C,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAE5E;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAChD,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAC9C,GAAG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACnC,GAAG,IAAI,IAAI,CAAC;IACZ,QAAQ,IAAI,OAAO,CAAC;CACpB,CAyHA;AASD,wBAAgB,qBAAqB,CAAC,QAAQ,SAAyB,GAAG;IACzE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACnC,IAAI,IAAI,MAAM,CAAC;IACf,UAAU,IAAI,MAAM,CAAC;CACrB,CAWA;AAED,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,cAAc,GAAG,MAAM,CAAC;AAE3E,wBAAgB,qBAAqB,CACpC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,EAC7C,qBAAqB,UAAQ,GAC3B,oBAAoB,CAKtB","sourcesContent":["import { Buffer } from \"node:buffer\";\nimport type { ProtocolOutputLimit } from \"../../shared/types.ts\";\n\nexport type { ProtocolOutputLimit } from \"../../shared/types.ts\";\n\nexport const MAX_CHILD_PENDING_LINE_BYTES = 16 * 1024 * 1024;\nexport const MAX_CHILD_STDERR_BYTES = 128 * 1024;\nconst MAX_PROTOCOL_DIAGNOSTIC_BYTES = 4096;\nconst MAX_PROJECTED_JSON_DEPTH = 256;\n\nexport interface OversizedLineProjection {\n\tpush(chunk: Buffer): boolean;\n\tfinish(): string | undefined;\n}\n\nexport interface OversizedLineProjector {\n\taccepts(prefix: string): boolean;\n\tcreate(): OversizedLineProjection;\n}\n\ntype JsonContainer =\n\t| { type: \"array\"; state: \"value-or-end\" | \"value\" | \"comma-or-end\" }\n\t| { type: \"object\"; state: \"key-or-end\" | \"key\" | \"colon\" | \"value\" | \"comma-or-end\"; key?: string };\n\ntype JsonToken =\n\t| {\n\t\t\ttype: \"string\";\n\t\t\trole: \"key\" | \"value\";\n\t\t\tvalue: string;\n\t\t\tcapture: boolean;\n\t\t\tescape: boolean;\n\t\t\tunicodeDigits: number;\n\t\t\tunicodeValue: string;\n\t  }\n\t| { type: \"literal\"; expected: string; index: number; value: boolean | null }\n\t| { type: \"number\"; phase: \"minus\" | \"zero\" | \"int\" | \"dot\" | \"frac\" | \"exp\" | \"exp-sign\" | \"exp-digits\" };\n\nfunction createPiAggregateProjection(): OversizedLineProjection {\n\tconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\tconst stack: JsonContainer[] = [];\n\tlet rootState: \"value\" | \"end\" = \"value\";\n\tlet token: JsonToken | undefined;\n\tlet valid = true;\n\tlet eventType: string | undefined;\n\tlet willRetry: boolean | undefined;\n\n\tconst parent = (): JsonContainer | undefined => stack.at(-1);\n\tconst isTopLevelField = (key: string | undefined): boolean =>\n\t\tstack.length === 1 && parent()?.type === \"object\" && (key === \"type\" || key === \"willRetry\");\n\n\tconst completeValue = (value?: string | boolean | null): void => {\n\t\tconst container = parent();\n\t\tif (!container) {\n\t\t\trootState = \"end\";\n\t\t\treturn;\n\t\t}\n\t\tif (container.type === \"object\") {\n\t\t\tif (stack.length === 1 && container.key === \"type\" && typeof value === \"string\") eventType = value;\n\t\t\tif (stack.length === 1 && container.key === \"willRetry\" && typeof value === \"boolean\") willRetry = value;\n\t\t\tcontainer.key = undefined;\n\t\t\tcontainer.state = \"comma-or-end\";\n\t\t} else container.state = \"comma-or-end\";\n\t};\n\n\tconst startValue = (char: string): boolean => {\n\t\tconst container = parent();\n\t\tconst key = container?.type === \"object\" ? container.key : undefined;\n\t\tif (isTopLevelField(key)) {\n\t\t\tif (key === \"type\") eventType = undefined;\n\t\t\telse willRetry = undefined;\n\t\t}\n\t\tif (char === \"{\" || char === \"[\") {\n\t\t\tif (stack.length >= MAX_PROJECTED_JSON_DEPTH) return false;\n\t\t\tstack.push(char === \"{\" ? { type: \"object\", state: \"key-or-end\" } : { type: \"array\", state: \"value-or-end\" });\n\t\t\treturn true;\n\t\t}\n\t\tif (char === '\"') {\n\t\t\ttoken = {\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"value\",\n\t\t\t\tvalue: \"\",\n\t\t\t\tcapture: key === \"type\" && stack.length === 1,\n\t\t\t\tescape: false,\n\t\t\t\tunicodeDigits: 0,\n\t\t\t\tunicodeValue: \"\",\n\t\t\t};\n\t\t\treturn true;\n\t\t}\n\t\tif (char === \"t\") token = { type: \"literal\", expected: \"true\", index: 1, value: true };\n\t\telse if (char === \"f\") token = { type: \"literal\", expected: \"false\", index: 1, value: false };\n\t\telse if (char === \"n\") token = { type: \"literal\", expected: \"null\", index: 1, value: null };\n\t\telse if (char === \"-\") token = { type: \"number\", phase: \"minus\" };\n\t\telse if (char === \"0\") token = { type: \"number\", phase: \"zero\" };\n\t\telse if (char >= \"1\" && char <= \"9\") token = { type: \"number\", phase: \"int\" };\n\t\telse return false;\n\t\treturn true;\n\t};\n\n\tconst closeContainer = (): true => {\n\t\tstack.pop();\n\t\tcompleteValue();\n\t\treturn true;\n\t};\n\n\tconst processChar = (char: string): boolean => {\n\t\tif (token?.type === \"string\") {\n\t\t\tif (token.unicodeDigits > 0) {\n\t\t\t\tif (!/[0-9a-fA-F]/.test(char)) return false;\n\t\t\t\ttoken.unicodeValue += char;\n\t\t\t\ttoken.unicodeDigits--;\n\t\t\t\tif (token.unicodeDigits === 0 && token.capture) {\n\t\t\t\t\tif (token.value.length >= 64) return false;\n\t\t\t\t\ttoken.value += String.fromCharCode(Number.parseInt(token.unicodeValue, 16));\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (token.escape) {\n\t\t\t\ttoken.escape = false;\n\t\t\t\tif (char === \"u\") {\n\t\t\t\t\ttoken.unicodeDigits = 4;\n\t\t\t\t\ttoken.unicodeValue = \"\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (!'\"\\\\/bfnrt'.includes(char)) return false;\n\t\t\t\tif (token.capture) {\n\t\t\t\t\tif (token.value.length >= 64) return false;\n\t\t\t\t\ttoken.value += ({ b: \"\\b\", f: \"\\f\", n: \"\\n\", r: \"\\r\", t: \"\\t\" } as Record<string, string>)[char] ?? char;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (char === \"\\\\\") {\n\t\t\t\ttoken.escape = true;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (char === '\"') {\n\t\t\t\tconst finished = token;\n\t\t\t\ttoken = undefined;\n\t\t\t\tif (finished.role === \"key\") {\n\t\t\t\t\tconst container = parent();\n\t\t\t\t\tif (!container || container.type !== \"object\") return false;\n\t\t\t\t\tcontainer.key = finished.value;\n\t\t\t\t\tcontainer.state = \"colon\";\n\t\t\t\t} else completeValue(finished.capture ? finished.value : undefined);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (char.charCodeAt(0) < 0x20) return false;\n\t\t\tif (token.capture) {\n\t\t\t\tif (token.value.length >= 64) return false;\n\t\t\t\ttoken.value += char;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (token?.type === \"literal\") {\n\t\t\tif (char !== token.expected[token.index]) return false;\n\t\t\ttoken.index++;\n\t\t\tif (token.index === token.expected.length) {\n\t\t\t\tconst value = token.value;\n\t\t\t\ttoken = undefined;\n\t\t\t\tcompleteValue(value);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (token?.type === \"number\") {\n\t\t\tconst phase = token.phase;\n\t\t\tif (phase === \"minus\") {\n\t\t\t\tif (char === \"0\") token.phase = \"zero\";\n\t\t\t\telse if (char >= \"1\" && char <= \"9\") token.phase = \"int\";\n\t\t\t\telse return false;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (phase === \"zero\" || phase === \"int\") {\n\t\t\t\tif (char >= \"0\" && char <= \"9\") {\n\t\t\t\t\tif (phase === \"zero\") return false;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (char === \".\") {\n\t\t\t\t\ttoken.phase = \"dot\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (char === \"e\" || char === \"E\") {\n\t\t\t\t\ttoken.phase = \"exp\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else if (phase === \"dot\") {\n\t\t\t\tif (char >= \"0\" && char <= \"9\") {\n\t\t\t\t\ttoken.phase = \"frac\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t} else if (phase === \"frac\") {\n\t\t\t\tif (char >= \"0\" && char <= \"9\") return true;\n\t\t\t\tif (char === \"e\" || char === \"E\") {\n\t\t\t\t\ttoken.phase = \"exp\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else if (phase === \"exp\") {\n\t\t\t\tif (char === \"+\" || char === \"-\") {\n\t\t\t\t\ttoken.phase = \"exp-sign\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (char >= \"0\" && char <= \"9\") {\n\t\t\t\t\ttoken.phase = \"exp-digits\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t} else if (phase === \"exp-sign\") {\n\t\t\t\tif (char >= \"0\" && char <= \"9\") {\n\t\t\t\t\ttoken.phase = \"exp-digits\";\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t} else if (phase === \"exp-digits\" && char >= \"0\" && char <= \"9\") return true;\n\t\t\tif (![\"zero\", \"int\", \"frac\", \"exp-digits\"].includes(phase)) return false;\n\t\t\ttoken = undefined;\n\t\t\tcompleteValue();\n\t\t\treturn processChar(char);\n\t\t}\n\t\tif (char === \" \" || char === \"\\t\" || char === \"\\r\" || char === \"\\n\") return true;\n\t\tconst container = parent();\n\t\tif (!container) return rootState === \"value\" ? startValue(char) : false;\n\t\tif (container.type === \"object\") {\n\t\t\tif (container.state === \"key-or-end\" || container.state === \"key\") {\n\t\t\t\tif (char === \"}\" && container.state === \"key-or-end\") return closeContainer();\n\t\t\t\tif (char !== '\"') return false;\n\t\t\t\ttoken = {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trole: \"key\",\n\t\t\t\t\tvalue: \"\",\n\t\t\t\t\tcapture: stack.length === 1,\n\t\t\t\t\tescape: false,\n\t\t\t\t\tunicodeDigits: 0,\n\t\t\t\t\tunicodeValue: \"\",\n\t\t\t\t};\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (container.state === \"colon\") {\n\t\t\t\tif (char !== \":\") return false;\n\t\t\t\tcontainer.state = \"value\";\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (container.state === \"value\") return startValue(char);\n\t\t\tif (char === \",\") {\n\t\t\t\tcontainer.state = \"key\";\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (char === \"}\") return closeContainer();\n\t\t\treturn false;\n\t\t}\n\t\tif (container.state === \"value-or-end\" || container.state === \"value\") {\n\t\t\tif (char === \"]\" && container.state === \"value-or-end\") return closeContainer();\n\t\t\treturn startValue(char);\n\t\t}\n\t\tif (char === \",\") {\n\t\t\tcontainer.state = \"value\";\n\t\t\treturn true;\n\t\t}\n\t\tif (char === \"]\") return closeContainer();\n\t\treturn false;\n\t};\n\n\tconst processText = (text: string): boolean => {\n\t\tfor (const char of text) if (!processChar(char)) return false;\n\t\treturn true;\n\t};\n\n\treturn {\n\t\tpush(chunk) {\n\t\t\tif (!valid) return false;\n\t\t\ttry {\n\t\t\t\tvalid = processText(decoder.decode(chunk, { stream: true }));\n\t\t\t} catch {\n\t\t\t\tvalid = false;\n\t\t\t}\n\t\t\treturn valid;\n\t\t},\n\t\tfinish() {\n\t\t\ttry {\n\t\t\t\tvalid = valid && processText(decoder.decode());\n\t\t\t} catch {\n\t\t\t\tvalid = false;\n\t\t\t}\n\t\t\tif (token?.type === \"number\" && [\"zero\", \"int\", \"frac\", \"exp-digits\"].includes(token.phase)) {\n\t\t\t\ttoken = undefined;\n\t\t\t\tcompleteValue();\n\t\t\t}\n\t\t\tif (!valid || token || stack.length !== 0 || rootState !== \"end\") return undefined;\n\t\t\tif (eventType === \"turn_end\") return '{\"type\":\"turn_end\"}';\n\t\t\tif (eventType === \"agent_end\" && typeof willRetry === \"boolean\")\n\t\t\t\treturn JSON.stringify({ type: \"agent_end\", willRetry });\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n\n/**\n * Pi JSON mode emits granular message/tool events followed by aggregate\n * `turn_end` and `agent_end` events that duplicate those payloads. Parallel\n * image reads can make one aggregate record exceed the child line limit even\n * though every granular event was valid. Replace only syntactically valid,\n * redundant records with the lifecycle fields the runners consume.\n */\nexport const PI_AGGREGATE_EVENT_PROJECTOR: OversizedLineProjector = {\n\taccepts(prefix) {\n\t\treturn prefix.startsWith('{\"type\":\"turn_end\"') || prefix.startsWith('{\"type\":\"agent_end\"');\n\t},\n\tcreate: createPiAggregateProjection,\n};\n\nexport function formatProtocolOutputLimit(limit: ProtocolOutputLimit): string {\n\treturn `${limit.code}: child ${limit.stream} line exceeded ${limit.limitBytes} bytes (observed at least ${limit.observedBytes} bytes without a newline).`;\n}\n\nexport function createBoundedLineReader(options: {\n\tstream?: \"stdout\" | \"stderr\";\n\tmaxPendingLineBytes?: number;\n\toversizedLineProjector?: OversizedLineProjector;\n\tonLine: (line: string) => void;\n\tonLimit: (limit: ProtocolOutputLimit) => void;\n}): {\n\tpush(chunk: Buffer | string): void;\n\tend(): void;\n\texceeded(): boolean;\n} {\n\tconst maxPendingLineBytes = options.maxPendingLineBytes ?? MAX_CHILD_PENDING_LINE_BYTES;\n\tif (!Number.isInteger(maxPendingLineBytes) || maxPendingLineBytes < 1) {\n\t\tthrow new Error(\"maxPendingLineBytes must be a positive integer.\");\n\t}\n\tlet pending: Buffer[] = [];\n\tlet pendingBytes = 0;\n\tlet projectedPrefix: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n\tlet projectedTail: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n\tlet projectedBytes = 0;\n\tlet projection: OversizedLineProjection | undefined;\n\tlet projectingOversizedLine = false;\n\tlet limitExceeded = false;\n\n\tconst diagnosticTail = (prior: Buffer, segment: Buffer): Buffer => {\n\t\tconst tailFromSegment = segment.subarray(Math.max(0, segment.length - MAX_PROTOCOL_DIAGNOSTIC_BYTES));\n\t\treturn tailFromSegment.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES\n\t\t\t? tailFromSegment\n\t\t\t: Buffer.concat([\n\t\t\t\t\tprior.subarray(Math.max(0, prior.length - (MAX_PROTOCOL_DIAGNOSTIC_BYTES - tailFromSegment.length))),\n\t\t\t\t\ttailFromSegment,\n\t\t\t\t]);\n\t};\n\n\tconst failLimit = (observedBytes: number, prefix: Buffer, tail: Buffer): false => {\n\t\tlimitExceeded = true;\n\t\tpending = [];\n\t\tpendingBytes = 0;\n\t\tprojectingOversizedLine = false;\n\t\tprojection = undefined;\n\t\tprojectedPrefix = Buffer.alloc(0);\n\t\tprojectedTail = Buffer.alloc(0);\n\t\tprojectedBytes = 0;\n\t\toptions.onLimit({\n\t\t\tcode: \"protocol_output_limit\",\n\t\t\tstream: options.stream ?? \"stdout\",\n\t\t\tlimitBytes: maxPendingLineBytes,\n\t\t\tobservedBytes,\n\t\t\tdiagnosticPrefix: prefix.toString(\"utf8\"),\n\t\t\tdiagnosticTail: tail.toString(\"utf8\"),\n\t\t});\n\t\treturn false;\n\t};\n\n\tconst finishLine = (): void => {\n\t\tif (projectingOversizedLine) {\n\t\t\tconst projected = projection?.finish();\n\t\t\tif (projected === undefined) {\n\t\t\t\tfailLimit(projectedBytes, projectedPrefix, projectedTail);\n\t\t\t} else {\n\t\t\t\toptions.onLine(projected);\n\t\t\t}\n\t\t} else if (pendingBytes > 0) {\n\t\t\toptions.onLine(Buffer.concat(pending, pendingBytes).toString(\"utf8\"));\n\t\t}\n\t\tpending = [];\n\t\tpendingBytes = 0;\n\t\tprojectingOversizedLine = false;\n\t\tprojection = undefined;\n\t\tprojectedPrefix = Buffer.alloc(0);\n\t\tprojectedTail = Buffer.alloc(0);\n\t\tprojectedBytes = 0;\n\t};\n\n\tconst append = (segment: Buffer): boolean => {\n\t\tif (segment.length === 0) return true;\n\t\tif (projectingOversizedLine) {\n\t\t\tprojectedBytes += segment.length;\n\t\t\tprojectedTail = diagnosticTail(projectedTail, segment);\n\t\t\treturn projection?.push(segment) === true || failLimit(projectedBytes, projectedPrefix, projectedTail);\n\t\t}\n\t\tconst observedBytes = pendingBytes + segment.length;\n\t\tif (observedBytes > maxPendingLineBytes) {\n\t\t\tconst prior = pendingBytes > 0 ? Buffer.concat(pending, pendingBytes) : Buffer.alloc(0);\n\t\t\tconst prefixFromPrior = prior.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES);\n\t\t\tconst prefix =\n\t\t\t\tprefixFromPrior.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES\n\t\t\t\t\t? prefixFromPrior\n\t\t\t\t\t: Buffer.concat([\n\t\t\t\t\t\t\tprefixFromPrior,\n\t\t\t\t\t\t\tsegment.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES - prefixFromPrior.length),\n\t\t\t\t\t\t]);\n\t\t\tconst tail = diagnosticTail(prior, segment);\n\t\t\tif (options.oversizedLineProjector?.accepts(prefix.toString(\"utf8\"))) {\n\t\t\t\tconst candidate = options.oversizedLineProjector.create();\n\t\t\t\tif (!candidate.push(prior) || !candidate.push(segment)) return failLimit(observedBytes, prefix, tail);\n\t\t\t\tpending = [];\n\t\t\t\tpendingBytes = 0;\n\t\t\t\tprojectingOversizedLine = true;\n\t\t\t\tprojection = candidate;\n\t\t\t\tprojectedPrefix = prefix;\n\t\t\t\tprojectedTail = tail;\n\t\t\t\tprojectedBytes = observedBytes;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn failLimit(observedBytes, prefix, tail);\n\t\t}\n\t\tpending.push(segment);\n\t\tpendingBytes = observedBytes;\n\t\treturn true;\n\t};\n\n\treturn {\n\t\tpush(chunk) {\n\t\t\tif (limitExceeded) return;\n\t\t\tconst bytes = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\tlet start = 0;\n\t\t\tfor (let index = 0; index < bytes.length; index++) {\n\t\t\t\tif (bytes[index] !== 0x0a) continue;\n\t\t\t\tif (!append(bytes.subarray(start, index))) return;\n\t\t\t\tfinishLine();\n\t\t\t\tif (limitExceeded) return;\n\t\t\t\tstart = index + 1;\n\t\t\t}\n\t\t\tappend(bytes.subarray(start));\n\t\t},\n\t\tend() {\n\t\t\tif (!limitExceeded) finishLine();\n\t\t},\n\t\texceeded: () => limitExceeded,\n\t};\n}\n\nfunction trimToUtf8Boundary(buffer: Buffer, maxBytes: number): Buffer {\n\tif (buffer.length <= maxBytes) return buffer;\n\tlet start = buffer.length - maxBytes;\n\twhile (start < buffer.length && (buffer[start]! & 0xc0) === 0x80) start++;\n\treturn buffer.subarray(start);\n}\n\nexport function createBoundedByteTail(maxBytes = MAX_CHILD_STDERR_BYTES): {\n\tpush(chunk: Buffer | string): void;\n\ttext(): string;\n\tbyteLength(): number;\n} {\n\tif (!Number.isInteger(maxBytes) || maxBytes < 1) throw new Error(\"maxBytes must be a positive integer.\");\n\tlet tail: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n\treturn {\n\t\tpush(chunk) {\n\t\t\tconst bytes = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\ttail = trimToUtf8Boundary(Buffer.concat([tail, bytes]), maxBytes);\n\t\t},\n\t\ttext: () => tail.toString(\"utf8\"),\n\t\tbyteLength: () => tail.length,\n\t};\n}\n\nexport type ChildLifecycleAction = \"start-drain\" | \"cancel-drain\" | \"none\";\n\nexport function projectChildLifecycle(\n\tevent: { type?: string; willRetry?: unknown },\n\tterminalAssistantStop = false,\n): ChildLifecycleAction {\n\tif (event.type === \"agent_end\" && event.willRetry === true) return \"cancel-drain\";\n\tif (event.type === \"agent_settled\") return \"start-drain\";\n\tif (terminalAssistantStop) return \"start-drain\";\n\treturn \"none\";\n}\n"]}