{"version":3,"file":"powershell.d.ts","sourceRoot":"","sources":["../../../src/core/tools/powershell.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAIN,KAAK,gBAAgB,EAErB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAEtH;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,OAAO,CAAiD;IAChE,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,kBAAkB,CAAS;IAEnC;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAoBtB,4EAA4E;IAC5E,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB1B;IAED,2EAA2E;IAC3E,KAAK,IAAI,MAAM,CAGd;IAED,qDAAqD;IACrD,IAAI,OAAO,IAAI,OAAO,CAErB;CACD;AAwBD,QAAA,MAAM,gBAAgB;;;EAGpB,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACrC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,CACL,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;KACxB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAE1C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACnH;AAED,MAAM,WAAW,sCAAsC;IACtD,aAAa,CAAC,EAAE,MAAM,gBAAgB,CAAC;CACvC;AAgGD,wBAAgB,+BAA+B,CAC9C,OAAO,GAAE,sCAA2C,GAClD,oBAAoB,CAoDtB;AAED,MAAM,WAAW,qBAAqB;IACrC,UAAU,CAAC,EAAE,oBAAoB,CAAC;CAClC;AAQD,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAmBD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC,OAAO,gBAAgB,CAAC,CA8IrH;AAED,eAAO,MAAM,cAAc;;;QAAsC,CAAC","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream, existsSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@apholdings/jensen-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport {\n\tgetPowerShellConfig,\n\tgetShellEnv,\n\tkillProcessTree,\n\ttype PowerShellConfig,\n\tsanitizeBinaryOutput,\n} from \"../../utils/shell.js\";\nimport { runForegroundProcess } from \"../process-runner.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Stateful stream decoder that normalizes PowerShell output to UTF-8.\n *\n * PowerShell may emit UTF-16LE (with or without BOM) or UTF-8 depending on\n * the host version and whether the encoding preamble takes effect.\n * This decoder detects the encoding on the first non-empty chunk and\n * decodes consistently across arbitrary chunk boundaries using TextDecoder.\n *\n * @internal Exported for testing.\n */\nexport class PowerStreamDecoder {\n\tprivate decoder: InstanceType<typeof TextDecoder> | null = null;\n\tprivate encoding: \"utf-8\" | \"utf-16le\" | null = null;\n\tprivate haveReadFirstChunk = false;\n\n\t/**\n\t * Detect encoding from the first bytes of the stream.\n\t * Returns \"utf-16le\" if a BOM or NUL-alternation pattern is found,\n\t * \"utf-8\" otherwise.\n\t */\n\tprivate detectEncoding(chunk: Buffer): \"utf-8\" | \"utf-16le\" {\n\t\t// UTF-16LE BOM\n\t\tif (chunk.length >= 2 && chunk[0] === 0xff && chunk[1] === 0xfe) {\n\t\t\treturn \"utf-16le\";\n\t\t}\n\t\t// NUL-alternation pattern: ASCII text in UTF-16LE has every other byte = 0x00.\n\t\t// Check first N bytes where N = min(chunk.length, 32).\n\t\tconst sampleLen = Math.min(chunk.length, 32);\n\t\tlet nulCount = 0;\n\t\tfor (let i = 1; i < sampleLen; i += 2) {\n\t\t\tif (chunk[i] === 0x00) nulCount++;\n\t\t}\n\t\t// If >70% of odd bytes are NUL, it's likely UTF-16LE.\n\t\tconst oddBytes = Math.floor(sampleLen / 2);\n\t\tif (oddBytes >= 4 && nulCount / oddBytes > 0.7) {\n\t\t\treturn \"utf-16le\";\n\t\t}\n\t\treturn \"utf-8\";\n\t}\n\n\t/** Feed a chunk. Returns decoded UTF-8 string, or \"\" if nothing to emit. */\n\tfeed(chunk: Buffer): string {\n\t\tif (chunk.length === 0) return \"\";\n\n\t\tif (!this.haveReadFirstChunk) {\n\t\t\tthis.haveReadFirstChunk = true;\n\t\t\tthis.encoding = this.detectEncoding(chunk);\n\t\t\tthis.decoder = new TextDecoder(this.encoding, { fatal: false });\n\t\t}\n\n\t\t// If encoding is UTF-16LE and chunk starts with BOM, skip it.\n\t\t// The BOM is only present on the very first chunk.\n\t\tif (this.encoding === \"utf-16le\" && chunk.length >= 2 && chunk[0] === 0xff && chunk[1] === 0xfe) {\n\t\t\tchunk = chunk.subarray(2);\n\t\t}\n\n\t\tif (chunk.length === 0) return \"\";\n\t\treturn this.decoder!.decode(chunk, { stream: true });\n\t}\n\n\t/** Flush any remaining decoder state. Call once when the stream closes. */\n\tflush(): string {\n\t\tif (!this.decoder) return \"\";\n\t\treturn this.decoder.decode(undefined, { stream: false });\n\t}\n\n\t/** Whether any data has been fed to this decoder. */\n\tget hasData(): boolean {\n\t\treturn this.haveReadFirstChunk;\n\t}\n}\n\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pi-powershell-${id}.log`);\n}\n\n/**\n * Encode a PowerShell command as UTF-16LE base64 for use with -EncodedCommand.\n * This avoids all quoting and encoding issues across PowerShell versions.\n */\nfunction encodePowerShellCommand(command: string): string {\n\treturn Buffer.from(command, \"utf-16le\").toString(\"base64\");\n}\n\n/**\n * UTF-8 encoding preamble forced before every user command.\n * On Windows PowerShell 5.1, stdout defaults to the system OEM code page\n * when writing to a pipe, which causes TextDecoder(\"utf-8\") to produce garbled output.\n * This preamble forces UTF-8 output encoding on all PowerShell versions.\n */\nconst ENCODING_PREAMBLE =\n\t\"$ProgressPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$OutputEncoding=[Text.Encoding]::UTF8;\";\n\nconst powershellSchema = Type.Object({\n\tcommand: Type.String({ description: \"PowerShell command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport type PowerShellToolInput = Static<typeof powershellSchema>;\n\nexport interface PowerShellToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tcancelled?: boolean;\n}\n\nexport interface PowerShellValidateResult {\n\tvalid: boolean;\n\terror?: string;\n}\n\nexport interface PowerShellOperations {\n\texec: (\n\t\tcommand: string,\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\ttimeout?: number;\n\t\t\tenv?: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n\n\t/**\n\t * Validate that the PowerShell transport works correctly.\n\t * Runs a probe command and verifies stdout is properly captured.\n\t * If the probe returns exit 0 but no stdout marker, the transport is broken\n\t * (likely encoding mismatch on Windows PowerShell 5.1).\n\t */\n\tvalidate?: (cwd: string, options: { signal?: AbortSignal; timeout?: number }) => Promise<PowerShellValidateResult>;\n}\n\nexport interface CreateLocalPowerShellOperationsOptions {\n\tresolveConfig?: () => PowerShellConfig;\n}\n\n/**\n * Low-level PowerShell spawn helper. Used by both exec and validate.\n */\nfunction spawnRawPowerShell(\n\tresolveConfig: () => PowerShellConfig,\n\twrappedCommand: string,\n\tcwd: string,\n\tspawnEnv: NodeJS.ProcessEnv | undefined,\n): ReturnType<typeof spawn> {\n\tconst shellConfig = resolveConfig();\n\t// Strip -Command from args (last element) and replace with -EncodedCommand + base64\n\tconst baseArgs = shellConfig.args.slice(0, -1);\n\tconst encoded = encodePowerShellCommand(wrappedCommand);\n\tconst spawnArgs = [...baseArgs, \"-EncodedCommand\", encoded];\n\n\t// NOTE: detached: true breaks stdout/stderr capture on pwsh 7.x (streams\n\t// are disconnected from the parent process). killProcessTree uses taskkill /T\n\t// on Windows, which terminates the entire tree without requiring detached.\n\t// Evidence from byte-probe: detached=false → clean UTF-8 output; detached=true → empty.\n\treturn spawn(shellConfig.shell, spawnArgs, {\n\t\tcwd,\n\t\tshell: false,\n\t\tdetached: false,\n\t\twindowsHide: shellConfig.windowsHide,\n\t\tenv: spawnEnv ?? getShellEnv(),\n\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t});\n}\n\n/**\n * Generic exec helper: spawns PowerShell and resolves/rejects based on lifecycle.\n */\nfunction execPowerShell(\n\tresolveConfig: () => PowerShellConfig,\n\tcommand: string,\n\tcwd: string,\n\toptions: {\n\t\tonData: (data: Buffer) => void;\n\t\tsignal?: AbortSignal;\n\t\ttimeout?: number;\n\t\tenv?: NodeJS.ProcessEnv;\n\t},\n): Promise<{ exitCode: number | null }> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (!existsSync(cwd)) {\n\t\t\treject(new Error(`Working directory does not exist: ${cwd}\\nCannot execute PowerShell commands.`));\n\t\t\treturn;\n\t\t}\n\n\t\t// Pre-validate config resolution (spawnRawPowerShell also validates, but we want\n\t\t// a clear error before spawning)\n\t\ttry {\n\t\t\tresolveConfig();\n\t\t} catch (error) {\n\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\treturn;\n\t\t}\n\n\t\tconst wrappedCommand = `${ENCODING_PREAMBLE}${command}`;\n\t\tconst child = spawnRawPowerShell(resolveConfig, wrappedCommand, cwd, options.env);\n\n\t\t// Use separate stateful decoders per stream to normalize PowerShell\n\t\t// output to UTF-8 regardless of whether the host emits UTF-8 or UTF-16LE.\n\t\t// Both streams emit to the same onData callback but are decoded independently.\n\t\tconst stdoutDecoder = new PowerStreamDecoder();\n\t\tconst stderrDecoder = new PowerStreamDecoder();\n\n\t\trunForegroundProcess({\n\t\t\tchild,\n\t\t\tonStdout: (chunk) => {\n\t\t\t\tconst text = stdoutDecoder.feed(chunk);\n\t\t\t\tif (text.length > 0) options.onData(Buffer.from(text, \"utf-8\"));\n\t\t\t},\n\t\t\tonStderr: (chunk) => {\n\t\t\t\tconst text = stderrDecoder.feed(chunk);\n\t\t\t\tif (text.length > 0) options.onData(Buffer.from(text, \"utf-8\"));\n\t\t\t},\n\t\t\tsignal: options.signal,\n\t\t\ttimeout: options.timeout,\n\t\t\tkill: () => {\n\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t},\n\t\t})\n\t\t\t.then(({ exitCode }) => {\n\t\t\t\tconst stdoutFlush = stdoutDecoder.flush();\n\t\t\t\tif (stdoutFlush.length > 0) options.onData(Buffer.from(stdoutFlush, \"utf-8\"));\n\t\t\t\tconst stderrFlush = stderrDecoder.flush();\n\t\t\t\tif (stderrFlush.length > 0) options.onData(Buffer.from(stderrFlush, \"utf-8\"));\n\t\t\t\tresolve({ exitCode });\n\t\t\t})\n\t\t\t.catch(reject);\n\t});\n}\n\nexport function createLocalPowerShellOperations(\n\toptions: CreateLocalPowerShellOperationsOptions = {},\n): PowerShellOperations {\n\tconst resolveConfig = options.resolveConfig ?? getPowerShellConfig;\n\n\tconst ops: PowerShellOperations = {\n\t\texec: (command, cwd, execOptions) => execPowerShell(resolveConfig, command, cwd, execOptions),\n\n\t\tvalidate: (cwd, { signal, timeout }) => {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tconst marker = `JENSEN_PS_HEALTH_${randomBytes(4).toString(\"hex\")}`;\n\t\t\t\tconst probeCommand = `Write-Output '${marker}'`;\n\t\t\t\tlet rawOutput = \"\";\n\n\t\t\t\tops.exec(probeCommand, cwd, {\n\t\t\t\t\t// onData already receives normalized UTF-8 buffers from the decoders\n\t\t\t\t\tonData: (data) => {\n\t\t\t\t\t\trawOutput += data.toString(\"utf-8\");\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimeout: timeout ?? 10,\n\t\t\t\t\tenv: getShellEnv(),\n\t\t\t\t})\n\t\t\t\t\t.then(({ exitCode }) => {\n\t\t\t\t\t\tif (exitCode === 0 && rawOutput.includes(marker)) {\n\t\t\t\t\t\t\tresolve({ valid: true });\n\t\t\t\t\t\t} else if (exitCode === 0 && !rawOutput.includes(marker)) {\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tvalid: false,\n\t\t\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\t\t\"JENSEN_POWERSHELL_TRANSPORT_BROKEN. \" +\n\t\t\t\t\t\t\t\t\t\"The PowerShell host ran successfully but did not produce the expected health probe marker. \" +\n\t\t\t\t\t\t\t\t\t`Output length: ${rawOutput.length} bytes. ` +\n\t\t\t\t\t\t\t\t\t\"Likely causes: detached process disconnecting streams, encoding mismatch that survived normalization, \" +\n\t\t\t\t\t\t\t\t\t\"or PowerShell host producing output on a channel not captured by stdout/stderr pipes.\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tvalid: false,\n\t\t\t\t\t\t\t\terror: `Health probe failed with exit code ${exitCode}. Output: ${rawOutput.slice(0, 200)}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: Error) => {\n\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\tvalid: false,\n\t\t\t\t\t\t\terror: `Health probe error: ${err.message}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n\n\treturn ops;\n}\n\nexport interface PowerShellToolOptions {\n\toperations?: PowerShellOperations;\n}\n\n/**\n * Lazily-tracked health check: only runs once per process lifetime.\n * Reset via resetPowerShellHealthCheck() for testing.\n */\nlet healthCheckResult: PowerShellValidateResult | undefined;\n\nexport function resetPowerShellHealthCheck(): void {\n\thealthCheckResult = undefined;\n}\n\nfunction ensureHealthCheck(ops: PowerShellOperations, cwd: string): Promise<PowerShellValidateResult | undefined> {\n\t// If already validated, skip\n\tif (healthCheckResult !== undefined) {\n\t\treturn Promise.resolve(healthCheckResult);\n\t}\n\n\t// If no validate method, skip (mock operations)\n\tif (!ops.validate) {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\treturn ops.validate(cwd, { timeout: 10 }).then((result) => {\n\t\thealthCheckResult = result;\n\t\treturn result;\n\t});\n}\n\nexport function createPowerShellTool(cwd: string, options?: PowerShellToolOptions): AgentTool<typeof powershellSchema> {\n\tconst ops = options?.operations ?? createLocalPowerShellOperations();\n\n\treturn {\n\t\tname: \"powershell\",\n\t\tlabel: \"powershell\",\n\t\tdescription: `Execute a PowerShell command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds. On Windows, prefers PowerShell 7 (pwsh) and falls back to Windows PowerShell when needed. On non-Windows hosts, this requires PowerShell 7+ (pwsh).`,\n\t\tparameters: powershellSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout }: PowerShellToolInput,\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t) => {\n\t\t\t// Run health check on first invocation\n\t\t\tconst health = await ensureHealthCheck(ops, cwd);\n\t\t\tif (health && !health.valid) {\n\t\t\t\tthrow new Error(`PowerShell transport validation failed: ${health.error}`);\n\t\t\t}\n\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tlet tempFilePath: string | undefined;\n\t\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\t\t\tlet totalBytes = 0;\n\t\t\t\tconst chunks: string[] = [];\n\t\t\t\tlet chunksBytes = 0;\n\t\t\t\tconst maxChunksBytes = DEFAULT_MAX_BYTES * 2;\n\t\t\t\tconst decoder = new TextDecoder();\n\n\t\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\t\ttotalBytes += data.length;\n\n\t\t\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\ttempFileStream.write(text);\n\t\t\t\t\t}\n\n\t\t\t\t\tchunks.push(text);\n\t\t\t\t\tchunksBytes += text.length;\n\n\t\t\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\t\t\tconst removed = chunks.shift();\n\t\t\t\t\t\tif (removed) {\n\t\t\t\t\t\t\tchunksBytes -= removed.length;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tconst fullText = chunks.join(\"\");\n\t\t\t\t\t\tconst truncation = truncateTail(fullText);\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: truncation.content || \"\" }],\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\ttruncation: truncation.truncated ? truncation : undefined,\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tops.exec(command, cwd, {\n\t\t\t\t\tonData: handleData,\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimeout,\n\t\t\t\t\tenv: getShellEnv(),\n\t\t\t\t})\n\t\t\t\t\t.then(({ exitCode }) => {\n\t\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\t\ttempFileStream.end();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst fullOutput = chunks.join(\"\");\n\t\t\t\t\t\tconst truncation = truncateTail(fullOutput);\n\t\t\t\t\t\tlet outputText = truncation.content || \"(no output)\";\n\t\t\t\t\t\tlet details: PowerShellToolDetails | undefined;\n\n\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\tdetails = {\n\t\t\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(fullOutput.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\t\t\toutputText += `\\n\\nCommand exited with code ${exitCode}`;\n\t\t\t\t\t\t\treject(new Error(outputText));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: outputText }], details });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: Error) => {\n\t\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\t\ttempFileStream.end();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet output = chunks.join(\"\");\n\n\t\t\t\t\t\tif (err.message === \"aborted\") {\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\t\t\tconst truncation = truncateTail(output);\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: truncation.content || \"Command aborted\" }],\n\t\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\t\ttruncation: truncation.truncated ? truncation : undefined,\n\t\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t\t\tcancelled: true,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t\t\toutput += `Command timed out after ${timeoutSecs} seconds`;\n\t\t\t\t\t\t\treject(new Error(output));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport const powershellTool = createPowerShellTool(process.cwd());\n"]}