diff --git a/scripts/format-staged.js b/scripts/format-staged.js index 0ad2d7dd7..5a524f821 100644 --- a/scripts/format-staged.js +++ b/scripts/format-staged.js @@ -61,6 +61,9 @@ function resolveOxfmtCommand(repoRoot) { const binName = process.platform === "win32" ? "oxfmt.cmd" : "oxfmt"; const local = path.join(repoRoot, "node_modules", ".bin", binName); if (fs.existsSync(local)) { + if (process.platform === "win32" && local.toLowerCase().endsWith(".cmd")) { + return { command: "cmd.exe", args: ["/d", "/s", "/c", local] }; + } return { command: local, args: [] }; } diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 833771a1f..94e9a4d8f 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -13,6 +13,7 @@ import type { EmbeddedContextFile } from "../pi-embedded-helpers.js"; import { buildSystemPromptParams } from "../system-prompt-params.js"; import { resolveDefaultModelForAgent } from "../model-selection.js"; import { buildAgentSystemPrompt } from "../system-prompt.js"; +import { detectRuntimeShell } from "../shell-utils.js"; import { buildTtsSystemPromptHint } from "../../tts/tts.js"; const CLI_RUN_QUEUE = new Map>(); @@ -193,6 +194,7 @@ export function buildSystemPrompt(params: { node: process.version, model: params.modelDisplay, defaultModel: defaultModelLabel, + shell: detectRuntimeShell(), }, }); const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined; diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index dc68561c2..91786ebc2 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -67,6 +67,7 @@ import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "./system- import { splitSdkTools } from "./tool-split.js"; import type { EmbeddedPiCompactResult } from "./types.js"; import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js"; +import { detectRuntimeShell } from "../shell-utils.js"; import { describeUnknownError, mapThinkingLevel, resolveExecToolDefaults } from "./utils.js"; import { buildTtsSystemPromptHint } from "../../tts/tts.js"; @@ -300,6 +301,7 @@ export async function compactEmbeddedPiSessionDirect( arch: os.arch(), node: process.version, model: `${provider}/${modelId}`, + shell: detectRuntimeShell(), channel: runtimeChannel, capabilities: runtimeCapabilities, channelActions, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 46a53bd8f..3e62233b7 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -77,6 +77,7 @@ import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system import { splitSdkTools } from "../tool-split.js"; import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js"; import { buildSystemPromptParams } from "../../system-prompt-params.js"; +import { detectRuntimeShell } from "../../shell-utils.js"; import { describeUnknownError, mapThinkingLevel } from "../utils.js"; import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js"; import { buildTtsSystemPromptHint } from "../../../tts/tts.js"; @@ -319,6 +320,7 @@ export async function runEmbeddedAttempt( node: process.version, model: `${params.provider}/${params.modelId}`, defaultModel: defaultModelLabel, + shell: detectRuntimeShell(), channel: runtimeChannel, capabilities: runtimeCapabilities, channelActions, diff --git a/src/agents/shell-utils.ts b/src/agents/shell-utils.ts index 6d4efac59..2bb2c73d1 100644 --- a/src/agents/shell-utils.ts +++ b/src/agents/shell-utils.ts @@ -59,6 +59,42 @@ function resolveShellFromPath(name: string): string | undefined { return undefined; } +function normalizeShellName(value: string): string { + const trimmed = value.trim(); + if (!trimmed) return ""; + return path.basename(trimmed).replace(/\.(exe|cmd|bat)$/i, ""); +} + +export function detectRuntimeShell(): string | undefined { + const overrideShell = process.env.CLAWDBOT_SHELL?.trim(); + if (overrideShell) { + const name = normalizeShellName(overrideShell); + if (name) return name; + } + + if (process.platform === "win32") { + if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL) { + return "pwsh"; + } + return "powershell"; + } + + const envShell = process.env.SHELL?.trim(); + if (envShell) { + const name = normalizeShellName(envShell); + if (name) return name; + } + + if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL) return "pwsh"; + if (process.env.BASH_VERSION) return "bash"; + if (process.env.ZSH_VERSION) return "zsh"; + if (process.env.FISH_VERSION) return "fish"; + if (process.env.KSH_VERSION) return "ksh"; + if (process.env.NU_VERSION || process.env.NUSHELL_VERSION) return "nu"; + + return undefined; +} + export function sanitizeBinaryOutput(text: string): string { const scrubbed = text.replace(/[\p{Format}\p{Surrogate}]/gu, ""); if (!scrubbed) return scrubbed; diff --git a/src/agents/system-prompt-params.ts b/src/agents/system-prompt-params.ts index 276203ecb..499b28059 100644 --- a/src/agents/system-prompt-params.ts +++ b/src/agents/system-prompt-params.ts @@ -17,6 +17,7 @@ export type RuntimeInfoInput = { node: string; model: string; defaultModel?: string; + shell?: string; channel?: string; capabilities?: string[]; /** Supported message actions for the current channel (e.g., react, edit, unsend) */ diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index ed97fd539..6d7fd2a06 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -155,6 +155,7 @@ export function buildAgentSystemPrompt(params: { node?: string; model?: string; defaultModel?: string; + shell?: string; channel?: string; capabilities?: string[]; repoRoot?: string; @@ -562,6 +563,7 @@ export function buildRuntimeLine( node?: string; model?: string; defaultModel?: string; + shell?: string; repoRoot?: string; }, runtimeChannel?: string, @@ -580,6 +582,7 @@ export function buildRuntimeLine( runtimeInfo?.node ? `node=${runtimeInfo.node}` : "", runtimeInfo?.model ? `model=${runtimeInfo.model}` : "", runtimeInfo?.defaultModel ? `default_model=${runtimeInfo.defaultModel}` : "", + runtimeInfo?.shell ? `shell=${runtimeInfo.shell}` : "", runtimeChannel ? `channel=${runtimeChannel}` : "", runtimeChannel ? `capabilities=${runtimeCapabilities.length > 0 ? runtimeCapabilities.join(",") : "none"}`