Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 54 additions & 18 deletions src/core/prompts/sections/__tests__/system-info.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ vi.mock("../../../../utils/shell", () => ({
import { getSystemInfoSection } from "../system-info"
import osName from "os-name"

const mockOsName = osName as unknown as ReturnType<typeof vi.fn>
const mockOsName = vi.mocked(osName)

describe("getSystemInfoSection", () => {
const mockCwd = "/test/workspace"
const mockHomeDir = "/home/user"

beforeEach(() => {
vi.spyOn(os, "homedir").mockReturnValue(mockHomeDir)
vi.spyOn(os, "platform").mockReturnValue("linux" as any)
vi.spyOn(os, "platform").mockReturnValue("linux")
vi.spyOn(os, "release").mockReturnValue("5.15.0")
})

Expand All @@ -34,6 +34,7 @@ describe("getSystemInfoSection", () => {

afterEach(() => {
vi.clearAllMocks()
vi.restoreAllMocks()
})

it("should return system info with os-name when available", () => {
Expand All @@ -47,29 +48,64 @@ describe("getSystemInfoSection", () => {
expect(result).toContain(`Current Workspace Directory: ${mockCwd}`)
})

it("should fallback to platform and release when os-name throws error", () => {
mockOsName.mockImplementation(() => {
throw new Error("Command failed with ENOENT: powershell")
it.each([
{
platform: "linux" as const,
release: "5.15.0",
makeLookupThrow: () =>
mockOsName.mockImplementation(() => {
throw new Error("Command failed with ENOENT: powershell")
}),
},
{
platform: "win32" as const,
release: "10.0.26300",
makeLookupThrow: () =>
vi.spyOn(os, "version").mockImplementation(() => {
throw new Error("uv_os_uname failed")
}),
},
])(
"falls back to platform and release when the OS lookup throws on $platform",
({ platform, release, makeLookupThrow }) => {
vi.spyOn(os, "platform").mockReturnValue(platform)
vi.spyOn(os, "release").mockReturnValue(release)
makeLookupThrow()

const result = getSystemInfoSection(mockCwd, policyFor())

expect(result).toContain(`Operating System: ${platform} ${release}`)
expect(result).toContain("Default Shell: /bin/bash")
expect(result).toContain(`Home Directory: ${mockHomeDir}`)
expect(result).toContain(`Current Workspace Directory: ${mockCwd}`)
},
)

describe("on Windows", () => {
beforeEach(() => {
vi.spyOn(os, "platform").mockReturnValue("win32")
vi.spyOn(os, "release").mockReturnValue("10.0.26300")
})

const result = getSystemInfoSection(mockCwd, policyFor())
it("reads the product name from os.version() without calling os-name", () => {
// os-name runs wmic and PowerShell synchronously on Windows; a PowerShell that never
// exits freezes the extension host, so it must not be reached at all.
vi.spyOn(os, "version").mockReturnValue("Windows 11 Pro")

expect(result).toContain("Operating System: linux 5.15.0")
expect(result).toContain("Default Shell: /bin/bash")
expect(result).toContain(`Home Directory: ${mockHomeDir}`)
expect(result).toContain(`Current Workspace Directory: ${mockCwd}`)
})
const result = getSystemInfoSection(mockCwd, policyFor())

it("should handle Windows platform in fallback", () => {
mockOsName.mockImplementation(() => {
throw new Error("Command failed with ENOENT: powershell")
expect(result).toContain("Operating System: Windows 11 Pro")
expect(mockOsName).not.toHaveBeenCalled()
})
vi.spyOn(os, "platform").mockReturnValue("win32" as any)
vi.spyOn(os, "release").mockReturnValue("10.0.19043")

const result = getSystemInfoSection(mockCwd, policyFor())
it("falls back to platform and release when os.version() is empty", () => {
vi.spyOn(os, "version").mockReturnValue("")

expect(result).toContain("Operating System: win32 10.0.19043")
const result = getSystemInfoSection(mockCwd, policyFor())

expect(result).toContain("Operating System: win32 10.0.26300")
expect(mockOsName).not.toHaveBeenCalled()
})
})

it("omits the terminal sentence when execute_command is absent", () => {
Expand Down
33 changes: 23 additions & 10 deletions src/core/prompts/sections/system-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ import { getShell } from "../../../utils/shell"

import type { EffectiveToolPolicy } from "../tools/effective-tool-policy"

/**
* Describes the host operating system for the system prompt.
*
* Windows bypasses `os-name`: its `windows-release` dependency runs `wmic`, then PowerShell,
* through `execaSync` with no timeout. This section is built for every request, so that call
* blocks the extension host for as long as the process runs. `os.version()` reads the product
* name without starting a process.
*/
function getOsInfo(): string {
const fallback = `${os.platform()} ${os.release()}`

try {
if (os.platform() === "win32") {
return os.version() || fallback
}

return osName()
} catch {
return fallback
}
}

/**
* Builds the SYSTEM INFORMATION section of the system prompt.
*
Expand All @@ -16,16 +38,7 @@ import type { EffectiveToolPolicy } from "../tools/effective-tool-policy"
* @param policy The request's effective tool policy.
*/
export function getSystemInfoSection(cwd: string, policy: EffectiveToolPolicy): string {
// Try to get detailed OS name, fall back to basic info if it fails
let osInfo: string
try {
osInfo = osName()
} catch {
// Fallback when os-name fails (e.g., PowerShell not available on Windows)
const platform = os.platform()
const release = os.release()
osInfo = `${platform} ${release}`
}
const osInfo = getOsInfo()

const executeCommandAvailable = policy.tools.has("execute_command")

Expand Down
5 changes: 0 additions & 5 deletions src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,6 @@
"count": 55
}
},
"core/prompts/sections/__tests__/system-info.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
}
},
"core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 14
Expand Down
Loading