{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@apholdings/jensen-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { getShellConfig, getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { BashEvidence, ResolvedBashResult } from \"../bash-executor.js\";\nimport { executeBashWithOperations } from \"../bash-executor.js\";\nimport { runForegroundProcess } from \"../process-runner.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Maximum allowed timeout in seconds.\n * Values above this are clamped. This prevents excessively large timeouts.\n */\nconst MAX_TIMEOUT_SECONDS = 86400; // 24 hours\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\n/**\n * Public details for the bash tool shown in the TUI and event stream.\n *\n * All fields added after 1.1.6 are optional in the public type for backward\n * compatibility with consumers that construct mocks, adapters, or fixtures\n * using the 1.1.6 shape. The runtime always produces every field.\n */\nexport interface BashToolDetails {\n\t// 1.1.6 surface\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tcancelled?: boolean;\n\n\t// --- Added in 1.1.7 (optional in public type; always present at runtime) ---\n\tcommand?: string;\n\tcwd?: string;\n\tstdout?: string;\n\tstderr?: string;\n\texitCode?: number | undefined;\n\tstartedAt?: string;\n\tfinishedAt?: string;\n\ttimedOut?: boolean;\n\ttruncated?: boolean;\n\tspawnError?: string;\n\tevidence?: BashEvidence;\n}\n\n/**\n * Internal resolved details type. Every runtime-produced BashToolDetails\n * satisfies this contract. Internal consumers use this type so they can\n * rely on fields being present without optional chaining.\n */\nexport interface ResolvedBashToolDetails extends BashToolDetails {\n\tcommand: string;\n\tcwd: string;\n\tstdout: string;\n\tstderr: string;\n\texitCode: number | undefined;\n\tstartedAt: string;\n\tfinishedAt: string;\n\ttimedOut: boolean;\n\tcancelled: boolean;\n\ttruncated: boolean;\n}\n\nexport interface BashOperations {\n\t/**\n\t * Execute a command and stream output.\n\t * @param command - The command to execute\n\t * @param cwd - Working directory\n\t * @param options - Execution options\n\t * @returns Promise resolving to exit code (null if killed) and optional pipeline data\n\t */\n\texec: (\n\t\tcommand: string,\n\t\tcwd: string,\n\t\toptions: {\n\t\t\t/** Combined stdout + stderr stream (legacy, always called) */\n\t\t\tonData: (data: Buffer) => void;\n\t\t\t/** Separate stdout stream (optional, called alongside onData) */\n\t\t\tonStdout?: (data: Buffer) => void;\n\t\t\t/** Separate stderr stream (optional, called alongside onData) */\n\t\t\tonStderr?: (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\nexport function createLocalBashOperations(): BashOperations {\n\treturn {\n\t\texec: (command, cwd, { onData, onStdout, onStderr, signal, timeout, env }) => {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tconst { shell, args } = getShellConfig();\n\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}\\nCannot execute bash commands.`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Execute user command directly — no preamble, no trap, no wrapper.\n\t\t\t\t// Pipeline detection is done heuristically at the call site\n\t\t\t\t// (executeBashWithOperations), not via injected instrumentation.\n\t\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\n\t\t\t\trunForegroundProcess({\n\t\t\t\t\tchild,\n\t\t\t\t\tonStdout: (data) => {\n\t\t\t\t\t\tonData(data);\n\t\t\t\t\t\tonStdout?.(data);\n\t\t\t\t\t},\n\t\t\t\t\tonStderr: (data) => {\n\t\t\t\t\t\tonData(data);\n\t\t\t\t\t\tonStderr?.(data);\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimeout,\n\t\t\t\t\tkill: () => {\n\t\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t\t.then(resolve)\n\t\t\t\t\t.catch(reject);\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface BashSpawnContext {\n\tcommand: string;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;\n\nfunction resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {\n\tconst baseContext: BashSpawnContext = {\n\t\tcommand,\n\t\tcwd,\n\t\tenv: { ...getShellEnv() },\n\t};\n\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Custom operations for command execution. Default: local shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (e.g., \"shopt -s expand_aliases\" for alias support) */\n\tcommandPrefix?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\n/**\n * Format BashResult into model-facing text content with structured evidence section.\n * The model sees: command, exit code, stdout, stderr, timing, and pipeline metadata.\n * TUI details carry the full BashResult for rendering.\n */\nfunction formatBashResultForModel(\n\tresult: ResolvedBashResult,\n\tcommand: string,\n\tcwd: string,\n): { contentText: string; details: ResolvedBashToolDetails } {\n\tconst lines: string[] = [];\n\n\t// Structured evidence header\n\tlines.push(`Command: \\`${command}\\``);\n\tlines.push(`CWD: ${cwd}`);\n\n\tconst trimmedStdout = result.stdout.trimEnd();\n\tconst trimmedStderr = result.stderr.trimEnd();\n\n\tif (trimmedStdout) {\n\t\tlines.push(\"\");\n\t\tlines.push(\"stdout:\");\n\t\tlines.push(trimmedStdout);\n\t}\n\n\tif (trimmedStderr) {\n\t\tlines.push(\"\");\n\t\tlines.push(\"stderr:\");\n\t\tlines.push(trimmedStderr);\n\t}\n\n\tif (!trimmedStdout && !trimmedStderr) {\n\t\tlines.push(\"\");\n\t\tlines.push(\"(no output)\");\n\t}\n\n\tconst evidenceLines: string[] = [];\n\tevidenceLines.push(`exit_code: ${result.exitCode !== undefined ? result.exitCode : \"undefined\"}`);\n\tevidenceLines.push(`timed_out: ${result.timedOut}`);\n\tevidenceLines.push(`cancelled: ${result.cancelled}`);\n\tevidenceLines.push(`truncated: ${result.truncated}`);\n\n\tif (result.spawnError) {\n\t\tevidenceLines.push(`spawn_error: ${result.spawnError}`);\n\t}\n\n\t// Always render evidence metadata for every command\n\tevidenceLines.push(`exit_status_known: ${result.evidence.exitStatusKnown}`);\n\tevidenceLines.push(`exit_status_authoritative: ${result.evidence.exitStatusAuthoritative}`);\n\tevidenceLines.push(`authority_scope: ${result.evidence.authorityScope}`);\n\tevidenceLines.push(`internal_command_statuses_known: ${result.evidence.internalCommandStatusesKnown}`);\n\tevidenceLines.push(`validation_evidence_authoritative: ${result.evidence.validationEvidenceAuthoritative}`);\n\n\tif (result.evidence.pipelineSuspected) {\n\t\tevidenceLines.push(`pipeline_suspected: true`);\n\t\tevidenceLines.push(`stage_exit_codes_known: false`);\n\t\tif (result.evidence.warning) {\n\t\t\tevidenceLines.push(`warning: ${result.evidence.warning}`);\n\t\t}\n\t}\n\n\tif (result.fullOutputPath && result.truncated) {\n\t\tevidenceLines.push(`full_output: ${result.fullOutputPath}`);\n\t}\n\n\tlines.push(\"\");\n\tlines.push(\"--- Evidence ---\");\n\tlines.push(evidenceLines.join(\"\\n\"));\n\n\tconst details: ResolvedBashToolDetails = {\n\t\tcommand,\n\t\tcwd,\n\t\tstdout: result.stdout,\n\t\tstderr: result.stderr,\n\t\texitCode: result.exitCode,\n\t\tstartedAt: result.startedAt,\n\t\tfinishedAt: result.finishedAt,\n\t\ttimedOut: result.timedOut,\n\t\tcancelled: result.cancelled,\n\t\ttruncated: result.truncated,\n\t\tfullOutputPath: result.fullOutputPath,\n\t\tspawnError: result.spawnError,\n\t\tevidence: result.evidence,\n\t};\n\n\treturn { contentText: lines.join(\"\\n\"), details };\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\tconst ops = options?.operations ?? createLocalBashOperations();\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash 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.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout: _timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t) => {\n\t\t\t// Validate timeout\n\t\t\tif (_timeout !== undefined) {\n\t\t\t\tif (typeof _timeout !== \"number\" || Number.isNaN(_timeout) || !Number.isFinite(_timeout)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Invalid timeout value: ${_timeout}. Timeout must be a finite positive number in seconds, or omitted for no timeout.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (_timeout === 0) {\n\t\t\t\t\tthrow new Error(\"Timeout must be a positive number in seconds, or omitted for no timeout. Got 0.\");\n\t\t\t\t}\n\t\t\t\tif (_timeout < 0) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Timeout must be a positive number in seconds, or omitted for no timeout. Got ${_timeout}.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (_timeout > MAX_TIMEOUT_SECONDS) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Timeout value ${_timeout} exceeds maximum allowed timeout of ${MAX_TIMEOUT_SECONDS} seconds.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst resolvedCommand = commandPrefix ? `${commandPrefix}\\n${command}` : command;\n\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\n\t\t\t// Stream partial updates via the executor's onChunk callback\n\t\t\tconst onChunk = (chunk: string) => {\n\t\t\t\tif (onUpdate) {\n\t\t\t\t\tconst truncated = truncateTail(chunk);\n\t\t\t\t\tconst details: BashToolDetails = {\n\t\t\t\t\t\tcommand: resolvedCommand,\n\t\t\t\t\t\tcwd: spawnContext.cwd,\n\t\t\t\t\t\tstdout: chunk,\n\t\t\t\t\t\tstderr: \"\",\n\t\t\t\t\t\texitCode: undefined,\n\t\t\t\t\t\tstartedAt: \"\",\n\t\t\t\t\t\tfinishedAt: \"\",\n\t\t\t\t\t\ttimedOut: false,\n\t\t\t\t\t\tcancelled: false,\n\t\t\t\t\t\ttruncated: truncated.truncated,\n\t\t\t\t\t\ttruncation: truncated.truncated ? truncated : undefined,\n\t\t\t\t\t};\n\t\t\t\t\tonUpdate({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: truncated.content || \"\" }],\n\t\t\t\t\t\tdetails,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst result = (await executeBashWithOperations(spawnContext.command, spawnContext.cwd, ops, {\n\t\t\t\tonChunk,\n\t\t\t\tsignal,\n\t\t\t\ttimeout: _timeout,\n\t\t\t})) as ResolvedBashResult;\n\n\t\t\tconst { contentText, details } = formatBashResultForModel(result, resolvedCommand, spawnContext.cwd);\n\n\t\t\t// Non-zero exit code, cancelled, timed out, or spawn error: reject so the\n\t\t\t// agent loop marks it isError=true. The error message carries the full\n\t\t\t// structured content so the model sees everything.\n\t\t\tconst isError =\n\t\t\t\t(result.exitCode !== undefined && result.exitCode !== 0 && result.exitCode !== null) ||\n\t\t\t\tresult.cancelled ||\n\t\t\t\tresult.timedOut ||\n\t\t\t\tresult.spawnError !== undefined;\n\n\t\t\tif (isError) {\n\t\t\t\tthrow new Error(contentText);\n\t\t\t}\n\n\t\t\treturn { content: [{ type: \"text\", text: contentText }], details };\n\t\t},\n\t};\n}\n\n/** Default bash tool using process.cwd() - for backwards compatibility */\nexport const bashTool = createBashTool(process.cwd());\n"]}