diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d1722f2ec..9c0a1e87b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed the `` block rendering `Kernel: unknown` on macOS 15+, which caused the model to display the wrong OS glyph, by falling back to ` ` (e.g. `Darwin 25.5.0`) when `os.version()` returns `"unknown"` or an empty string ([#4141](https://github.com/can1357/oh-my-pi/issues/4141)). + ## [16.2.12] - 2026-07-01 ### Breaking Changes diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts index 3c410b396..890e37484 100644 --- a/packages/coding-agent/src/system-prompt.ts +++ b/packages/coding-agent/src/system-prompt.ts @@ -249,6 +249,20 @@ async function getCachedGpu(): Promise { await logger.time("getCachedGpu:saveGpuCache", saveGpuCache, { gpu }); return gpu ?? undefined; } +/** + * Kernel identity for the workstation block. Prefers the uname build string + * from `os.version()`, but Bun on macOS 15+ (Darwin 24/25) returns the literal + * `"unknown"` when `uv_os_uname()`'s `version` field is empty — which surfaces + * `Kernel: unknown` in the system prompt and makes the model misidentify the + * host as Windows (#4141). Fall back to ` ` (uname -s + -r) so + * macOS is always tagged as `Darwin ` and Linux keeps its build info. + */ +function getKernelIdentity(): string { + const version = os.version()?.trim(); + if (version && version.toLowerCase() !== "unknown") return version; + return `${os.type()} ${os.release()}`.trim(); +} + function getEnvironmentInfo(gpu: string | undefined): Array<{ label: string; value: string }> { let cpuModel: string | undefined; try { @@ -259,7 +273,7 @@ function getEnvironmentInfo(gpu: string | undefined): Array<{ label: string; val const entries: Array<{ label: string; value: string | undefined }> = [ { label: "OS", value: `${os.platform()} ${os.release()}` }, { label: "Distro", value: os.type() }, - { label: "Kernel", value: os.version() }, + { label: "Kernel", value: getKernelIdentity() }, { label: "Arch", value: os.arch() }, { label: "CPU", value: cpuModel }, { label: "GPU", value: gpu }, diff --git a/packages/coding-agent/test/system-prompt-kernel.test.ts b/packages/coding-agent/test/system-prompt-kernel.test.ts new file mode 100644 index 000000000..ee6676e50 --- /dev/null +++ b/packages/coding-agent/test/system-prompt-kernel.test.ts @@ -0,0 +1,86 @@ +import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { buildSystemPrompt } from "@oh-my-pi/pi-coding-agent/system-prompt"; +import { cleanupTempHome } from "./helpers/temp-home-cleanup"; + +const EMPTY_TREE = { + rootPath: "", + rendered: "", + truncated: false, + totalLines: 0, + agentsMdFiles: [], +}; + +// Regression: Bun on macOS 15+ (Darwin 24/25) makes `os.version()` return the +// literal "unknown", which used to leak into the block as +// `Kernel: unknown` and caused the model to display the wrong OS glyph +// (issue #4141). The Kernel field must always carry a real identity. +describe("system prompt Kernel field", () => { + let tempDir = ""; + let tempHomeDir = ""; + let originalHome: string | undefined; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-kernel-")); + tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-kernel-home-")); + originalHome = process.env.HOME; + process.env.HOME = tempHomeDir; + }); + + afterEach(cleanupTempHome(() => ({ tempDir, tempHomeDir, originalHome }))); + + it(`falls back to " " when os.version() returns "unknown" (Bun on macOS 15+)`, async () => { + spyOn(os, "version").mockReturnValue("unknown"); + spyOn(os, "type").mockReturnValue("Darwin"); + spyOn(os, "release").mockReturnValue("25.5.0"); + + const { systemPrompt } = await buildSystemPrompt({ + cwd: tempDir, + contextFiles: [], + skills: [], + rules: [], + toolNames: [], + workspaceTree: { ...EMPTY_TREE, rootPath: tempDir }, + }); + + const rendered = systemPrompt.join("\n\n"); + expect(rendered).not.toContain("Kernel: unknown"); + expect(rendered).toContain("Kernel: Darwin 25.5.0"); + }); + + it("also falls back when os.version() is empty or whitespace", async () => { + spyOn(os, "version").mockReturnValue(" "); + spyOn(os, "type").mockReturnValue("Darwin"); + spyOn(os, "release").mockReturnValue("25.5.0"); + + const { systemPrompt } = await buildSystemPrompt({ + cwd: tempDir, + contextFiles: [], + skills: [], + rules: [], + toolNames: [], + workspaceTree: { ...EMPTY_TREE, rootPath: tempDir }, + }); + + expect(systemPrompt.join("\n\n")).toContain("Kernel: Darwin 25.5.0"); + }); + + it("keeps the real uname build string when os.version() is populated", async () => { + spyOn(os, "version").mockReturnValue( + "Darwin Kernel Version 25.5.0: Tue Nov 7 21:48:04 PST 2026; root:xnu-11215.1.12~1/RELEASE_ARM64_T6031", + ); + + const { systemPrompt } = await buildSystemPrompt({ + cwd: tempDir, + contextFiles: [], + skills: [], + rules: [], + toolNames: [], + workspaceTree: { ...EMPTY_TREE, rootPath: tempDir }, + }); + + expect(systemPrompt.join("\n\n")).toContain("Kernel: Darwin Kernel Version 25.5.0:"); + }); +});