@caupulican/pi-adaptative 0.81.37 → 0.81.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/core/background-lane-controller.d.ts.map +1 -1
  4. package/dist/core/background-lane-controller.js +1 -1
  5. package/dist/core/background-lane-controller.js.map +1 -1
  6. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  7. package/dist/core/context/sqlite-runtime-index.js +13 -7
  8. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  9. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  10. package/dist/core/delegation/worker-actions.js +1 -1
  11. package/dist/core/delegation/worker-actions.js.map +1 -1
  12. package/dist/core/extensions/loader.d.ts.map +1 -1
  13. package/dist/core/extensions/loader.js +3 -7
  14. package/dist/core/extensions/loader.js.map +1 -1
  15. package/dist/core/models/local-runtime.d.ts.map +1 -1
  16. package/dist/core/models/local-runtime.js +4 -3
  17. package/dist/core/models/local-runtime.js.map +1 -1
  18. package/dist/core/tools/bash.d.ts.map +1 -1
  19. package/dist/core/tools/bash.js +2 -2
  20. package/dist/core/tools/bash.js.map +1 -1
  21. package/dist/core/tools/find.d.ts.map +1 -1
  22. package/dist/core/tools/find.js +52 -44
  23. package/dist/core/tools/find.js.map +1 -1
  24. package/dist/core/tools/grep.d.ts.map +1 -1
  25. package/dist/core/tools/grep.js +5 -4
  26. package/dist/core/tools/grep.js.map +1 -1
  27. package/dist/core/tools/render-utils.d.ts.map +1 -1
  28. package/dist/core/tools/render-utils.js +1 -1
  29. package/dist/core/tools/render-utils.js.map +1 -1
  30. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  31. package/dist/core/tools/shell-contract-router.js +46 -11
  32. package/dist/core/tools/shell-contract-router.js.map +1 -1
  33. package/dist/utils/shell.d.ts.map +1 -1
  34. package/dist/utils/shell.js +18 -3
  35. package/dist/utils/shell.js.map +1 -1
  36. package/docs/development.md +2 -0
  37. package/docs/packages.md +1 -1
  38. package/docs/settings.md +2 -2
  39. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  40. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  41. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  42. package/examples/extensions/sandbox/package-lock.json +2 -2
  43. package/examples/extensions/sandbox/package.json +1 -1
  44. package/examples/extensions/with-deps/package-lock.json +2 -2
  45. package/examples/extensions/with-deps/package.json +1 -1
  46. package/npm-shrinkwrap.json +12 -12
  47. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport { type AgentTool, createSilenceWatchdog } from \"@caupulican/pi-agent-core\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n} from \"@caupulican/pi-agent-core/node\";\nimport { Container, Text, truncateToWidth } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { truncateToVisualLines } from \"../../modes/interactive/components/visual-truncate.ts\";\nimport { theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport {\n\tgetPlatformShellToolName,\n\tgetShellConfig,\n\tgetShellEnv,\n\ttype PlatformShellToolName,\n\tprefixPowerShellCommand,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withExclusiveMutationBarrier } from \"./file-mutation-queue.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./git-filter.ts\";\nimport { OutputAccumulator } from \"./output-accumulator.ts\";\nimport { getTextOutput, invalidArgText, str } from \"./render-utils.ts\";\nimport { routeShellContract } from \"./shell-contract-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */\nconst DEFAULT_COMMAND_SILENCE_MS = 600_000;\n/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */\nexport const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;\nexport const MAX_COMMAND_TIMEOUT_SECONDS = 3600;\nconst MIN_COMMAND_TIMEOUT_SECONDS = 0.1;\nlet commandSilenceMsOverride: number | undefined;\nlet commandTimeoutMsOverride: number | undefined;\n\n/** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */\nexport function setCommandSilenceMsForTests(ms: number | undefined): void {\n\tcommandSilenceMsOverride = ms;\n}\n\n/** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */\nexport function setCommandTimeoutMsForTests(ms: number | undefined): void {\n\tcommandTimeoutMsOverride = ms;\n}\n\nexport function resolveCommandTimeoutSeconds(timeout: number | undefined): number {\n\tif (typeof timeout !== \"number\" || !Number.isFinite(timeout) || timeout <= 0) {\n\t\treturn DEFAULT_COMMAND_TIMEOUT_SECONDS;\n\t}\n\treturn Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,\n\t\t}),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tfullOutputError?: string;\n\tpreview?: {\n\t\tcontent: string;\n\t\tskippedLines: number;\n\t};\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (for example SSH).\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)\n\t */\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\nfunction createLocalShellOperations(\n\tshellName: PlatformShellToolName,\n\toptions?: { shellPath?: string },\n): BashOperations {\n\treturn {\n\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\tconst { shell, args } = getShellConfig(options?.shellPath, shellName);\n\t\t\ttry {\n\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t}\n\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tconst terminationController = new AbortController();\n\t\t\tconst onAbort = () => terminationController.abort();\n\t\t\tlet silenceKilled = false;\n\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\tconst silenceWatchdog =\n\t\t\t\t(timeout === undefined || timeout <= 0) && silenceMs > 0\n\t\t\t\t\t? createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs,\n\t\t\t\t\t\t\tonSilence: () => {\n\t\t\t\t\t\t\t\tsilenceKilled = true;\n\t\t\t\t\t\t\t\tterminationController.abort();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst onChunk = (data: Buffer) => {\n\t\t\t\tsilenceWatchdog?.touch();\n\t\t\t\tonData(data);\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tchild.stdout?.on(\"data\", onChunk);\n\t\t\t\tchild.stderr?.on(\"data\", onChunk);\n\t\t\t\tif (signal) {\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\ttimeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,\n\t\t\t\t\tkillGraceMs: 2_000,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (terminal.reason === \"timeout\") throw new Error(`timeout:${timeout}`);\n\t\t\t\tif (silenceKilled) throw new Error(`silence:${silenceMs / 1000}`);\n\t\t\t\treturn { exitCode: terminal.code };\n\t\t\t} finally {\n\t\t\t\tsilenceWatchdog?.disarm();\n\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create bash operations using pi's built-in local shell execution backend.\n *\n * This is useful for extensions that intercept user_bash and still want pi's\n * standard local shell behavior while wrapping or rewriting commands.\n */\nexport function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"bash\", options);\n}\n\n/** Create PowerShell operations using pi's built-in local execution backend. */\nexport function createLocalPowerShellOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"powershell\", options);\n}\n\n/** Create the platform shell backend without requiring callers or the model to choose a shell. */\nexport function createLocalPlatformShellOperations(\n\toptions: { shellPath?: string; commandPrefix?: string; operations?: BashOperations } = {},\n\tplatform: NodeJS.Platform = process.platform,\n): BashOperations {\n\tconst operations =\n\t\toptions.operations ??\n\t\tcreateLocalShellOperations(getPlatformShellToolName(platform), { shellPath: options.shellPath });\n\treturn {\n\t\tasync exec(command, cwd, execOptions) {\n\t\t\tlet resolvedCommand = command;\n\t\t\tif (platform === \"win32\") {\n\t\t\t\tconst route = routeShellContract(command, platform);\n\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\tif (route.kind === \"powershell\") resolvedCommand = route.command;\n\t\t\t}\n\t\t\tif (options.commandPrefix) resolvedCommand = `${options.commandPrefix}\\n${resolvedCommand}`;\n\t\t\tif (platform === \"win32\") resolvedCommand = prefixPowerShellCommand(resolvedCommand);\n\t\t\treturn operations.exec(resolvedCommand, cwd, execOptions);\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 = { command, cwd, env: { ...getShellEnv() } };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Platform used to choose the default backend and contract router. Defaults to process.platform. */\n\tplatform?: NodeJS.Platform;\n\t/** Custom operations for command execution. Default: local platform shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_PREVIEW_BYTES = 8 * 1024;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\ntype BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n\tcachedSkipped: number | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t\tcachedSkipped: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\treturn `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatBashCall(\n\targs: { command?: string; timeout?: number } | undefined,\n\tshellName: PlatformShellToolName,\n): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\tconst prompt = shellName === \"powershell\" ? \"PS>\" : \"$\";\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tconst renderPreview = !options.expanded ? result.details?.preview : undefined;\n\tlet output = (renderPreview ? renderPreview.content : getTextOutput(result as any, showImages)).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tconst fullOutputError = result.details?.fullOutputError;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tif (options.expanded) {\n\t\t\tconst styledOutput = output\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t\t.join(\"\\n\");\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(output, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tstate.cachedLines = preview.visualLines.map((line) => theme.fg(\"toolOutput\", line));\n\t\t\t\t\t\tstate.cachedSkipped = (result.details?.preview?.skippedLines ?? 0) + preview.skippedCount;\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\tif (state.cachedSkipped && state.cachedSkipped > 0) {\n\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${state.cachedSkipped} earlier lines,`) +\n\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t\t\t\t\treturn [\"\", truncateToWidth(hint, width, \"...\"), ...(state.cachedLines ?? [])];\n\t\t\t\t\t}\n\t\t\t\t\treturn [\"\", ...(state.cachedLines ?? [])];\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t\tstate.cachedSkipped = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath || fullOutputError) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t} else if (fullOutputError) {\n\t\t\twarnings.push(`Full output unavailable: ${fullOutputError}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nfunction createShellToolDefinition(\n\tcwd: string,\n\tbackendShell: PlatformShellToolName,\n\tcontractPlatform: NodeJS.Platform,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst toolName = \"bash\";\n\tconst ops =\n\t\toptions?.operations ??\n\t\t(backendShell === \"powershell\"\n\t\t\t? createLocalPowerShellOperations({ shellPath: options?.shellPath })\n\t\t\t: createLocalBashOperations({ shellPath: options?.shellPath }));\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);\n\tconst canFilterCommand = !hasExecutionOverrides;\n\tconst routesWindowsContract = contractPlatform === \"win32\";\n\tconst contractDescription = routesWindowsContract\n\t\t? \"Execute Pi's stable Bash-like command contract in the current working directory. On Windows, a finite deterministic router converts supported simple commands to PowerShell; unsupported Bash constructs fail closed instead of being guessed.\"\n\t\t: \"Execute a Bash command in the current working directory.\";\n\treturn {\n\t\tname: toolName,\n\t\tlabel: toolName,\n\t\tdescription: `${contractDescription} 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. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,\n\t\tpromptSnippet: routesWindowsContract\n\t\t\t? \"Execute simple Bash-like commands; Pi routes supported forms deterministically to PowerShell on Windows\"\n\t\t\t: \"Execute Bash commands (ls, grep, find, etc.)\",\n\t\tpromptGuidelines: routesWindowsContract\n\t\t\t? [\n\t\t\t\t\t\"Use the bash tool's portable simple-command contract on Windows; do not write PowerShell or ask the user to choose a shell.\",\n\t\t\t\t\t\"Use one simple command per call. The deterministic router rejects pipelines, redirection, expansion, shell chaining, nested shells, and unsupported Bash forms; use dedicated read/edit/search tools or separate calls instead.\",\n\t\t\t\t\t\"Supported Bash-like file commands are converted with literal-path PowerShell operations; verify targets before recursive rm, cp, or mv calls.\",\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t],\n\t\tparameters: bashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.previewSnapshot(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES, {\n\t\t\t\t\tpersistIfFullTruncated: true,\n\t\t\t\t});\n\t\t\t\tconst preview = {\n\t\t\t\t\tcontent: snapshot.content,\n\t\t\t\t\tskippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),\n\t\t\t\t};\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: preview.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\tpreview,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\treturn output.snapshot({ persistIfTruncated: true });\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tconst preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);\n\t\t\t\tconst fullOutputNotice = snapshot.fullOutputPath\n\t\t\t\t\t? `Full output: ${snapshot.fullOutputPath}`\n\t\t\t\t\t: snapshot.fullOutputError\n\t\t\t\t\t\t? `Full output unavailable: ${snapshot.fullOutputError}`\n\t\t\t\t\t\t: \"Full output unavailable\";\n\t\t\t\tif (truncation.truncated || preview.skippedLines > 0) {\n\t\t\t\t\tdetails = { preview };\n\t\t\t\t}\n\t\t\t\tif (snapshot.fullOutputPath || snapshot.fullOutputError) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). ${fullOutputNotice}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. ${fullOutputNotice}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). ${fullOutputNotice}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\t\t\tconst effectiveTimeoutSeconds =\n\t\t\t\ttypeof timeout === \"number\" && Number.isFinite(timeout) && timeout > 0\n\t\t\t\t\t? resolveCommandTimeoutSeconds(timeout)\n\t\t\t\t\t: (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;\n\n\t\t\ttry {\n\t\t\t\tif (canFilterCommand) {\n\t\t\t\t\tconst classification = classifyGitCommand(command, getShellEnv());\n\t\t\t\t\tif (classification.eligible && classification.subcommand) {\n\t\t\t\t\t\tconst res = await executeFilteredGit(\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\tclassification.subcommand,\n\t\t\t\t\t\t\tclassification.globalOptions || [],\n\t\t\t\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t\t\t\t{ signal, timeout: effectiveTimeoutSeconds },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (res.exitCode !== -100) {\n\t\t\t\t\t\t\toutput.append(res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\"));\n\t\t\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\t\t\tif (res.exitCode !== 0) {\n\t\t\t\t\t\t\t\tconst { text: rawOutputText } = formatOutput(snapshot);\n\t\t\t\t\t\t\t\tthrow new Error(appendStatus(rawOutputText, `Command exited with code ${res.exitCode}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst details = snapshot.truncation.truncated\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttruncation: snapshot.truncation,\n\t\t\t\t\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: snapshot.fullOutputPath || snapshot.fullOutputError\n\t\t\t\t\t\t\t\t\t? { fullOutputPath: snapshot.fullOutputPath, fullOutputError: snapshot.fullOutputError }\n\t\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\treturn { content: [{ type: \"text\", text: res.output }], details };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet backendCommand = command;\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tconst route = routeShellContract(command, contractPlatform);\n\t\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t\tbackendCommand = route.command;\n\t\t\t\t}\n\t\t\t\tconst commandWithPrefix = commandPrefix ? `${commandPrefix}\\n${backendCommand}` : backendCommand;\n\t\t\t\tconst resolvedCommand =\n\t\t\t\t\tbackendShell === \"powershell\" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;\n\t\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\t// Shell commands cannot statically declare which files they mutate, so the\n\t\t\t\t\t// actual execution takes the coarse exclusive barrier: it waits for\n\t\t\t\t\t// in-flight edit/write mutations to drain and blocks new ones meanwhile.\n\t\t\t\t\tconst result = await withExclusiveMutationBarrier(() =>\n\t\t\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\ttimeout: effectiveTimeoutSeconds,\n\t\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, \"Command aborted\"));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(appendStatus(text, `Command timed out after ${timeoutSecs} seconds`));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\t\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\t\t\t\tconst recovery =\n\t\t\t\t\t\t\tbackendShell === \"bash\"\n\t\t\t\t\t\t\t\t? \"re-run it with an explicit timeout, or run it in the background with '&'.\"\n\t\t\t\t\t\t\t\t: \"re-run it with an explicit timeout.\";\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(\n\t\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`,\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\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, `Command exited with code ${exitCode}`));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t\tawait output.closeTempFile();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatBashCall(args, toolName));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult as any,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst platform = options?.platform ?? process.platform;\n\treturn createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\treturn wrapToolDefinition(createBashToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport { type AgentTool, createSilenceWatchdog } from \"@caupulican/pi-agent-core\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n} from \"@caupulican/pi-agent-core/node\";\nimport { Container, Text, truncateToWidth } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { truncateToVisualLines } from \"../../modes/interactive/components/visual-truncate.ts\";\nimport { theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport {\n\tgetPlatformShellToolName,\n\tgetShellConfig,\n\tgetShellEnv,\n\ttype PlatformShellToolName,\n\tprefixPowerShellCommand,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withExclusiveMutationBarrier } from \"./file-mutation-queue.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./git-filter.ts\";\nimport { OutputAccumulator } from \"./output-accumulator.ts\";\nimport { getTextOutput, invalidArgText, str } from \"./render-utils.ts\";\nimport { routeShellContract } from \"./shell-contract-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */\nconst DEFAULT_COMMAND_SILENCE_MS = 600_000;\n/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */\nexport const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;\nexport const MAX_COMMAND_TIMEOUT_SECONDS = 3600;\nconst MIN_COMMAND_TIMEOUT_SECONDS = 0.1;\nlet commandSilenceMsOverride: number | undefined;\nlet commandTimeoutMsOverride: number | undefined;\n\n/** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */\nexport function setCommandSilenceMsForTests(ms: number | undefined): void {\n\tcommandSilenceMsOverride = ms;\n}\n\n/** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */\nexport function setCommandTimeoutMsForTests(ms: number | undefined): void {\n\tcommandTimeoutMsOverride = ms;\n}\n\nexport function resolveCommandTimeoutSeconds(timeout: number | undefined): number {\n\tif (typeof timeout !== \"number\" || !Number.isFinite(timeout) || timeout <= 0) {\n\t\treturn DEFAULT_COMMAND_TIMEOUT_SECONDS;\n\t}\n\treturn Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,\n\t\t}),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tfullOutputError?: string;\n\tpreview?: {\n\t\tcontent: string;\n\t\tskippedLines: number;\n\t};\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (for example SSH).\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)\n\t */\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\nfunction createLocalShellOperations(\n\tshellName: PlatformShellToolName,\n\toptions?: { shellPath?: string },\n): BashOperations {\n\treturn {\n\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\tconst { shell, args } = getShellConfig(options?.shellPath, shellName);\n\t\t\ttry {\n\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t}\n\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tconst terminationController = new AbortController();\n\t\t\tconst onAbort = () => terminationController.abort();\n\t\t\tlet silenceKilled = false;\n\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\tconst silenceWatchdog =\n\t\t\t\t(timeout === undefined || timeout <= 0) && silenceMs > 0\n\t\t\t\t\t? createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs,\n\t\t\t\t\t\t\tonSilence: () => {\n\t\t\t\t\t\t\t\tsilenceKilled = true;\n\t\t\t\t\t\t\t\tterminationController.abort();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst onChunk = (data: Buffer) => {\n\t\t\t\tsilenceWatchdog?.touch();\n\t\t\t\tonData(data);\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tchild.stdout?.on(\"data\", onChunk);\n\t\t\t\tchild.stderr?.on(\"data\", onChunk);\n\t\t\t\tif (signal) {\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\ttimeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,\n\t\t\t\t\tkillGraceMs: 2_000,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (terminal.reason === \"timeout\") throw new Error(`timeout:${timeout}`);\n\t\t\t\tif (silenceKilled) throw new Error(`silence:${silenceMs / 1000}`);\n\t\t\t\treturn { exitCode: terminal.code };\n\t\t\t} finally {\n\t\t\t\tsilenceWatchdog?.disarm();\n\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create bash operations using pi's built-in local shell execution backend.\n *\n * This is useful for extensions that intercept user_bash and still want pi's\n * standard local shell behavior while wrapping or rewriting commands.\n */\nexport function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"bash\", options);\n}\n\n/** Create PowerShell operations using pi's built-in local execution backend. */\nexport function createLocalPowerShellOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"powershell\", options);\n}\n\n/** Create the platform shell backend without requiring callers or the model to choose a shell. */\nexport function createLocalPlatformShellOperations(\n\toptions: { shellPath?: string; commandPrefix?: string; operations?: BashOperations } = {},\n\tplatform: NodeJS.Platform = process.platform,\n): BashOperations {\n\tconst operations =\n\t\toptions.operations ??\n\t\tcreateLocalShellOperations(getPlatformShellToolName(platform), { shellPath: options.shellPath });\n\treturn {\n\t\tasync exec(command, cwd, execOptions) {\n\t\t\tlet resolvedCommand = command;\n\t\t\tif (platform === \"win32\") {\n\t\t\t\tconst route = routeShellContract(command, platform);\n\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\tif (route.kind === \"powershell\") resolvedCommand = route.command;\n\t\t\t}\n\t\t\tif (options.commandPrefix) resolvedCommand = `${options.commandPrefix}\\n${resolvedCommand}`;\n\t\t\tif (platform === \"win32\") resolvedCommand = prefixPowerShellCommand(resolvedCommand);\n\t\t\treturn operations.exec(resolvedCommand, cwd, execOptions);\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 = { command, cwd, env: { ...getShellEnv() } };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Platform used to choose the default backend and contract router. Defaults to process.platform. */\n\tplatform?: NodeJS.Platform;\n\t/** Custom operations for command execution. Default: local platform shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_PREVIEW_BYTES = 8 * 1024;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\ntype BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n\tcachedSkipped: number | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t\tcachedSkipped: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\treturn `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatBashCall(\n\targs: { command?: string; timeout?: number } | undefined,\n\tshellName: PlatformShellToolName,\n): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\tconst prompt = shellName === \"powershell\" ? \"PS>\" : \"$\";\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tconst renderPreview = !options.expanded ? result.details?.preview : undefined;\n\tlet output = (renderPreview ? renderPreview.content : getTextOutput(result as any, showImages)).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tconst fullOutputError = result.details?.fullOutputError;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tif (options.expanded) {\n\t\t\tconst styledOutput = output\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t\t.join(\"\\n\");\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(output, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tstate.cachedLines = preview.visualLines.map((line) => theme.fg(\"toolOutput\", line));\n\t\t\t\t\t\tstate.cachedSkipped = (result.details?.preview?.skippedLines ?? 0) + preview.skippedCount;\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\tif (state.cachedSkipped && state.cachedSkipped > 0) {\n\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${state.cachedSkipped} earlier lines,`) +\n\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t\t\t\t\treturn [\"\", truncateToWidth(hint, width, \"...\"), ...(state.cachedLines ?? [])];\n\t\t\t\t\t}\n\t\t\t\t\treturn [\"\", ...(state.cachedLines ?? [])];\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t\tstate.cachedSkipped = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath || fullOutputError) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t} else if (fullOutputError) {\n\t\t\twarnings.push(`Full output unavailable: ${fullOutputError}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nfunction createShellToolDefinition(\n\tcwd: string,\n\tbackendShell: PlatformShellToolName,\n\tcontractPlatform: NodeJS.Platform,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst toolName = \"bash\";\n\tconst ops =\n\t\toptions?.operations ??\n\t\t(backendShell === \"powershell\"\n\t\t\t? createLocalPowerShellOperations({ shellPath: options?.shellPath })\n\t\t\t: createLocalBashOperations({ shellPath: options?.shellPath }));\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);\n\tconst canFilterCommand = !hasExecutionOverrides;\n\tconst routesWindowsContract = contractPlatform === \"win32\";\n\tconst contractDescription = routesWindowsContract\n\t\t? \"Execute Pi's stable Bash-like command contract in the current working directory. On Windows, a finite deterministic router converts supported simple commands to PowerShell; unsupported Bash constructs fail closed instead of being guessed.\"\n\t\t: \"Execute a Bash command in the current working directory.\";\n\treturn {\n\t\tname: toolName,\n\t\tlabel: toolName,\n\t\tdescription: `${contractDescription} 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. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,\n\t\tpromptSnippet: routesWindowsContract\n\t\t\t? \"Execute simple Bash-like commands; Pi routes supported forms deterministically to PowerShell on Windows\"\n\t\t\t: \"Execute Bash commands (ls, grep, find, etc.)\",\n\t\tpromptGuidelines: routesWindowsContract\n\t\t\t? [\n\t\t\t\t\t\"Use the bash tool's portable simple-command contract on Windows; do not write PowerShell or ask the user to choose a shell.\",\n\t\t\t\t\t\"Use one simple command per call. The deterministic router rejects pipelines, redirection, expansion, shell chaining, nested shells, and unsupported Bash forms; use dedicated read/edit/search tools or separate calls instead.\",\n\t\t\t\t\t\"Supported Bash-like file commands are converted with literal-path PowerShell operations; verify targets before recursive rm, cp, or mv calls.\",\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t],\n\t\tparameters: bashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.previewSnapshot(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES, {\n\t\t\t\t\tpersistIfFullTruncated: true,\n\t\t\t\t});\n\t\t\t\tconst preview = {\n\t\t\t\t\tcontent: snapshot.content.replace(/\\r/g, \"\"),\n\t\t\t\t\tskippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),\n\t\t\t\t};\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: preview.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\tpreview,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\treturn output.snapshot({ persistIfTruncated: true });\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content.replace(/\\r/g, \"\") || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tconst preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);\n\t\t\t\tconst fullOutputNotice = snapshot.fullOutputPath\n\t\t\t\t\t? `Full output: ${snapshot.fullOutputPath}`\n\t\t\t\t\t: snapshot.fullOutputError\n\t\t\t\t\t\t? `Full output unavailable: ${snapshot.fullOutputError}`\n\t\t\t\t\t\t: \"Full output unavailable\";\n\t\t\t\tif (truncation.truncated || preview.skippedLines > 0) {\n\t\t\t\t\tdetails = { preview };\n\t\t\t\t}\n\t\t\t\tif (snapshot.fullOutputPath || snapshot.fullOutputError) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). ${fullOutputNotice}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. ${fullOutputNotice}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). ${fullOutputNotice}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\t\t\tconst effectiveTimeoutSeconds =\n\t\t\t\ttypeof timeout === \"number\" && Number.isFinite(timeout) && timeout > 0\n\t\t\t\t\t? resolveCommandTimeoutSeconds(timeout)\n\t\t\t\t\t: (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;\n\n\t\t\ttry {\n\t\t\t\tif (canFilterCommand) {\n\t\t\t\t\tconst classification = classifyGitCommand(command, getShellEnv());\n\t\t\t\t\tif (classification.eligible && classification.subcommand) {\n\t\t\t\t\t\tconst res = await executeFilteredGit(\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\tclassification.subcommand,\n\t\t\t\t\t\t\tclassification.globalOptions || [],\n\t\t\t\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t\t\t\t{ signal, timeout: effectiveTimeoutSeconds },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (res.exitCode !== -100) {\n\t\t\t\t\t\t\toutput.append(res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\"));\n\t\t\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\t\t\tif (res.exitCode !== 0) {\n\t\t\t\t\t\t\t\tconst { text: rawOutputText } = formatOutput(snapshot);\n\t\t\t\t\t\t\t\tthrow new Error(appendStatus(rawOutputText, `Command exited with code ${res.exitCode}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst details = snapshot.truncation.truncated\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttruncation: snapshot.truncation,\n\t\t\t\t\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: snapshot.fullOutputPath || snapshot.fullOutputError\n\t\t\t\t\t\t\t\t\t? { fullOutputPath: snapshot.fullOutputPath, fullOutputError: snapshot.fullOutputError }\n\t\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\treturn { content: [{ type: \"text\", text: res.output }], details };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet backendCommand = command;\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tconst route = routeShellContract(command, contractPlatform);\n\t\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t\tbackendCommand = route.command;\n\t\t\t\t}\n\t\t\t\tconst commandWithPrefix = commandPrefix ? `${commandPrefix}\\n${backendCommand}` : backendCommand;\n\t\t\t\tconst resolvedCommand =\n\t\t\t\t\tbackendShell === \"powershell\" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;\n\t\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\t// Shell commands cannot statically declare which files they mutate, so the\n\t\t\t\t\t// actual execution takes the coarse exclusive barrier: it waits for\n\t\t\t\t\t// in-flight edit/write mutations to drain and blocks new ones meanwhile.\n\t\t\t\t\tconst result = await withExclusiveMutationBarrier(() =>\n\t\t\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\ttimeout: effectiveTimeoutSeconds,\n\t\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, \"Command aborted\"));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(appendStatus(text, `Command timed out after ${timeoutSecs} seconds`));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\t\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\t\t\t\tconst recovery =\n\t\t\t\t\t\t\tbackendShell === \"bash\"\n\t\t\t\t\t\t\t\t? \"re-run it with an explicit timeout, or run it in the background with '&'.\"\n\t\t\t\t\t\t\t\t: \"re-run it with an explicit timeout.\";\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(\n\t\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`,\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\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, `Command exited with code ${exitCode}`));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t\tawait output.closeTempFile();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatBashCall(args, toolName));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult as any,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst platform = options?.platform ?? process.platform;\n\treturn createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\treturn wrapToolDefinition(createBashToolDefinition(cwd, options));\n}\n"]}
@@ -290,7 +290,7 @@ function createShellToolDefinition(cwd, backendShell, contractPlatform, options)
290
290
  persistIfFullTruncated: true,
291
291
  });
292
292
  const preview = {
293
- content: snapshot.content,
293
+ content: snapshot.content.replace(/\r/g, ""),
294
294
  skippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),
295
295
  };
296
296
  onUpdate({
@@ -339,7 +339,7 @@ function createShellToolDefinition(cwd, backendShell, contractPlatform, options)
339
339
  };
340
340
  const formatOutput = (snapshot, emptyText = "(no output)") => {
341
341
  const truncation = snapshot.truncation;
342
- let text = snapshot.content || emptyText;
342
+ let text = snapshot.content.replace(/\r/g, "") || emptyText;
343
343
  let details;
344
344
  const preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);
345
345
  const fullOutputNotice = snapshot.fullOutputPath
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport { type AgentTool, createSilenceWatchdog } from \"@caupulican/pi-agent-core\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n} from \"@caupulican/pi-agent-core/node\";\nimport { Container, Text, truncateToWidth } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { truncateToVisualLines } from \"../../modes/interactive/components/visual-truncate.ts\";\nimport { theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport {\n\tgetPlatformShellToolName,\n\tgetShellConfig,\n\tgetShellEnv,\n\ttype PlatformShellToolName,\n\tprefixPowerShellCommand,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withExclusiveMutationBarrier } from \"./file-mutation-queue.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./git-filter.ts\";\nimport { OutputAccumulator } from \"./output-accumulator.ts\";\nimport { getTextOutput, invalidArgText, str } from \"./render-utils.ts\";\nimport { routeShellContract } from \"./shell-contract-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */\nconst DEFAULT_COMMAND_SILENCE_MS = 600_000;\n/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */\nexport const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;\nexport const MAX_COMMAND_TIMEOUT_SECONDS = 3600;\nconst MIN_COMMAND_TIMEOUT_SECONDS = 0.1;\nlet commandSilenceMsOverride: number | undefined;\nlet commandTimeoutMsOverride: number | undefined;\n\n/** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */\nexport function setCommandSilenceMsForTests(ms: number | undefined): void {\n\tcommandSilenceMsOverride = ms;\n}\n\n/** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */\nexport function setCommandTimeoutMsForTests(ms: number | undefined): void {\n\tcommandTimeoutMsOverride = ms;\n}\n\nexport function resolveCommandTimeoutSeconds(timeout: number | undefined): number {\n\tif (typeof timeout !== \"number\" || !Number.isFinite(timeout) || timeout <= 0) {\n\t\treturn DEFAULT_COMMAND_TIMEOUT_SECONDS;\n\t}\n\treturn Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,\n\t\t}),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tfullOutputError?: string;\n\tpreview?: {\n\t\tcontent: string;\n\t\tskippedLines: number;\n\t};\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (for example SSH).\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)\n\t */\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\nfunction createLocalShellOperations(\n\tshellName: PlatformShellToolName,\n\toptions?: { shellPath?: string },\n): BashOperations {\n\treturn {\n\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\tconst { shell, args } = getShellConfig(options?.shellPath, shellName);\n\t\t\ttry {\n\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t}\n\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tconst terminationController = new AbortController();\n\t\t\tconst onAbort = () => terminationController.abort();\n\t\t\tlet silenceKilled = false;\n\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\tconst silenceWatchdog =\n\t\t\t\t(timeout === undefined || timeout <= 0) && silenceMs > 0\n\t\t\t\t\t? createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs,\n\t\t\t\t\t\t\tonSilence: () => {\n\t\t\t\t\t\t\t\tsilenceKilled = true;\n\t\t\t\t\t\t\t\tterminationController.abort();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst onChunk = (data: Buffer) => {\n\t\t\t\tsilenceWatchdog?.touch();\n\t\t\t\tonData(data);\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tchild.stdout?.on(\"data\", onChunk);\n\t\t\t\tchild.stderr?.on(\"data\", onChunk);\n\t\t\t\tif (signal) {\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\ttimeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,\n\t\t\t\t\tkillGraceMs: 2_000,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (terminal.reason === \"timeout\") throw new Error(`timeout:${timeout}`);\n\t\t\t\tif (silenceKilled) throw new Error(`silence:${silenceMs / 1000}`);\n\t\t\t\treturn { exitCode: terminal.code };\n\t\t\t} finally {\n\t\t\t\tsilenceWatchdog?.disarm();\n\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create bash operations using pi's built-in local shell execution backend.\n *\n * This is useful for extensions that intercept user_bash and still want pi's\n * standard local shell behavior while wrapping or rewriting commands.\n */\nexport function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"bash\", options);\n}\n\n/** Create PowerShell operations using pi's built-in local execution backend. */\nexport function createLocalPowerShellOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"powershell\", options);\n}\n\n/** Create the platform shell backend without requiring callers or the model to choose a shell. */\nexport function createLocalPlatformShellOperations(\n\toptions: { shellPath?: string; commandPrefix?: string; operations?: BashOperations } = {},\n\tplatform: NodeJS.Platform = process.platform,\n): BashOperations {\n\tconst operations =\n\t\toptions.operations ??\n\t\tcreateLocalShellOperations(getPlatformShellToolName(platform), { shellPath: options.shellPath });\n\treturn {\n\t\tasync exec(command, cwd, execOptions) {\n\t\t\tlet resolvedCommand = command;\n\t\t\tif (platform === \"win32\") {\n\t\t\t\tconst route = routeShellContract(command, platform);\n\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\tif (route.kind === \"powershell\") resolvedCommand = route.command;\n\t\t\t}\n\t\t\tif (options.commandPrefix) resolvedCommand = `${options.commandPrefix}\\n${resolvedCommand}`;\n\t\t\tif (platform === \"win32\") resolvedCommand = prefixPowerShellCommand(resolvedCommand);\n\t\t\treturn operations.exec(resolvedCommand, cwd, execOptions);\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 = { command, cwd, env: { ...getShellEnv() } };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Platform used to choose the default backend and contract router. Defaults to process.platform. */\n\tplatform?: NodeJS.Platform;\n\t/** Custom operations for command execution. Default: local platform shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_PREVIEW_BYTES = 8 * 1024;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\ntype BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n\tcachedSkipped: number | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t\tcachedSkipped: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\treturn `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatBashCall(\n\targs: { command?: string; timeout?: number } | undefined,\n\tshellName: PlatformShellToolName,\n): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\tconst prompt = shellName === \"powershell\" ? \"PS>\" : \"$\";\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tconst renderPreview = !options.expanded ? result.details?.preview : undefined;\n\tlet output = (renderPreview ? renderPreview.content : getTextOutput(result as any, showImages)).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tconst fullOutputError = result.details?.fullOutputError;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tif (options.expanded) {\n\t\t\tconst styledOutput = output\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t\t.join(\"\\n\");\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(output, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tstate.cachedLines = preview.visualLines.map((line) => theme.fg(\"toolOutput\", line));\n\t\t\t\t\t\tstate.cachedSkipped = (result.details?.preview?.skippedLines ?? 0) + preview.skippedCount;\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\tif (state.cachedSkipped && state.cachedSkipped > 0) {\n\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${state.cachedSkipped} earlier lines,`) +\n\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t\t\t\t\treturn [\"\", truncateToWidth(hint, width, \"...\"), ...(state.cachedLines ?? [])];\n\t\t\t\t\t}\n\t\t\t\t\treturn [\"\", ...(state.cachedLines ?? [])];\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t\tstate.cachedSkipped = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath || fullOutputError) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t} else if (fullOutputError) {\n\t\t\twarnings.push(`Full output unavailable: ${fullOutputError}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nfunction createShellToolDefinition(\n\tcwd: string,\n\tbackendShell: PlatformShellToolName,\n\tcontractPlatform: NodeJS.Platform,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst toolName = \"bash\";\n\tconst ops =\n\t\toptions?.operations ??\n\t\t(backendShell === \"powershell\"\n\t\t\t? createLocalPowerShellOperations({ shellPath: options?.shellPath })\n\t\t\t: createLocalBashOperations({ shellPath: options?.shellPath }));\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);\n\tconst canFilterCommand = !hasExecutionOverrides;\n\tconst routesWindowsContract = contractPlatform === \"win32\";\n\tconst contractDescription = routesWindowsContract\n\t\t? \"Execute Pi's stable Bash-like command contract in the current working directory. On Windows, a finite deterministic router converts supported simple commands to PowerShell; unsupported Bash constructs fail closed instead of being guessed.\"\n\t\t: \"Execute a Bash command in the current working directory.\";\n\treturn {\n\t\tname: toolName,\n\t\tlabel: toolName,\n\t\tdescription: `${contractDescription} 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. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,\n\t\tpromptSnippet: routesWindowsContract\n\t\t\t? \"Execute simple Bash-like commands; Pi routes supported forms deterministically to PowerShell on Windows\"\n\t\t\t: \"Execute Bash commands (ls, grep, find, etc.)\",\n\t\tpromptGuidelines: routesWindowsContract\n\t\t\t? [\n\t\t\t\t\t\"Use the bash tool's portable simple-command contract on Windows; do not write PowerShell or ask the user to choose a shell.\",\n\t\t\t\t\t\"Use one simple command per call. The deterministic router rejects pipelines, redirection, expansion, shell chaining, nested shells, and unsupported Bash forms; use dedicated read/edit/search tools or separate calls instead.\",\n\t\t\t\t\t\"Supported Bash-like file commands are converted with literal-path PowerShell operations; verify targets before recursive rm, cp, or mv calls.\",\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t],\n\t\tparameters: bashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.previewSnapshot(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES, {\n\t\t\t\t\tpersistIfFullTruncated: true,\n\t\t\t\t});\n\t\t\t\tconst preview = {\n\t\t\t\t\tcontent: snapshot.content,\n\t\t\t\t\tskippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),\n\t\t\t\t};\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: preview.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\tpreview,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\treturn output.snapshot({ persistIfTruncated: true });\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tconst preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);\n\t\t\t\tconst fullOutputNotice = snapshot.fullOutputPath\n\t\t\t\t\t? `Full output: ${snapshot.fullOutputPath}`\n\t\t\t\t\t: snapshot.fullOutputError\n\t\t\t\t\t\t? `Full output unavailable: ${snapshot.fullOutputError}`\n\t\t\t\t\t\t: \"Full output unavailable\";\n\t\t\t\tif (truncation.truncated || preview.skippedLines > 0) {\n\t\t\t\t\tdetails = { preview };\n\t\t\t\t}\n\t\t\t\tif (snapshot.fullOutputPath || snapshot.fullOutputError) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). ${fullOutputNotice}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. ${fullOutputNotice}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). ${fullOutputNotice}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\t\t\tconst effectiveTimeoutSeconds =\n\t\t\t\ttypeof timeout === \"number\" && Number.isFinite(timeout) && timeout > 0\n\t\t\t\t\t? resolveCommandTimeoutSeconds(timeout)\n\t\t\t\t\t: (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;\n\n\t\t\ttry {\n\t\t\t\tif (canFilterCommand) {\n\t\t\t\t\tconst classification = classifyGitCommand(command, getShellEnv());\n\t\t\t\t\tif (classification.eligible && classification.subcommand) {\n\t\t\t\t\t\tconst res = await executeFilteredGit(\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\tclassification.subcommand,\n\t\t\t\t\t\t\tclassification.globalOptions || [],\n\t\t\t\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t\t\t\t{ signal, timeout: effectiveTimeoutSeconds },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (res.exitCode !== -100) {\n\t\t\t\t\t\t\toutput.append(res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\"));\n\t\t\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\t\t\tif (res.exitCode !== 0) {\n\t\t\t\t\t\t\t\tconst { text: rawOutputText } = formatOutput(snapshot);\n\t\t\t\t\t\t\t\tthrow new Error(appendStatus(rawOutputText, `Command exited with code ${res.exitCode}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst details = snapshot.truncation.truncated\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttruncation: snapshot.truncation,\n\t\t\t\t\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: snapshot.fullOutputPath || snapshot.fullOutputError\n\t\t\t\t\t\t\t\t\t? { fullOutputPath: snapshot.fullOutputPath, fullOutputError: snapshot.fullOutputError }\n\t\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\treturn { content: [{ type: \"text\", text: res.output }], details };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet backendCommand = command;\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tconst route = routeShellContract(command, contractPlatform);\n\t\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t\tbackendCommand = route.command;\n\t\t\t\t}\n\t\t\t\tconst commandWithPrefix = commandPrefix ? `${commandPrefix}\\n${backendCommand}` : backendCommand;\n\t\t\t\tconst resolvedCommand =\n\t\t\t\t\tbackendShell === \"powershell\" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;\n\t\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\t// Shell commands cannot statically declare which files they mutate, so the\n\t\t\t\t\t// actual execution takes the coarse exclusive barrier: it waits for\n\t\t\t\t\t// in-flight edit/write mutations to drain and blocks new ones meanwhile.\n\t\t\t\t\tconst result = await withExclusiveMutationBarrier(() =>\n\t\t\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\ttimeout: effectiveTimeoutSeconds,\n\t\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, \"Command aborted\"));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(appendStatus(text, `Command timed out after ${timeoutSecs} seconds`));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\t\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\t\t\t\tconst recovery =\n\t\t\t\t\t\t\tbackendShell === \"bash\"\n\t\t\t\t\t\t\t\t? \"re-run it with an explicit timeout, or run it in the background with '&'.\"\n\t\t\t\t\t\t\t\t: \"re-run it with an explicit timeout.\";\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(\n\t\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`,\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\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, `Command exited with code ${exitCode}`));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t\tawait output.closeTempFile();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatBashCall(args, toolName));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult as any,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst platform = options?.platform ?? process.platform;\n\treturn createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\treturn wrapToolDefinition(createBashToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport { type AgentTool, createSilenceWatchdog } from \"@caupulican/pi-agent-core\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n} from \"@caupulican/pi-agent-core/node\";\nimport { Container, Text, truncateToWidth } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { truncateToVisualLines } from \"../../modes/interactive/components/visual-truncate.ts\";\nimport { theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport {\n\tgetPlatformShellToolName,\n\tgetShellConfig,\n\tgetShellEnv,\n\ttype PlatformShellToolName,\n\tprefixPowerShellCommand,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withExclusiveMutationBarrier } from \"./file-mutation-queue.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./git-filter.ts\";\nimport { OutputAccumulator } from \"./output-accumulator.ts\";\nimport { getTextOutput, invalidArgText, str } from \"./render-utils.ts\";\nimport { routeShellContract } from \"./shell-contract-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */\nconst DEFAULT_COMMAND_SILENCE_MS = 600_000;\n/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */\nexport const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;\nexport const MAX_COMMAND_TIMEOUT_SECONDS = 3600;\nconst MIN_COMMAND_TIMEOUT_SECONDS = 0.1;\nlet commandSilenceMsOverride: number | undefined;\nlet commandTimeoutMsOverride: number | undefined;\n\n/** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */\nexport function setCommandSilenceMsForTests(ms: number | undefined): void {\n\tcommandSilenceMsOverride = ms;\n}\n\n/** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */\nexport function setCommandTimeoutMsForTests(ms: number | undefined): void {\n\tcommandTimeoutMsOverride = ms;\n}\n\nexport function resolveCommandTimeoutSeconds(timeout: number | undefined): number {\n\tif (typeof timeout !== \"number\" || !Number.isFinite(timeout) || timeout <= 0) {\n\t\treturn DEFAULT_COMMAND_TIMEOUT_SECONDS;\n\t}\n\treturn Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,\n\t\t}),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tfullOutputError?: string;\n\tpreview?: {\n\t\tcontent: string;\n\t\tskippedLines: number;\n\t};\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (for example SSH).\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)\n\t */\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\nfunction createLocalShellOperations(\n\tshellName: PlatformShellToolName,\n\toptions?: { shellPath?: string },\n): BashOperations {\n\treturn {\n\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\tconst { shell, args } = getShellConfig(options?.shellPath, shellName);\n\t\t\ttry {\n\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t}\n\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tconst terminationController = new AbortController();\n\t\t\tconst onAbort = () => terminationController.abort();\n\t\t\tlet silenceKilled = false;\n\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\tconst silenceWatchdog =\n\t\t\t\t(timeout === undefined || timeout <= 0) && silenceMs > 0\n\t\t\t\t\t? createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs,\n\t\t\t\t\t\t\tonSilence: () => {\n\t\t\t\t\t\t\t\tsilenceKilled = true;\n\t\t\t\t\t\t\t\tterminationController.abort();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst onChunk = (data: Buffer) => {\n\t\t\t\tsilenceWatchdog?.touch();\n\t\t\t\tonData(data);\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tchild.stdout?.on(\"data\", onChunk);\n\t\t\t\tchild.stderr?.on(\"data\", onChunk);\n\t\t\t\tif (signal) {\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\ttimeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,\n\t\t\t\t\tkillGraceMs: 2_000,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (terminal.reason === \"timeout\") throw new Error(`timeout:${timeout}`);\n\t\t\t\tif (silenceKilled) throw new Error(`silence:${silenceMs / 1000}`);\n\t\t\t\treturn { exitCode: terminal.code };\n\t\t\t} finally {\n\t\t\t\tsilenceWatchdog?.disarm();\n\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create bash operations using pi's built-in local shell execution backend.\n *\n * This is useful for extensions that intercept user_bash and still want pi's\n * standard local shell behavior while wrapping or rewriting commands.\n */\nexport function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"bash\", options);\n}\n\n/** Create PowerShell operations using pi's built-in local execution backend. */\nexport function createLocalPowerShellOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"powershell\", options);\n}\n\n/** Create the platform shell backend without requiring callers or the model to choose a shell. */\nexport function createLocalPlatformShellOperations(\n\toptions: { shellPath?: string; commandPrefix?: string; operations?: BashOperations } = {},\n\tplatform: NodeJS.Platform = process.platform,\n): BashOperations {\n\tconst operations =\n\t\toptions.operations ??\n\t\tcreateLocalShellOperations(getPlatformShellToolName(platform), { shellPath: options.shellPath });\n\treturn {\n\t\tasync exec(command, cwd, execOptions) {\n\t\t\tlet resolvedCommand = command;\n\t\t\tif (platform === \"win32\") {\n\t\t\t\tconst route = routeShellContract(command, platform);\n\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\tif (route.kind === \"powershell\") resolvedCommand = route.command;\n\t\t\t}\n\t\t\tif (options.commandPrefix) resolvedCommand = `${options.commandPrefix}\\n${resolvedCommand}`;\n\t\t\tif (platform === \"win32\") resolvedCommand = prefixPowerShellCommand(resolvedCommand);\n\t\t\treturn operations.exec(resolvedCommand, cwd, execOptions);\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 = { command, cwd, env: { ...getShellEnv() } };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Platform used to choose the default backend and contract router. Defaults to process.platform. */\n\tplatform?: NodeJS.Platform;\n\t/** Custom operations for command execution. Default: local platform shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_PREVIEW_BYTES = 8 * 1024;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\ntype BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n\tcachedSkipped: number | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t\tcachedSkipped: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\treturn `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatBashCall(\n\targs: { command?: string; timeout?: number } | undefined,\n\tshellName: PlatformShellToolName,\n): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\tconst prompt = shellName === \"powershell\" ? \"PS>\" : \"$\";\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tconst renderPreview = !options.expanded ? result.details?.preview : undefined;\n\tlet output = (renderPreview ? renderPreview.content : getTextOutput(result as any, showImages)).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tconst fullOutputError = result.details?.fullOutputError;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tif (options.expanded) {\n\t\t\tconst styledOutput = output\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t\t.join(\"\\n\");\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(output, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tstate.cachedLines = preview.visualLines.map((line) => theme.fg(\"toolOutput\", line));\n\t\t\t\t\t\tstate.cachedSkipped = (result.details?.preview?.skippedLines ?? 0) + preview.skippedCount;\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\tif (state.cachedSkipped && state.cachedSkipped > 0) {\n\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${state.cachedSkipped} earlier lines,`) +\n\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t\t\t\t\treturn [\"\", truncateToWidth(hint, width, \"...\"), ...(state.cachedLines ?? [])];\n\t\t\t\t\t}\n\t\t\t\t\treturn [\"\", ...(state.cachedLines ?? [])];\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t\tstate.cachedSkipped = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath || fullOutputError) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t} else if (fullOutputError) {\n\t\t\twarnings.push(`Full output unavailable: ${fullOutputError}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nfunction createShellToolDefinition(\n\tcwd: string,\n\tbackendShell: PlatformShellToolName,\n\tcontractPlatform: NodeJS.Platform,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst toolName = \"bash\";\n\tconst ops =\n\t\toptions?.operations ??\n\t\t(backendShell === \"powershell\"\n\t\t\t? createLocalPowerShellOperations({ shellPath: options?.shellPath })\n\t\t\t: createLocalBashOperations({ shellPath: options?.shellPath }));\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);\n\tconst canFilterCommand = !hasExecutionOverrides;\n\tconst routesWindowsContract = contractPlatform === \"win32\";\n\tconst contractDescription = routesWindowsContract\n\t\t? \"Execute Pi's stable Bash-like command contract in the current working directory. On Windows, a finite deterministic router converts supported simple commands to PowerShell; unsupported Bash constructs fail closed instead of being guessed.\"\n\t\t: \"Execute a Bash command in the current working directory.\";\n\treturn {\n\t\tname: toolName,\n\t\tlabel: toolName,\n\t\tdescription: `${contractDescription} 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. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,\n\t\tpromptSnippet: routesWindowsContract\n\t\t\t? \"Execute simple Bash-like commands; Pi routes supported forms deterministically to PowerShell on Windows\"\n\t\t\t: \"Execute Bash commands (ls, grep, find, etc.)\",\n\t\tpromptGuidelines: routesWindowsContract\n\t\t\t? [\n\t\t\t\t\t\"Use the bash tool's portable simple-command contract on Windows; do not write PowerShell or ask the user to choose a shell.\",\n\t\t\t\t\t\"Use one simple command per call. The deterministic router rejects pipelines, redirection, expansion, shell chaining, nested shells, and unsupported Bash forms; use dedicated read/edit/search tools or separate calls instead.\",\n\t\t\t\t\t\"Supported Bash-like file commands are converted with literal-path PowerShell operations; verify targets before recursive rm, cp, or mv calls.\",\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t],\n\t\tparameters: bashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.previewSnapshot(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES, {\n\t\t\t\t\tpersistIfFullTruncated: true,\n\t\t\t\t});\n\t\t\t\tconst preview = {\n\t\t\t\t\tcontent: snapshot.content.replace(/\\r/g, \"\"),\n\t\t\t\t\tskippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),\n\t\t\t\t};\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: preview.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\tpreview,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\treturn output.snapshot({ persistIfTruncated: true });\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content.replace(/\\r/g, \"\") || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tconst preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);\n\t\t\t\tconst fullOutputNotice = snapshot.fullOutputPath\n\t\t\t\t\t? `Full output: ${snapshot.fullOutputPath}`\n\t\t\t\t\t: snapshot.fullOutputError\n\t\t\t\t\t\t? `Full output unavailable: ${snapshot.fullOutputError}`\n\t\t\t\t\t\t: \"Full output unavailable\";\n\t\t\t\tif (truncation.truncated || preview.skippedLines > 0) {\n\t\t\t\t\tdetails = { preview };\n\t\t\t\t}\n\t\t\t\tif (snapshot.fullOutputPath || snapshot.fullOutputError) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). ${fullOutputNotice}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. ${fullOutputNotice}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). ${fullOutputNotice}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\t\t\tconst effectiveTimeoutSeconds =\n\t\t\t\ttypeof timeout === \"number\" && Number.isFinite(timeout) && timeout > 0\n\t\t\t\t\t? resolveCommandTimeoutSeconds(timeout)\n\t\t\t\t\t: (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;\n\n\t\t\ttry {\n\t\t\t\tif (canFilterCommand) {\n\t\t\t\t\tconst classification = classifyGitCommand(command, getShellEnv());\n\t\t\t\t\tif (classification.eligible && classification.subcommand) {\n\t\t\t\t\t\tconst res = await executeFilteredGit(\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\tclassification.subcommand,\n\t\t\t\t\t\t\tclassification.globalOptions || [],\n\t\t\t\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t\t\t\t{ signal, timeout: effectiveTimeoutSeconds },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (res.exitCode !== -100) {\n\t\t\t\t\t\t\toutput.append(res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\"));\n\t\t\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\t\t\tif (res.exitCode !== 0) {\n\t\t\t\t\t\t\t\tconst { text: rawOutputText } = formatOutput(snapshot);\n\t\t\t\t\t\t\t\tthrow new Error(appendStatus(rawOutputText, `Command exited with code ${res.exitCode}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst details = snapshot.truncation.truncated\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttruncation: snapshot.truncation,\n\t\t\t\t\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: snapshot.fullOutputPath || snapshot.fullOutputError\n\t\t\t\t\t\t\t\t\t? { fullOutputPath: snapshot.fullOutputPath, fullOutputError: snapshot.fullOutputError }\n\t\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\treturn { content: [{ type: \"text\", text: res.output }], details };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet backendCommand = command;\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tconst route = routeShellContract(command, contractPlatform);\n\t\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t\tbackendCommand = route.command;\n\t\t\t\t}\n\t\t\t\tconst commandWithPrefix = commandPrefix ? `${commandPrefix}\\n${backendCommand}` : backendCommand;\n\t\t\t\tconst resolvedCommand =\n\t\t\t\t\tbackendShell === \"powershell\" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;\n\t\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\t// Shell commands cannot statically declare which files they mutate, so the\n\t\t\t\t\t// actual execution takes the coarse exclusive barrier: it waits for\n\t\t\t\t\t// in-flight edit/write mutations to drain and blocks new ones meanwhile.\n\t\t\t\t\tconst result = await withExclusiveMutationBarrier(() =>\n\t\t\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\ttimeout: effectiveTimeoutSeconds,\n\t\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, \"Command aborted\"));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(appendStatus(text, `Command timed out after ${timeoutSecs} seconds`));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\t\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\t\t\t\tconst recovery =\n\t\t\t\t\t\t\tbackendShell === \"bash\"\n\t\t\t\t\t\t\t\t? \"re-run it with an explicit timeout, or run it in the background with '&'.\"\n\t\t\t\t\t\t\t\t: \"re-run it with an explicit timeout.\";\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(\n\t\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`,\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\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, `Command exited with code ${exitCode}`));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t\tawait output.closeTempFile();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatBashCall(args, toolName));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult as any,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst platform = options?.platform ?? process.platform;\n\treturn createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\treturn wrapToolDefinition(createBashToolDefinition(cwd, options));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAiC,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAItG,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAK5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACN,KAAK,iBAAiB,EAKtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAEN,KAAK,gBAAgB,EAKrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAO5E,QAAA,MAAM,UAAU;;;;;EAQd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CACnH;AAQD,MAAM,WAAW,eAAe;IAC/B,sFAAsF;IACtF,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,+EAA+E;IAC/E,GAAG,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAC;IAC/B,qFAAqF;IACrF,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACtC;AAsGD,wBAAsB,UAAU,CAAC,OAAO,EAAE;IACzC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GAAG,SAAS,CAAC,CAiFlE;AA2GD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA6QhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@caupulican/pi-agent-core\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult } from \"@caupulican/pi-agent-core/node\";\nimport { Text } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport type { ArtifactStore } from \"../context/context-artifacts.ts\";\nimport {\n\ttype BroadQueryTracker,\n\tbroadQueryInvalidationNote,\n\tformatArtifactNotice,\n\tnormalizeBroadQueryKey,\n\tpackToolOutput,\n} from \"../context/tool-output-packer.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport {\n\tdefaultFffSearchBackend,\n\ttype FffSearchBackend,\n\ttype FffSearchResult,\n\thasGitignoreInTree,\n\trelativePathInside,\n\tsafeGetFinder,\n} from \"./fff-search-backend.ts\";\nimport { pathExists, resolveToCwd } from \"./path-utils.ts\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { defaultSearchRouter, type SearchRouter } from \"./search-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nfunction toPosixPath(value: string): string {\n\treturn value.split(path.sep).join(\"/\");\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription:\n\t\t\t\"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'. Use '.' to match all files.\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive matching (default: false)\" })),\n});\n\nexport type FindToolInput = Static<typeof findSchema>;\n\nconst DEFAULT_LIMIT = 1000;\nconst FIND_PROCESS_TIMEOUT_MS = 5 * 60_000;\nconst FIND_PROCESS_KILL_GRACE_MS = 1_000;\n\nexport interface FindToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n\t/** Set only when output was packed to an artifact; see tool-output-packer.ts. */\n\tartifactId?: string;\n\t/** Set when this exact query has repeatedly produced broad/truncated results. */\n\tinvalidationCandidate?: boolean;\n}\n\n/**\n * Pluggable operations for the find tool.\n * Override these to delegate file search to remote systems (for example SSH).\n */\nexport interface FindOperations {\n\t/** Check if path exists */\n\texists: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Find files matching glob pattern. Returns relative or absolute paths. */\n\tglob: (pattern: string, cwd: string, options: { ignore: string[]; limit: number }) => Promise<string[]> | string[];\n}\n\nconst defaultFindOperations: FindOperations = {\n\texists: pathExists,\n\t// This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.\n\tglob: () => [],\n};\n\nexport interface FindToolOptions {\n\t/** Custom operations for find. Default: local filesystem plus routed FFF/fd search */\n\toperations?: FindOperations;\n\t/** FFF backend for resident indexed search. Set false to force fd fallback. */\n\tfff?: FffSearchBackend | false;\n\t/** Pure router that selects FFF or fd from request filters and environment facts. */\n\tsearchRouter?: SearchRouter;\n\t/**\n\t * Opt-in artifact store for first-capture-then-bound output packing (Phase 3). When\n\t * omitted (the default), behavior is byte-for-byte unchanged from before this option\n\t * existed: output is truncated the same way, just never artifact-backed.\n\t */\n\tartifactStore?: ArtifactStore;\n\t/** Opt-in tracker for repeated-broad-query \"do not repeat\" signals. Also default-off. */\n\tbroadQueryTracker?: BroadQueryTracker;\n}\n\nfunction formatFindCall(\n\targs: { pattern: string; path?: string; limit?: number } | undefined,\n\ttheme: Theme,\n\tcwd: string,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\", cwd) : null;\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"find\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", pattern || \"\")) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t}\n\treturn text;\n}\n\nfunction formatFindResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: FindToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 20;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst resultLimit = result.details?.resultLimitReached;\n\tconst truncation = result.details?.truncation;\n\tif (resultLimit || truncation?.truncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (resultLimit) warnings.push(`${resultLimit} results limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nfunction hasGlobSyntax(pattern: string): boolean {\n\treturn pattern === \".\" || /[*?[{]/.test(pattern);\n}\n\nfunction fffQueryParts(parts: string[]): string {\n\treturn parts.filter(Boolean).join(\" \");\n}\n\nfunction toSearchRelative(repoRelativePath: string, searchPathRelativeToCwd: string): string | undefined {\n\tif (!searchPathRelativeToCwd) return repoRelativePath;\n\tconst prefix = `${searchPathRelativeToCwd}/`;\n\tif (!repoRelativePath.startsWith(prefix)) return undefined;\n\treturn repoRelativePath.slice(prefix.length);\n}\n\nfunction fffGlobPattern(pattern: string, searchPathRelativeToCwd: string): string {\n\tconst effectivePattern = pattern === \".\" ? \"**/*\" : pattern;\n\tif (!searchPathRelativeToCwd) {\n\t\tif (effectivePattern.includes(\"/\") || effectivePattern.startsWith(\"**/\")) return effectivePattern;\n\t\treturn `**/${effectivePattern}`;\n\t}\n\tif (effectivePattern === \"**\" || effectivePattern === \"**/*\") return `${searchPathRelativeToCwd}/**/*`;\n\tif (effectivePattern.includes(\"/\")) return `${searchPathRelativeToCwd}/${effectivePattern}`;\n\treturn `${searchPathRelativeToCwd}/**/${effectivePattern}`;\n}\n\ninterface FindPackingOptions {\n\ttoolCallId: string;\n\tartifactStore?: ArtifactStore;\n\tbroadQueryTracker?: BroadQueryTracker;\n\tpattern: string;\n\trawPath?: string;\n}\n\nfunction fffSearchOutput(\n\tresult: FffSearchResult,\n\tsearchPathRelativeToCwd: string,\n\teffectiveLimit: number,\n\tpacking: FindPackingOptions,\n) {\n\tconst relativized = result.items\n\t\t.map((item) => toSearchRelative(item.relativePath, searchPathRelativeToCwd))\n\t\t.filter((item): item is string => Boolean(item));\n\treturn formatFindResults(relativized, effectiveLimit, packing);\n}\n\nexport async function tryFffFind(options: {\n\tbackend: FffSearchBackend;\n\trouter: SearchRouter;\n\tcwd: string;\n\tsearchPath: string;\n\tpattern: string;\n\tignoreCase?: boolean;\n\teffectiveLimit: number;\n\ttoolCallId: string;\n\tartifactStore?: ArtifactStore;\n\tbroadQueryTracker?: BroadQueryTracker;\n\trawPath?: string;\n}): Promise<{ text: string; details: FindToolDetails } | undefined> {\n\tif (!(await pathExists(options.searchPath))) return undefined;\n\n\t// Kick off the FFF finder -- and, on a machine where fff-node isn't provisioned\n\t// yet, its lazy managed install -- unconditionally and as early as possible.\n\t// Routing below can still send THIS call to the fd fallback for reasons that\n\t// have nothing to do with tool availability (most commonly: the default result\n\t// limit is well above the FFF top-N threshold, see search-router.ts). That\n\t// outcome must not gate the install itself, or a machine that only ever issues\n\t// default-limit searches would never provision fff-node at all. getFinder() is\n\t// cached per basePath, so this costs nothing beyond the first call for a given\n\t// cwd -- the `await` further down, when this call does want FFF, reuses this\n\t// same in-flight/resolved promise instead of triggering the work twice.\n\t// safeGetFinder() guarantees this can never reject (a non-conforming custom\n\t// backend that throws synchronously still degrades to \"unavailable\"), so it\n\t// can neither produce an unhandled rejection nor fail this tool call outright.\n\tconst finderPromise = safeGetFinder(options.backend, options.cwd);\n\n\tconst searchPathRelativeToCwd = relativePathInside(options.cwd, options.searchPath);\n\tconst glob = hasGlobSyntax(options.pattern);\n\tconst baseRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: true,\n\t\tpathResolvable: searchPathRelativeToCwd !== undefined,\n\t\tgitignoreInTree: false,\n\t});\n\tif (baseRoute.backend !== \"fff\") return undefined;\n\tif (searchPathRelativeToCwd === undefined) return undefined;\n\n\tconst gitignoreInTree = await hasGitignoreInTree(options.searchPath);\n\tconst semanticRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: true,\n\t\tpathResolvable: true,\n\t\tgitignoreInTree,\n\t});\n\tif (semanticRoute.backend !== \"fff\") return undefined;\n\n\tconst finder = await finderPromise;\n\tconst finderRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: Boolean(finder),\n\t\tpathResolvable: true,\n\t\tgitignoreInTree: false,\n\t});\n\tif (!finder || finderRoute.backend !== \"fff\") return undefined;\n\n\tconst packing: FindPackingOptions = {\n\t\ttoolCallId: options.toolCallId,\n\t\tartifactStore: options.artifactStore,\n\t\tbroadQueryTracker: options.broadQueryTracker,\n\t\tpattern: options.pattern,\n\t\trawPath: options.rawPath,\n\t};\n\n\tconst probeLimit = options.effectiveLimit + 1;\n\tif (glob) {\n\t\tconst result = finder.glob(fffGlobPattern(options.pattern, searchPathRelativeToCwd), {\n\t\t\tpageSize: probeLimit,\n\t\t});\n\t\treturn result.ok\n\t\t\t? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)\n\t\t\t: undefined;\n\t}\n\n\tconst pathConstraint = searchPathRelativeToCwd ? `${searchPathRelativeToCwd}/` : \"\";\n\tconst result = finder.fileSearch(fffQueryParts([pathConstraint, options.pattern]), {\n\t\tpageSize: probeLimit,\n\t});\n\treturn result.ok\n\t\t? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)\n\t\t: undefined;\n}\n\nfunction formatFindResults(\n\trelativized: string[],\n\teffectiveLimit: number,\n\tpacking: FindPackingOptions,\n): { text: string; details: FindToolDetails } {\n\tif (relativized.length === 0) {\n\t\treturn { text: \"No files found matching pattern\", details: {} };\n\t}\n\n\tconst resultLimitReached = relativized.length > effectiveLimit;\n\tconst displayedResults = resultLimitReached ? relativized.slice(0, effectiveLimit) : relativized;\n\tconst dirGroups = new Map<string, string[]>();\n\tconst extCounts = new Map<string, number>();\n\n\tfor (const p of displayedResults) {\n\t\tconst dir = path.dirname(p);\n\t\tconst base = path.basename(p);\n\t\tconst dirKey = dir === \".\" ? \"./\" : `${dir}/`;\n\t\tif (!dirGroups.has(dirKey)) {\n\t\t\tdirGroups.set(dirKey, []);\n\t\t}\n\t\tdirGroups.get(dirKey)!.push(base);\n\n\t\tconst ext = path.extname(p).toLowerCase() || \"(no extension)\";\n\t\textCounts.set(ext, (extCounts.get(ext) || 0) + 1);\n\t}\n\n\tconst sortedDirs = Array.from(dirGroups.keys()).sort((a, b) => a.localeCompare(b));\n\tconst formattedLines: string[] = [];\n\tfor (const dir of sortedDirs) {\n\t\tformattedLines.push(dir);\n\t\tconst files = dirGroups.get(dir)!;\n\t\tfiles.sort((a, b) => a.localeCompare(b));\n\t\tfor (const file of files) {\n\t\t\tformattedLines.push(` ${file}`);\n\t\t}\n\t}\n\n\tconst extSummaryParts = Array.from(extCounts.entries())\n\t\t.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n\t\t.map(([ext, count]) => `${ext}: ${count}`);\n\tconst extSummary = `Extensions: ${extSummaryParts.join(\", \")}`;\n\n\tconst rawOutput = formattedLines.join(\"\\n\");\n\t// Measure -> pack (artifact-backed if oversized and a store was provided) -> notices.\n\tconst packed = packToolOutput(\n\t\t{\n\t\t\ttoolName: \"find\",\n\t\t\tpath: packing.rawPath,\n\t\t\trawContent: rawOutput,\n\t\t\t// No line limit here because the result limit already caps rows; only the byte\n\t\t\t// cap should apply, matching the pre-Slice-B truncateHead call exactly.\n\t\t\ttruncation: { maxLines: Number.MAX_SAFE_INTEGER },\n\t\t},\n\t\tpacking.artifactStore,\n\t\tpacking.toolCallId,\n\t);\n\tlet resultOutput = packed.content;\n\tconst details: FindToolDetails = {};\n\tconst notices: string[] = [];\n\tif (packed.artifactId) {\n\t\tnotices.push(formatArtifactNotice(packed.artifactId));\n\t\tdetails.artifactId = packed.artifactId;\n\t}\n\tif (resultLimitReached) {\n\t\tnotices.push(\n\t\t\t`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or narrow path/pattern`,\n\t\t);\n\t\tdetails.resultLimitReached = effectiveLimit;\n\t}\n\tif (packed.truncation.truncated) {\n\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t// Drop the duplicated bounded-preview text: it's already in the message's own\n\t\t// content, and re-including it here can push `details` past\n\t\t// MAX_RETAINED_TOOL_RESULT_DETAILS_BYTES (message-retention.ts), which replaces\n\t\t// the *entire* details object with a stub -- silently losing artifactId and every\n\t\t// other field alongside it. This is load-bearing beyond just the retention budget:\n\t\t// agent-session.ts's _releaseGcPackedArtifactReferences() reads artifactId back off\n\t\t// this same canonical message at eviction time (potentially many turns later), so\n\t\t// keeping `details` small here is what keeps that release path working at all. If\n\t\t// this field ever grows a large addition again, add a regression proving artifactId\n\t\t// survives compactToolResultDetailsForRetention (see\n\t\t// test/suite/agent-session-artifact-lifecycle.test.ts), not just a details-size check.\n\t\tdetails.truncation = { ...packed.truncation, content: \"\" };\n\t}\n\tif (resultLimitReached || packed.truncation.truncated) {\n\t\tconst note = broadQueryInvalidationNote(\n\t\t\tpacking.broadQueryTracker,\n\t\t\tnormalizeBroadQueryKey({ toolName: \"find\", pattern: packing.pattern, path: packing.rawPath }),\n\t\t\t`find \"${packing.pattern}\" in ${packing.rawPath ?? \".\"}`,\n\t\t);\n\t\tif (note) {\n\t\t\tnotices.push(note);\n\t\t\tdetails.invalidationCandidate = true;\n\t\t}\n\t}\n\tif (displayedResults.length > 0) {\n\t\tresultOutput += `\\n\\n[Summary - ${extSummary}]`;\n\t}\n\tif (notices.length > 0) {\n\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t}\n\treturn { text: resultOutput, details };\n}\n\nexport function createFindToolDefinition(\n\tcwd: string,\n\toptions?: FindToolOptions,\n): ToolDefinition<typeof findSchema, FindToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\tconst fffBackend = options?.fff === false ? undefined : (options?.fff ?? defaultFffSearchBackend);\n\tconst searchRouter = options?.searchRouter ?? defaultSearchRouter;\n\tconst artifactStore = options?.artifactStore;\n\tconst broadQueryTracker = options?.broadQueryTracker;\n\treturn {\n\t\tname: \"find\",\n\t\tlabel: \"find\",\n\t\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\t\tpromptSnippet: \"Find files by glob pattern (respects .gitignore)\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use the narrowest explicit root and glob that can answer the question; do not recursively scan a repository, home directory, or filesystem without a concrete purpose.\",\n\t\t],\n\t\tparameters: findSchema,\n\t\ttoolGroup: \"explore\",\n\t\tasync execute(\n\t\t\ttoolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tlimit,\n\t\t\t\tignoreCase,\n\t\t\t}: { pattern: string; path?: string; limit?: number; ignoreCase?: boolean },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet settled = false;\n\t\t\t\tlet stopChild: (() => void) | undefined;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tstopChild = undefined;\n\t\t\t\t\tfn();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tstopChild?.();\n\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\t\t\t\t\t\tconst probeLimit = effectiveLimit + 1;\n\t\t\t\t\t\tconst ops = customOps ?? defaultFindOperations;\n\n\t\t\t\t\t\tlet effectivePattern = pattern;\n\t\t\t\t\t\tif (pattern === \".\") {\n\t\t\t\t\t\t\teffectivePattern = \"**/*\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!customOps && fffBackend) {\n\t\t\t\t\t\t\tconst fffResult = await tryFffFind({\n\t\t\t\t\t\t\t\tbackend: fffBackend,\n\t\t\t\t\t\t\t\trouter: searchRouter,\n\t\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\t\tsearchPath,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\tignoreCase,\n\t\t\t\t\t\t\t\teffectiveLimit,\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (fffResult) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: fffResult.text }],\n\t\t\t\t\t\t\t\t\t\tdetails: Object.keys(fffResult.details).length > 0 ? fffResult.details : undefined,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If custom operations provide glob(), use that instead of fd.\n\t\t\t\t\t\tif (customOps?.glob) {\n\t\t\t\t\t\t\tif (!(await ops.exists(searchPath))) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst results = await ops.glob(effectivePattern, searchPath, {\n\t\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t\t\tlimit: probeLimit,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Relativize paths against the search root for stable output.\n\t\t\t\t\t\t\tconst relativized = results.map((p) => {\n\t\t\t\t\t\t\t\tif (p.startsWith(searchPath)) return toPosixPath(p.slice(searchPath.length + 1));\n\t\t\t\t\t\t\t\treturn toPosixPath(path.relative(searchPath, p));\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit, {\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Default implementation uses fd.\n\t\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"fd is not available and could not be downloaded\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore\n\t\t\t\t\t\t// semantics whether or not the search path is inside a git repository, without\n\t\t\t\t\t\t// leaking sibling-directory rules the way --ignore-file (a global source) would.\n\t\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\t\"--glob\",\n\t\t\t\t\t\t\t\"--color=never\",\n\t\t\t\t\t\t\t\"--hidden\",\n\t\t\t\t\t\t\t\"--no-require-git\",\n\t\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\t\tString(probeLimit),\n\t\t\t\t\t\t];\n\t\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// fd --glob matches against the basename unless --full-path is set; in --full-path\n\t\t\t\t\t\t// mode it matches against the absolute candidate path, so a path-containing\n\t\t\t\t\t\t// pattern like 'src/**/*.spec.ts' needs a leading '**/' to match anything.\n\t\t\t\t\t\tlet finalPattern = effectivePattern;\n\t\t\t\t\t\tif (effectivePattern.includes(\"/\")) {\n\t\t\t\t\t\t\targs.push(\"--full-path\");\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"/\") &&\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"**/\") &&\n\t\t\t\t\t\t\t\teffectivePattern !== \"**\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tfinalPattern = `**/${effectivePattern}`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\targs.push(\"--\", finalPattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(fdPath, args, {\n\t\t\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\t\tconst terminationController = new AbortController();\n\n\t\t\t\t\t\tstopChild = () => terminationController.abort();\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tlines.push(line);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\t\t\ttimeoutMs: FIND_PROCESS_TIMEOUT_MS,\n\t\t\t\t\t\t\tkillGraceMs: FIND_PROCESS_KILL_GRACE_MS,\n\t\t\t\t\t\t}).finally(cleanup);\n\t\t\t\t\t\tconst code = terminal.code;\n\t\t\t\t\t\tif (terminal.reason === \"timeout\") {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`fd timed out after ${FIND_PROCESS_TIMEOUT_MS}ms`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst output = lines.join(\"\\n\");\n\t\t\t\t\t\tif (code !== 0) {\n\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `fd exited with code ${code}`;\n\t\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst relativized: string[] = [];\n\t\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\tif (!line) continue;\n\t\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) relativePath += \"/\";\n\t\t\t\t\t\t\trelativized.push(toPosixPath(relativePath));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit, {\n\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.details : undefined,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst error = e instanceof Error ? e : new Error(String(e));\n\t\t\t\t\t\tsettle(() => reject(error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatFindCall(args, theme, context.cwd));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatFindResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {\n\treturn wrapToolDefinition(createFindToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAiC,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAKtG,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAK5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACN,KAAK,iBAAiB,EAKtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAEN,KAAK,gBAAgB,EAKrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAO5E,QAAA,MAAM,UAAU;;;;;EAQd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CACnH;AAQD,MAAM,WAAW,eAAe;IAC/B,sFAAsF;IACtF,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,+EAA+E;IAC/E,GAAG,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAC;IAC/B,qFAAqF;IACrF,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACtC;AAsGD,wBAAsB,UAAU,CAAC,OAAO,EAAE;IACzC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GAAG,SAAS,CAAC,CAiFlE;AA2GD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAqRhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@caupulican/pi-agent-core\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult } from \"@caupulican/pi-agent-core/node\";\nimport { Text } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { minimatch } from \"minimatch\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport type { ArtifactStore } from \"../context/context-artifacts.ts\";\nimport {\n\ttype BroadQueryTracker,\n\tbroadQueryInvalidationNote,\n\tformatArtifactNotice,\n\tnormalizeBroadQueryKey,\n\tpackToolOutput,\n} from \"../context/tool-output-packer.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport {\n\tdefaultFffSearchBackend,\n\ttype FffSearchBackend,\n\ttype FffSearchResult,\n\thasGitignoreInTree,\n\trelativePathInside,\n\tsafeGetFinder,\n} from \"./fff-search-backend.ts\";\nimport { pathExists, resolveToCwd } from \"./path-utils.ts\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { defaultSearchRouter, type SearchRouter } from \"./search-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nfunction toPosixPath(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"/\");\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription:\n\t\t\t\"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'. Use '.' to match all files.\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive matching (default: false)\" })),\n});\n\nexport type FindToolInput = Static<typeof findSchema>;\n\nconst DEFAULT_LIMIT = 1000;\nconst FIND_PROCESS_TIMEOUT_MS = 5 * 60_000;\nconst FIND_PROCESS_KILL_GRACE_MS = 1_000;\n\nexport interface FindToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n\t/** Set only when output was packed to an artifact; see tool-output-packer.ts. */\n\tartifactId?: string;\n\t/** Set when this exact query has repeatedly produced broad/truncated results. */\n\tinvalidationCandidate?: boolean;\n}\n\n/**\n * Pluggable operations for the find tool.\n * Override these to delegate file search to remote systems (for example SSH).\n */\nexport interface FindOperations {\n\t/** Check if path exists */\n\texists: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Find files matching glob pattern. Returns relative or absolute paths. */\n\tglob: (pattern: string, cwd: string, options: { ignore: string[]; limit: number }) => Promise<string[]> | string[];\n}\n\nconst defaultFindOperations: FindOperations = {\n\texists: pathExists,\n\t// This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.\n\tglob: () => [],\n};\n\nexport interface FindToolOptions {\n\t/** Custom operations for find. Default: local filesystem plus routed FFF/fd search */\n\toperations?: FindOperations;\n\t/** FFF backend for resident indexed search. Set false to force fd fallback. */\n\tfff?: FffSearchBackend | false;\n\t/** Pure router that selects FFF or fd from request filters and environment facts. */\n\tsearchRouter?: SearchRouter;\n\t/**\n\t * Opt-in artifact store for first-capture-then-bound output packing (Phase 3). When\n\t * omitted (the default), behavior is byte-for-byte unchanged from before this option\n\t * existed: output is truncated the same way, just never artifact-backed.\n\t */\n\tartifactStore?: ArtifactStore;\n\t/** Opt-in tracker for repeated-broad-query \"do not repeat\" signals. Also default-off. */\n\tbroadQueryTracker?: BroadQueryTracker;\n}\n\nfunction formatFindCall(\n\targs: { pattern: string; path?: string; limit?: number } | undefined,\n\ttheme: Theme,\n\tcwd: string,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\", cwd) : null;\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"find\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", pattern || \"\")) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t}\n\treturn text;\n}\n\nfunction formatFindResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: FindToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 20;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst resultLimit = result.details?.resultLimitReached;\n\tconst truncation = result.details?.truncation;\n\tif (resultLimit || truncation?.truncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (resultLimit) warnings.push(`${resultLimit} results limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nfunction hasGlobSyntax(pattern: string): boolean {\n\treturn pattern === \".\" || /[*?[{]/.test(pattern);\n}\n\nfunction fffQueryParts(parts: string[]): string {\n\treturn parts.filter(Boolean).join(\" \");\n}\n\nfunction toSearchRelative(repoRelativePath: string, searchPathRelativeToCwd: string): string | undefined {\n\tif (!searchPathRelativeToCwd) return repoRelativePath;\n\tconst prefix = `${searchPathRelativeToCwd}/`;\n\tif (!repoRelativePath.startsWith(prefix)) return undefined;\n\treturn repoRelativePath.slice(prefix.length);\n}\n\nfunction fffGlobPattern(pattern: string, searchPathRelativeToCwd: string): string {\n\tconst effectivePattern = pattern === \".\" ? \"**/*\" : pattern;\n\tif (!searchPathRelativeToCwd) {\n\t\tif (effectivePattern.includes(\"/\") || effectivePattern.startsWith(\"**/\")) return effectivePattern;\n\t\treturn `**/${effectivePattern}`;\n\t}\n\tif (effectivePattern === \"**\" || effectivePattern === \"**/*\") return `${searchPathRelativeToCwd}/**/*`;\n\tif (effectivePattern.includes(\"/\")) return `${searchPathRelativeToCwd}/${effectivePattern}`;\n\treturn `${searchPathRelativeToCwd}/**/${effectivePattern}`;\n}\n\ninterface FindPackingOptions {\n\ttoolCallId: string;\n\tartifactStore?: ArtifactStore;\n\tbroadQueryTracker?: BroadQueryTracker;\n\tpattern: string;\n\trawPath?: string;\n}\n\nfunction fffSearchOutput(\n\tresult: FffSearchResult,\n\tsearchPathRelativeToCwd: string,\n\teffectiveLimit: number,\n\tpacking: FindPackingOptions,\n) {\n\tconst relativized = result.items\n\t\t.map((item) => toSearchRelative(toPosixPath(item.relativePath), searchPathRelativeToCwd))\n\t\t.filter((item): item is string => Boolean(item));\n\treturn formatFindResults(relativized, effectiveLimit, packing);\n}\n\nexport async function tryFffFind(options: {\n\tbackend: FffSearchBackend;\n\trouter: SearchRouter;\n\tcwd: string;\n\tsearchPath: string;\n\tpattern: string;\n\tignoreCase?: boolean;\n\teffectiveLimit: number;\n\ttoolCallId: string;\n\tartifactStore?: ArtifactStore;\n\tbroadQueryTracker?: BroadQueryTracker;\n\trawPath?: string;\n}): Promise<{ text: string; details: FindToolDetails } | undefined> {\n\tif (!(await pathExists(options.searchPath))) return undefined;\n\n\t// Kick off the FFF finder -- and, on a machine where fff-node isn't provisioned\n\t// yet, its lazy managed install -- unconditionally and as early as possible.\n\t// Routing below can still send THIS call to the fd fallback for reasons that\n\t// have nothing to do with tool availability (most commonly: the default result\n\t// limit is well above the FFF top-N threshold, see search-router.ts). That\n\t// outcome must not gate the install itself, or a machine that only ever issues\n\t// default-limit searches would never provision fff-node at all. getFinder() is\n\t// cached per basePath, so this costs nothing beyond the first call for a given\n\t// cwd -- the `await` further down, when this call does want FFF, reuses this\n\t// same in-flight/resolved promise instead of triggering the work twice.\n\t// safeGetFinder() guarantees this can never reject (a non-conforming custom\n\t// backend that throws synchronously still degrades to \"unavailable\"), so it\n\t// can neither produce an unhandled rejection nor fail this tool call outright.\n\tconst finderPromise = safeGetFinder(options.backend, options.cwd);\n\n\tconst searchPathRelativeToCwd = relativePathInside(options.cwd, options.searchPath);\n\tconst glob = hasGlobSyntax(options.pattern);\n\tconst baseRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: true,\n\t\tpathResolvable: searchPathRelativeToCwd !== undefined,\n\t\tgitignoreInTree: false,\n\t});\n\tif (baseRoute.backend !== \"fff\") return undefined;\n\tif (searchPathRelativeToCwd === undefined) return undefined;\n\n\tconst gitignoreInTree = await hasGitignoreInTree(options.searchPath);\n\tconst semanticRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: true,\n\t\tpathResolvable: true,\n\t\tgitignoreInTree,\n\t});\n\tif (semanticRoute.backend !== \"fff\") return undefined;\n\n\tconst finder = await finderPromise;\n\tconst finderRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: Boolean(finder),\n\t\tpathResolvable: true,\n\t\tgitignoreInTree: false,\n\t});\n\tif (!finder || finderRoute.backend !== \"fff\") return undefined;\n\n\tconst packing: FindPackingOptions = {\n\t\ttoolCallId: options.toolCallId,\n\t\tartifactStore: options.artifactStore,\n\t\tbroadQueryTracker: options.broadQueryTracker,\n\t\tpattern: options.pattern,\n\t\trawPath: options.rawPath,\n\t};\n\n\tconst probeLimit = options.effectiveLimit + 1;\n\tif (glob) {\n\t\tconst result = finder.glob(fffGlobPattern(options.pattern, searchPathRelativeToCwd), {\n\t\t\tpageSize: probeLimit,\n\t\t});\n\t\treturn result.ok\n\t\t\t? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)\n\t\t\t: undefined;\n\t}\n\n\tconst pathConstraint = searchPathRelativeToCwd ? `${searchPathRelativeToCwd}/` : \"\";\n\tconst result = finder.fileSearch(fffQueryParts([pathConstraint, options.pattern]), {\n\t\tpageSize: probeLimit,\n\t});\n\treturn result.ok\n\t\t? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)\n\t\t: undefined;\n}\n\nfunction formatFindResults(\n\trelativized: string[],\n\teffectiveLimit: number,\n\tpacking: FindPackingOptions,\n): { text: string; details: FindToolDetails } {\n\tif (relativized.length === 0) {\n\t\treturn { text: \"No files found matching pattern\", details: {} };\n\t}\n\n\tconst resultLimitReached = relativized.length > effectiveLimit;\n\tconst displayedResults = resultLimitReached ? relativized.slice(0, effectiveLimit) : relativized;\n\tconst dirGroups = new Map<string, string[]>();\n\tconst extCounts = new Map<string, number>();\n\n\tfor (const p of displayedResults) {\n\t\tconst dir = path.posix.dirname(p);\n\t\tconst base = path.posix.basename(p);\n\t\tconst dirKey = dir === \".\" ? \"./\" : `${dir}/`;\n\t\tif (!dirGroups.has(dirKey)) {\n\t\t\tdirGroups.set(dirKey, []);\n\t\t}\n\t\tdirGroups.get(dirKey)!.push(base);\n\n\t\tconst ext = path.posix.extname(p).toLowerCase() || \"(no extension)\";\n\t\textCounts.set(ext, (extCounts.get(ext) || 0) + 1);\n\t}\n\n\tconst sortedDirs = Array.from(dirGroups.keys()).sort((a, b) => a.localeCompare(b));\n\tconst formattedLines: string[] = [];\n\tfor (const dir of sortedDirs) {\n\t\tformattedLines.push(dir);\n\t\tconst files = dirGroups.get(dir)!;\n\t\tfiles.sort((a, b) => a.localeCompare(b));\n\t\tfor (const file of files) {\n\t\t\tformattedLines.push(` ${file}`);\n\t\t}\n\t}\n\n\tconst extSummaryParts = Array.from(extCounts.entries())\n\t\t.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n\t\t.map(([ext, count]) => `${ext}: ${count}`);\n\tconst extSummary = `Extensions: ${extSummaryParts.join(\", \")}`;\n\n\tconst rawOutput = formattedLines.join(\"\\n\");\n\t// Measure -> pack (artifact-backed if oversized and a store was provided) -> notices.\n\tconst packed = packToolOutput(\n\t\t{\n\t\t\ttoolName: \"find\",\n\t\t\tpath: packing.rawPath,\n\t\t\trawContent: rawOutput,\n\t\t\t// No line limit here because the result limit already caps rows; only the byte\n\t\t\t// cap should apply, matching the pre-Slice-B truncateHead call exactly.\n\t\t\ttruncation: { maxLines: Number.MAX_SAFE_INTEGER },\n\t\t},\n\t\tpacking.artifactStore,\n\t\tpacking.toolCallId,\n\t);\n\tlet resultOutput = packed.content;\n\tconst details: FindToolDetails = {};\n\tconst notices: string[] = [];\n\tif (packed.artifactId) {\n\t\tnotices.push(formatArtifactNotice(packed.artifactId));\n\t\tdetails.artifactId = packed.artifactId;\n\t}\n\tif (resultLimitReached) {\n\t\tnotices.push(\n\t\t\t`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or narrow path/pattern`,\n\t\t);\n\t\tdetails.resultLimitReached = effectiveLimit;\n\t}\n\tif (packed.truncation.truncated) {\n\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t// Drop the duplicated bounded-preview text: it's already in the message's own\n\t\t// content, and re-including it here can push `details` past\n\t\t// MAX_RETAINED_TOOL_RESULT_DETAILS_BYTES (message-retention.ts), which replaces\n\t\t// the *entire* details object with a stub -- silently losing artifactId and every\n\t\t// other field alongside it. This is load-bearing beyond just the retention budget:\n\t\t// agent-session.ts's _releaseGcPackedArtifactReferences() reads artifactId back off\n\t\t// this same canonical message at eviction time (potentially many turns later), so\n\t\t// keeping `details` small here is what keeps that release path working at all. If\n\t\t// this field ever grows a large addition again, add a regression proving artifactId\n\t\t// survives compactToolResultDetailsForRetention (see\n\t\t// test/suite/agent-session-artifact-lifecycle.test.ts), not just a details-size check.\n\t\tdetails.truncation = { ...packed.truncation, content: \"\" };\n\t}\n\tif (resultLimitReached || packed.truncation.truncated) {\n\t\tconst note = broadQueryInvalidationNote(\n\t\t\tpacking.broadQueryTracker,\n\t\t\tnormalizeBroadQueryKey({ toolName: \"find\", pattern: packing.pattern, path: packing.rawPath }),\n\t\t\t`find \"${packing.pattern}\" in ${packing.rawPath ?? \".\"}`,\n\t\t);\n\t\tif (note) {\n\t\t\tnotices.push(note);\n\t\t\tdetails.invalidationCandidate = true;\n\t\t}\n\t}\n\tif (displayedResults.length > 0) {\n\t\tresultOutput += `\\n\\n[Summary - ${extSummary}]`;\n\t}\n\tif (notices.length > 0) {\n\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t}\n\treturn { text: resultOutput, details };\n}\n\nexport function createFindToolDefinition(\n\tcwd: string,\n\toptions?: FindToolOptions,\n): ToolDefinition<typeof findSchema, FindToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\tconst fffBackend = options?.fff === false ? undefined : (options?.fff ?? defaultFffSearchBackend);\n\tconst searchRouter = options?.searchRouter ?? defaultSearchRouter;\n\tconst artifactStore = options?.artifactStore;\n\tconst broadQueryTracker = options?.broadQueryTracker;\n\treturn {\n\t\tname: \"find\",\n\t\tlabel: \"find\",\n\t\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\t\tpromptSnippet: \"Find files by glob pattern (respects .gitignore)\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use the narrowest explicit root and glob that can answer the question; do not recursively scan a repository, home directory, or filesystem without a concrete purpose.\",\n\t\t],\n\t\tparameters: findSchema,\n\t\ttoolGroup: \"explore\",\n\t\tasync execute(\n\t\t\ttoolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tlimit,\n\t\t\t\tignoreCase,\n\t\t\t}: { pattern: string; path?: string; limit?: number; ignoreCase?: boolean },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet settled = false;\n\t\t\t\tlet stopChild: (() => void) | undefined;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tstopChild = undefined;\n\t\t\t\t\tfn();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tstopChild?.();\n\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\t\t\t\t\t\tconst probeLimit = effectiveLimit + 1;\n\t\t\t\t\t\tconst ops = customOps ?? defaultFindOperations;\n\n\t\t\t\t\t\tlet effectivePattern = pattern;\n\t\t\t\t\t\tif (pattern === \".\") {\n\t\t\t\t\t\t\teffectivePattern = \"**/*\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!customOps && fffBackend) {\n\t\t\t\t\t\t\tconst fffResult = await tryFffFind({\n\t\t\t\t\t\t\t\tbackend: fffBackend,\n\t\t\t\t\t\t\t\trouter: searchRouter,\n\t\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\t\tsearchPath,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\tignoreCase,\n\t\t\t\t\t\t\t\teffectiveLimit,\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (fffResult) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: fffResult.text }],\n\t\t\t\t\t\t\t\t\t\tdetails: Object.keys(fffResult.details).length > 0 ? fffResult.details : undefined,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If custom operations provide glob(), use that instead of fd.\n\t\t\t\t\t\tif (customOps?.glob) {\n\t\t\t\t\t\t\tif (!(await ops.exists(searchPath))) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst results = await ops.glob(effectivePattern, searchPath, {\n\t\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t\t\tlimit: probeLimit,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Relativize paths against the search root for stable output.\n\t\t\t\t\t\t\tconst relativized = results.map((p) => {\n\t\t\t\t\t\t\t\tif (p.startsWith(searchPath)) return toPosixPath(p.slice(searchPath.length + 1));\n\t\t\t\t\t\t\t\treturn toPosixPath(path.relative(searchPath, p));\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit, {\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Default implementation uses fd.\n\t\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"fd is not available and could not be downloaded\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore\n\t\t\t\t\t\t// semantics whether or not the search path is inside a git repository, without\n\t\t\t\t\t\t// leaking sibling-directory rules the way --ignore-file (a global source) would.\n\t\t\t\t\t\tconst portablePathGlob = process.platform === \"win32\" && effectivePattern.includes(\"/\");\n\t\t\t\t\t\tconst args: string[] = [\"--color=never\", \"--hidden\", \"--no-require-git\"];\n\t\t\t\t\t\tif (!portablePathGlob) {\n\t\t\t\t\t\t\targs.push(\"--glob\", \"--max-results\", String(probeLimit));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// fd's Windows full-path glob matcher compares slash-based glob regexes with\n\t\t\t\t\t\t// native backslash paths, so path globs can return no matches. On Windows,\n\t\t\t\t\t\t// retain fd's traversal and .gitignore semantics, then apply the portable glob\n\t\t\t\t\t\t// to normalized relative paths while streaming. Other platforms keep fd's\n\t\t\t\t\t\t// native bounded full-path matching.\n\t\t\t\t\t\tlet finalPattern = effectivePattern;\n\t\t\t\t\t\tif (!portablePathGlob && effectivePattern.includes(\"/\")) {\n\t\t\t\t\t\t\targs.push(\"--full-path\");\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"/\") &&\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"**/\") &&\n\t\t\t\t\t\t\t\teffectivePattern !== \"**\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tfinalPattern = `**/${effectivePattern}`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\targs.push(\"--\", portablePathGlob ? \"\" : finalPattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(fdPath, args, {\n\t\t\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet sawOutput = false;\n\t\t\t\t\t\tlet portableLimitReached = false;\n\t\t\t\t\t\tconst relativized: string[] = [];\n\t\t\t\t\t\tconst terminationController = new AbortController();\n\n\t\t\t\t\t\tstopChild = () => terminationController.abort();\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\trl.on(\"line\", (rawLine) => {\n\t\t\t\t\t\t\tsawOutput = true;\n\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\tif (!line) return;\n\t\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\trelativePath = toPosixPath(relativePath);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tportablePathGlob &&\n\t\t\t\t\t\t\t\t!minimatch(relativePath.replace(/\\/$/, \"\"), effectivePattern, {\n\t\t\t\t\t\t\t\t\tdot: true,\n\t\t\t\t\t\t\t\t\tnocase: ignoreCase,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) relativePath += \"/\";\n\t\t\t\t\t\t\trelativized.push(relativePath);\n\t\t\t\t\t\t\tif (portablePathGlob && relativized.length >= probeLimit && !portableLimitReached) {\n\t\t\t\t\t\t\t\tportableLimitReached = true;\n\t\t\t\t\t\t\t\tstopChild?.();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\t\t\ttimeoutMs: FIND_PROCESS_TIMEOUT_MS,\n\t\t\t\t\t\t\tkillGraceMs: FIND_PROCESS_KILL_GRACE_MS,\n\t\t\t\t\t\t}).finally(cleanup);\n\t\t\t\t\t\tconst code = terminal.code;\n\t\t\t\t\t\tif (terminal.reason === \"timeout\") {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`fd timed out after ${FIND_PROCESS_TIMEOUT_MS}ms`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (code !== 0 && !portableLimitReached && !sawOutput) {\n\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `fd exited with code ${code}`;\n\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit, {\n\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.details : undefined,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst error = e instanceof Error ? e : new Error(String(e));\n\t\t\t\t\t\tsettle(() => reject(error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatFindCall(args, theme, context.cwd));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatFindResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {\n\treturn wrapToolDefinition(createFindToolDefinition(cwd, options));\n}\n"]}