{"version":3,"file":"single-output.d.ts","sourceRoot":"","sources":["../../../../src/runs/shared/single-output.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAG9E,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACxC,QAAQ,EAAE,OAAO,EAAE,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,GAAG,CAAC,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CA2BpB;AAED,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACpC,aAAa,EAAE,MAAM,GAAG,SAAS,GAC/B,MAAM,GAAG,KAAK,GAAG,SAAS,CAK5B;AAED,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACpC,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,eAAe,CAAC,EAAE,MAAM,GACtB,MAAM,GAAG,SAAS,CAUpB;AAED,UAAU,6BAA6B;IACtC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAkBD,wBAAgB,6BAA6B,CAC5C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,YAAY,CAAC,EAAE,6BAA6B,GAC1C,MAAM,CAGR;AAED,wBAAgB,4BAA4B,CAC3C,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,YAAY,CAAC,EAAE,6BAA6B,GAC1C,MAAM,CAIR;AAoBD,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,oBAAoB,CAUtG;AAED,wBAAgB,0BAA0B,CACzC,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GACb,MAAM,GAAG,SAAS,CAKpB;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,oBAAoB,GAAG,SAAS,CAS5G;AAgBD,wBAAgB,mBAAmB,CAClC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,oBAAoB,GAAG,SAAS,GACzC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAgChE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,oBAAoB,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAe5G","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Message } from \"@lpb-work/pi-ai\";\nimport type { OutputMode, SavedOutputReference } from \"../../shared/types.ts\";\nimport { hasMutationToolCapability } from \"./completion-guard.ts\";\n\nexport interface SingleOutputSnapshot {\n\texists: boolean;\n\tmtimeMs?: number;\n\tsize?: number;\n}\n\n/**\n * Content the child itself sent to the configured output path, taken from its\n * last `write` tool call whose tool result reports success. Unlike reading the\n * path from disk, this cannot be polluted by a sibling run writing the same\n * path (#420); requiring the successful tool result keeps failed, cancelled,\n * or unanswered write calls from counting as authored output. Returns\n * undefined when no such write exists (e.g. bash or edit-based construction),\n * in which case callers must not assume file authorship.\n */\nexport function extractChildWrittenOutput(\n\tmessages: Message[] | undefined,\n\toutputPath: string | undefined,\n\tcwd?: string,\n): string | undefined {\n\tif (!messages?.length || !outputPath) return undefined;\n\tconst resolvedTarget = path.resolve(cwd ?? \".\", outputPath);\n\tconst comparableTarget = process.platform === \"win32\" ? resolvedTarget.toLowerCase() : resolvedTarget;\n\tconst successfulCallIds = new Set<string>();\n\tfor (const message of messages) {\n\t\tif (message.role === \"toolResult\" && message.isError === false && typeof message.toolCallId === \"string\") {\n\t\t\tsuccessfulCallIds.add(message.toolCallId);\n\t\t}\n\t}\n\tlet content: string | undefined;\n\tfor (const message of messages) {\n\t\tif (message.role !== \"assistant\") continue;\n\t\tfor (const part of message.content) {\n\t\t\tif (part.type !== \"toolCall\" || part.name !== \"write\" || !successfulCallIds.has(part.id)) continue;\n\t\t\tconst args =\n\t\t\t\ttypeof part.arguments === \"object\" && part.arguments !== null && !Array.isArray(part.arguments)\n\t\t\t\t\t? (part.arguments as Record<string, unknown>)\n\t\t\t\t\t: {};\n\t\t\tif (typeof args.path !== \"string\" || typeof args.content !== \"string\") continue;\n\t\t\tconst resolvedWritePath = path.resolve(cwd ?? \".\", args.path);\n\t\t\tconst comparableWritePath = process.platform === \"win32\" ? resolvedWritePath.toLowerCase() : resolvedWritePath;\n\t\t\tif (comparableWritePath !== comparableTarget) continue;\n\t\t\tcontent = args.content;\n\t\t}\n\t}\n\treturn content;\n}\n\nexport function normalizeSingleOutputOverride(\n\toutput: string | boolean | undefined,\n\tdefaultOutput: string | undefined,\n): string | false | undefined {\n\tif (output === false || output === \"false\") return false;\n\tif (output === true || output === \"true\") return defaultOutput;\n\tif (typeof output === \"string\" && output.length > 0) return output;\n\treturn undefined;\n}\n\nexport function resolveSingleOutputPath(\n\toutput: string | boolean | undefined,\n\truntimeCwd: string,\n\trequestedCwd?: string,\n\trelativeBaseDir?: string,\n): string | undefined {\n\tif (typeof output !== \"string\" || !output || output === \"false\" || output === \"true\") return undefined;\n\tif (path.isAbsolute(output)) return output;\n\tif (relativeBaseDir) return path.resolve(relativeBaseDir, output);\n\tconst baseCwd = requestedCwd\n\t\t? path.isAbsolute(requestedCwd)\n\t\t\t? requestedCwd\n\t\t\t: path.resolve(runtimeCwd, requestedCwd)\n\t\t: runtimeCwd;\n\treturn path.resolve(baseCwd, output);\n}\n\ninterface OutputInstructionCapabilities {\n\ttools?: string[];\n\tmcpDirectTools?: string[];\n}\n\nfunction formatOutputPathInstruction(outputPath: string, capabilities?: OutputInstructionCapabilities): string {\n\tconst delivery =\n\t\t!capabilities || hasMutationToolCapability(capabilities.tools, capabilities.mcpDirectTools)\n\t\t\t? `Write your findings to exactly this path: ${outputPath}`\n\t\t\t: [\n\t\t\t\t\t\"Return the complete artifact in your final response.\",\n\t\t\t\t\t`The runtime will persist it to exactly this path: ${outputPath}`,\n\t\t\t\t\t\"Do not call contact_supervisor merely because no write-capable tool is available.\",\n\t\t\t\t].join(\"\\n\");\n\treturn [\n\t\tdelivery,\n\t\t\"This path is authoritative for this run.\",\n\t\t\"Ignore any other output filename or output path mentioned elsewhere, including output destinations in the base agent prompt, system prompt, or task instructions.\",\n\t].join(\"\\n\");\n}\n\nexport function injectSingleOutputInstruction(\n\ttask: string,\n\toutputPath: string | undefined,\n\tcapabilities?: OutputInstructionCapabilities,\n): string {\n\tif (!outputPath) return task;\n\treturn `${task}\\n\\n---\\n**Output:**\\n${formatOutputPathInstruction(outputPath, capabilities)}`;\n}\n\nexport function injectOutputPathSystemPrompt(\n\tsystemPrompt: string,\n\toutputPath: string | undefined,\n\tcapabilities?: OutputInstructionCapabilities,\n): string {\n\tif (!outputPath) return systemPrompt;\n\tconst instruction = `Runtime output path override:\\n${formatOutputPathInstruction(outputPath, capabilities)}`;\n\treturn systemPrompt ? `${systemPrompt}\\n\\n${instruction}` : instruction;\n}\n\nfunction countLines(text: string): number {\n\tif (!text) return 0;\n\tconst newlineMatches = text.match(/\\r\\n|\\r|\\n/g);\n\treturn (newlineMatches?.length ?? 0) + (/[\\r\\n]$/.test(text) ? 0 : 1);\n}\n\nfunction formatByteSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes} B`;\n\tconst units = [\"KB\", \"MB\", \"GB\", \"TB\"];\n\tlet value = bytes / 1024;\n\tlet unitIndex = 0;\n\twhile (value >= 1024 && unitIndex < units.length - 1) {\n\t\tvalue /= 1024;\n\t\tunitIndex++;\n\t}\n\treturn `${value.toFixed(1)} ${units[unitIndex]}`;\n}\n\nexport function formatSavedOutputReference(savedPath: string, fullOutput: string): SavedOutputReference {\n\tconst absolutePath = path.resolve(savedPath);\n\tconst bytes = Buffer.byteLength(fullOutput, \"utf-8\");\n\tconst lines = countLines(fullOutput);\n\treturn {\n\t\tpath: absolutePath,\n\t\tbytes,\n\t\tlines,\n\t\tmessage: `Output saved to: ${absolutePath} (${formatByteSize(bytes)}, ${lines} ${lines === 1 ? \"line\" : \"lines\"}). Read this file if needed.`,\n\t};\n}\n\nexport function validateFileOnlyOutputMode(\n\toutputMode: OutputMode | undefined,\n\toutputPath: string | undefined,\n\tcontext: string,\n): string | undefined {\n\tif (outputMode === \"file-only\" && !outputPath) {\n\t\treturn `${context} sets outputMode: \"file-only\" but does not configure an output file. Set output to a path or use outputMode: \"inline\".`;\n\t}\n\treturn undefined;\n}\n\nexport function captureSingleOutputSnapshot(outputPath: string | undefined): SingleOutputSnapshot | undefined {\n\tif (!outputPath) return undefined;\n\ttry {\n\t\tconst stat = fs.statSync(outputPath);\n\t\treturn { exists: true, mtimeMs: stat.mtimeMs, size: stat.size };\n\t} catch {\n\t\t// The snapshot is advisory; resolveSingleOutput reports concrete read/write failures.\n\t\treturn { exists: false };\n\t}\n}\n\nfunction persistSingleOutput(\n\toutputPath: string | undefined,\n\tfullOutput: string,\n): { savedPath?: string; error?: string } {\n\tif (!outputPath) return {};\n\ttry {\n\t\tfs.mkdirSync(path.dirname(outputPath), { recursive: true });\n\t\tfs.writeFileSync(outputPath, fullOutput, \"utf-8\");\n\t\treturn { savedPath: outputPath };\n\t} catch (err) {\n\t\treturn { error: err instanceof Error ? err.message : String(err) };\n\t}\n}\n\nexport function resolveSingleOutput(\n\toutputPath: string | undefined,\n\tfallbackOutput: string,\n\tbeforeRun: SingleOutputSnapshot | undefined,\n): { fullOutput: string; savedPath?: string; saveError?: string } {\n\tif (!outputPath) return { fullOutput: fallbackOutput };\n\n\tlet changedSinceStart = false;\n\ttry {\n\t\tconst stat = fs.statSync(outputPath);\n\t\tchangedSinceStart = !beforeRun?.exists || stat.mtimeMs !== beforeRun.mtimeMs || stat.size !== beforeRun.size;\n\t} catch (error) {\n\t\tconst code =\n\t\t\terror && typeof error === \"object\" && \"code\" in error ? (error as { code?: unknown }).code : undefined;\n\t\tif (code !== \"ENOENT\" && code !== \"ENOTDIR\") {\n\t\t\treturn {\n\t\t\t\tfullOutput: fallbackOutput,\n\t\t\t\tsaveError: `Failed to inspect output file: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (changedSinceStart) {\n\t\ttry {\n\t\t\treturn { fullOutput: fs.readFileSync(outputPath, \"utf-8\"), savedPath: outputPath };\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tfullOutput: fallbackOutput,\n\t\t\t\tsaveError: `Failed to read changed output file: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t};\n\t\t}\n\t}\n\n\tconst save = persistSingleOutput(outputPath, fallbackOutput);\n\tif (save.savedPath) return { fullOutput: fallbackOutput, savedPath: save.savedPath };\n\treturn { fullOutput: fallbackOutput, saveError: save.error };\n}\n\nexport function finalizeSingleOutput(params: {\n\tfullOutput: string;\n\ttruncatedOutput?: string;\n\toutputPath?: string;\n\toutputMode?: OutputMode;\n\texitCode: number;\n\tsavedPath?: string;\n\toutputReference?: SavedOutputReference;\n\tsaveError?: string;\n}): { displayOutput: string; savedPath?: string; outputReference?: SavedOutputReference; saveError?: string } {\n\tlet displayOutput = params.truncatedOutput || params.fullOutput;\n\tif (params.exitCode === 0 && params.savedPath) {\n\t\tconst outputReference = params.outputReference ?? formatSavedOutputReference(params.savedPath, params.fullOutput);\n\t\tif (params.outputMode === \"file-only\") {\n\t\t\treturn { displayOutput: outputReference.message, savedPath: params.savedPath, outputReference };\n\t\t}\n\t\tdisplayOutput += `\\n\\n${outputReference.message}`;\n\t\treturn { displayOutput, savedPath: params.savedPath, outputReference };\n\t}\n\tif (params.exitCode === 0 && params.saveError && params.outputPath) {\n\t\tdisplayOutput += `\\n\\nOutput file error: ${params.outputPath}\\n${params.saveError}`;\n\t\treturn { displayOutput, saveError: params.saveError };\n\t}\n\treturn { displayOutput };\n}\n"]}