diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a23a1a4bf..92a6ce536 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -40,6 +40,55 @@ - Fixed the Windows console disappearing when running commands like `/stats`. - Fixed retry-fallback selection switching to a fallback model with a context window too small to hold the current session context. - Fixed OpenCode discovery ignoring `opencode.jsonc` files and rejecting comments in `opencode.json`. +- Removed the `advisor.subagents` setting; subagent advisors are now configured per agent (frontmatter `advisor` / `task.agentAdvisor`). An existing `advisor.subagents: true` migrates to `task.agentAdvisor: { task: "on" }` — the bundled generic `task` agent keeps its advisor, other agents start unadvised. + +### Changed + +- `/usage`, `omp usage`, and the status line now show authoritative OpenCode Go quota from the official `GET /zen/go/v1/usage` endpoint — including usage made outside OMP — instead of dollar estimates summed from OMP-observed request costs. The status line renders all three windows (`5h` / `7d` / `mo`), and the per-turn cost recording special case for `opencode-go` sessions is gone along with the "OMP-observed spend only" disclaimer ([#8337](https://github.com/can1357/oh-my-pi/pull/8337) by [@will-bogusz](https://github.com/will-bogusz)). +- Clarified that the production collab relay source and binaries are not currently published, and documented the source-available local protocol relay ([#8165](https://github.com/can1357/oh-my-pi/issues/8165)). +- Enabled bounded Anthropic prompt-cache refreshes for the main agent loop while keeping advisor and side-channel requests from taking over the shared refresh timer. +- Fixed snapcompact compaction shipping its redundant frame archive out of `SessionMaintenance.compact()` on both the manual RPC response (which hard-failed protocol v1 with a transport error after the compaction had already persisted) and the `auto_compaction_end` event payload (which forced the shrink ladder on every unattended pass); the archive is now stripped from both exits while the persisted compaction entry keeps it ([#8168](https://github.com/can1357/oh-my-pi/issues/8168)). +- Fixed the edit tool showing no diff preview in `apply_patch` mode: the built-in `edit` tool presents on the wire as `apply_patch`, but the renderer-provenance gate did not resolve that alias to its built-in owner, so the edit renderer was skipped ([#8184](https://github.com/can1357/oh-my-pi/issues/8184)). + +### Fixed + +- Fixed WSL2 startup hanging forever when the Windows interop pipe is wedged: the host-home probe added for #3779 ran `Bun.spawnSync(["cmd.exe", "/d", "/c", "echo", "%USERPROFILE%"])` (and `wslpath`) with no timeout, so a stuck `cmd.exe` blocked the whole startup thread before the TUI painted or any log was written. Best-effort discovery probes now run under a 500ms hard timeout (SIGKILL) and treat a killed/non-zero exit as "host home unavailable", falling back to the Linux `$HOME`/`~/.omp` candidates ([#8402](https://github.com/can1357/oh-my-pi/issues/8402)). +- Refined interrupted-turn continuity prompts by omitting reasoning fragments under 60 characters, relying on native signed or encrypted thinking when available, and framing preserved text as a natural user interruption. +- Fixed session-title generation regressing after prompt condensation: the telegraphic rewrite of `title-system.md` garbled small-model output (invented names, punctuation-only titles). Restored plain-sentence phrasing with a name-fidelity instruction, pinned the online title request to greedy decoding, and rejected punctuation-only titles in normalization. +- Fixed Agent Control Center failing to open when an agent model override is configured as a YAML array. ([#8201](https://github.com/can1357/oh-my-pi/issues/8201)) +- Fixed streaming and finalized transcript blocks exposing width-independent source boundaries so multiplexer pane resizes retain output queued during settlement without duplicating prior transcript history. +- Fixed command-backed provider API keys (`!command`) staying pinned to their process-cached value after HTTP 401; auth retry now reruns the command, updates live authorization headers, and retries with the refreshed bearer. +- Fixed print mode claiming `plan.defaultOnStartup` was ignored and recommending `--plan-yolo` when that headless plan flow was already active ([#8312](https://github.com/can1357/oh-my-pi/issues/8312)). +- Fixed MCP startup failures omitting the originating configuration path, so stale imported server entries can be traced to their source file. +- Fixed custom STB-backed vision providers retaining decoder metadata and sending WebP bytes that llama.cpp cannot decode. Image format is now detected from bytes, attached and historical WebP blocks are normalized on the actual provider-request path, and corrupt WebP is omitted instead of making the turn unrecoverable ([#8339](https://github.com/can1357/oh-my-pi/pull/8339) by [@ethancawse](https://github.com/ethancawse)). +- Fixed Python Eval `parallel()` and `pipeline()` rejecting `await` after their work completed, allowing synchronous and awaited calls without repeating operations. +- Contained Mnemopi bank failures at agent lifecycle boundaries so optional recall and retention cannot terminate the host session ([#8351](https://github.com/can1357/oh-my-pi/issues/8351)). +- Fixed bash utility builtins resolving MSYS-style `/c/...` paths against a phantom rooted path instead of the live Windows drive, preventing stale reads and lost writes ([#8355](https://github.com/can1357/oh-my-pi/issues/8355)). +- Fixed open browser behaviour on Windows, OMP console no longer disappears on commands such as `/stats` +- Fixed long streaming `write` previews repeatedly scanning and splitting the full accumulated file content, which could stall the TUI. +- Fixed custom commands losing the documented `api.arktype.type(...)` compatibility surface while retaining the callable `api.arktype(...)` builder. ([#7968](https://github.com/can1357/oh-my-pi/issues/7968)) +- Fixed retry-fallback selection switching a live session from a large-context primary onto a smaller-context fallback and immediately sending a predictably oversized request; candidate selection now skips any fallback whose usable window cannot hold the current context and advances to the first configured candidate that fits ([#8065](https://github.com/can1357/oh-my-pi/issues/8065)). +- Fixed advisor recovery selecting another role's fallback chain when both roles use the same model. ([#8075](https://github.com/can1357/oh-my-pi/issues/8075)) +- Fixed `retry_fallback_applied` and `retry_fallback_succeeded` not being forwarded to extensions: `AgentSession.#emitExtensionEvent` had no branch for either event and `ExtensionAPI.on(...)` lacked overloads, so extension handlers could not observe model/advisor fallback transitions or successes that the TUI and RPC paths already received ([#8079](https://github.com/can1357/oh-my-pi/issues/8079)). +- Fixed the CLI crashing at startup with a raw uncaught `AuthBrokerError` when a configured auth broker (`auth.broker.url` / `OMP_AUTH_BROKER_URL`) is unreachable and no fresh cached snapshot exists. Startup auth discovery now fails with an actionable message naming the broker URL and the recovery options (`omp auth-broker serve`, or resetting `auth.broker.url` / `auth.broker.token`) and exits non-zero, instead of dumping a stack trace ([#8096](https://github.com/can1357/oh-my-pi/issues/8096)). +- Fixed OpenCode discovery ignoring `opencode.jsonc` files and rejecting comments in `opencode.json`, which omitted imported settings and MCP servers. ([#8104](https://github.com/can1357/oh-my-pi/issues/8104)) +- Fixed the launch broker staying alive indefinitely after its last persistent daemon exited with no clients connected: the idle-shutdown timer that fired while the daemon was still live returned without rearming, and terminal settlement never scheduled another idle check, so the broker process, endpoint, timers, and record maps leaked. Terminal settlement now rearms idle shutdown, which re-checks clients, remaining live persistent daemons, and detached project presence before exiting ([#8110](https://github.com/can1357/oh-my-pi/issues/8110)). +- Fixed a cold persisted-agent revival racing with lifecycle teardown: a revive whose reviver factory or session resolved after `AgentLifecycleManager.dispose()` could attach a live session and arm a TTL on the disposed manager, leaking a session graph and timers past teardown. Late revivals now reject deterministically and dispose any session they built ([#8114](https://github.com/can1357/oh-my-pi/issues/8114)). +- Fixed stale or unhealthy initial MCP connections leaving child processes or sockets open after `disconnectAll()` or a failed `tools/list` request. ([#8112](https://github.com/can1357/oh-my-pi/issues/8112)) +- Fixed the DAP `runInTerminal` reverse request leaving the spawned debuggee's stdout undrained: the child was spawned with a piped stdout that was never consumed, so a chatty debuggee's output buffered unboundedly in the omp process (toward OOM) and was lost from the session output. Its stdout is now continuously drained into the session output buffer. ([#8111](https://github.com/can1357/oh-my-pi/issues/8111)) +- Fixed the `/ssh add` inline hint omitting the `--scope project|user` option. +- Fixed `omp://` throwing `ENOENT` for npm/SDK consumers: `@oh-my-pi/pi-coding-agent`'s `exports` resolve to TypeScript source where the build-time `PI_DOCS_EMBED` is empty, and the dev-tree fallback pointed at an unreachable `node_modules/docs`, so `OmpProtocolHandler.complete()`/`.resolve()` crashed for any consumer importing the package from npm. `gen:bundle` now also ships the docs corpus as `dist/docs-index.generated.txt`, the source path reads it when the env embed is empty and the on-disk `docs/` is absent, and a missing corpus degrades to an empty index (with a warning) instead of propagating `ENOENT` ([#8134](https://github.com/can1357/oh-my-pi/issues/8134)). +- Fixed the legacy TypeBox facade rejecting `Type.Optional(Type.Unsafe(...))`, losing optional object properties when raw schemas were present, dropping JSON-Schema-only keywords (e.g. `patternProperties`) from nested `Type.Unsafe` wire schemas, and plugin installs accepting extension factories that fail during initialization ([#8143](https://github.com/can1357/oh-my-pi/issues/8143)). +- Fixed `omp install` failing extension validation for pi extensions that import the `isToolResult` event guards from `@earendil-works/pi-coding-agent` (e.g. `pi-lean-ctx@3.9.18`, which uses `isEditToolResult`/`isWriteToolResult`). The legacy shim's `export * from "../index"` never forwarded the guard family (dropped from the public API in 10.2.3), so a named import threw Bun's static "Export named 'isEditToolResult' not found" error. Restored `isBashToolResult`, `isReadToolResult`, `isEditToolResult`, `isWriteToolResult`, `isGrepToolResult`, `isFindToolResult`, and `isLsToolResult` on the shim to match the upstream pi surface ([#8161](https://github.com/can1357/oh-my-pi/issues/8161)). +- Fixed profile aliases generated by standalone binaries invoking Bun's embedded virtual script instead of the installed `omp` command ([#8233](https://github.com/can1357/oh-my-pi/issues/8233)). +- Fixed large-session restore and `/tree` navigation blocking input while rebuilding the transcript by chunking idle rebuilds and terminal paints across event-loop turns ([#8133](https://github.com/can1357/oh-my-pi/issues/8133)). +- Fixed the system prompt unconditionally requiring browser verification for UI changes even when the `browser` tool is unavailable; verification now follows the actual UI surface and available tools, with a behavioral/smoke-test fallback when no runtime tool exists ([#8139](https://github.com/can1357/oh-my-pi/issues/8139)). +- Fixed agent-facing prompts mentioning tools that may be absent from the session catalog: `todo`/`grep` workflow guidance in the system prompt, `glob`/`read`/`edit` drill-in hints in the project prompt, `ask` directives in plan mode, and the orchestrate notice's `task`/`edit`/`write`/`lsp`/`bash`/`todo` budget are now gated on tool availability. +- Fixed a `/skill:` token embedded in a `/plan` or `/vibe` inline prompt being sent to the agent as literal text instead of loading the skill; mode-command inline prompts now dispatch skill invocations through the same custom-message path as the editor submit flow ([#8137](https://github.com/can1357/oh-my-pi/issues/8137)). +- Fixed Codex reset fireworks triggering on ordinary weekly-usage decreases when the provider had not advanced the quota reset deadline. +- Fixed below-threshold tool turns waiting for asynchronous session persistence when no mid-run compaction will run, while preserving journal writes when a `message_end` listener fails and isolating notification-only handler payloads from late context mutations ([#8283](https://github.com/can1357/oh-my-pi/pull/8283) by [@ethancawse](https://github.com/ethancawse)). +- Manual `/shake` now keeps a small recent tail of tool results instead of stripping every eligible result, so the agent does not lose the context it is currently working from ([#7776](https://github.com/can1357/oh-my-pi/issues/7776)). +- Fixed Hindsight `per-project` and `per-project-tagged` scoping splitting one repository across two memory scopes when the checkout directory carries capitals: the project label is now lowercased, so a checkout at `~/code/General` writes and recalls under `project:general` like every other client of the same bank instead of opening a private `project:General` scope. **Migration note:** mixed-case checkouts previously stored memories under the case-preserving label (`per-project` bank id, `per-project-tagged` `project:` tags); after upgrading, those sessions read and write the lowercased scope, so memories retained under the old capitalized scope stay in the old bank/tag until re-retained there ([#8158](https://github.com/can1357/oh-my-pi/issues/8158)). ## [17.2.15] - 2026-08-12 diff --git a/packages/coding-agent/src/discovery/agents.ts b/packages/coding-agent/src/discovery/agents.ts index 1d5a6fae5..b6d3837bd 100644 --- a/packages/coding-agent/src/discovery/agents.ts +++ b/packages/coding-agent/src/discovery/agents.ts @@ -54,9 +54,33 @@ function convertWindowsPathToDefaultWslMount(windowsPath: string): string | unde return path.posix.join("/mnt", drive.toLowerCase(), ...segments); } -function resolveWithWslPath(windowsPath: string): string | undefined { +/** + * Hard cap for best-effort host-discovery probes. + * + * WSL→Windows interop can wedge indefinitely (issue #8402): a synchronous + * spawn with no timeout blocks the whole startup thread before the TUI paints + * or any log file is created. The probe result only ever augments discovery + * with an extra host-home candidate, so a few hundred milliseconds is a + * generous ceiling — past it we treat the host as unavailable. + */ +const HOST_PROBE_TIMEOUT_MS = 500; + +/** + * Run a best-effort discovery probe and return its trimmed stdout, or + * `undefined` when the command fails, produces no output, or exceeds + * {@link HOST_PROBE_TIMEOUT_MS}. On timeout the child is killed with SIGKILL so + * a wedged interop pipe cannot hang startup; the killed/non-zero exit is then + * reported as "unavailable" and discovery falls back to the Linux + * `$HOME`/`~/.omp` candidates. + */ +export function runHostProbe(cmd: string[]): string | undefined { try { - const result = Bun.spawnSync(["wslpath", "-u", windowsPath], { stdout: "pipe", stderr: "ignore" }); + const result = Bun.spawnSync(cmd, { + stdout: "pipe", + stderr: "ignore", + timeout: HOST_PROBE_TIMEOUT_MS, + killSignal: "SIGKILL", + }); if (result.exitCode !== 0) return undefined; const resolved = result.stdout.toString().trim(); return resolved.length > 0 ? resolved : undefined; @@ -65,18 +89,13 @@ function resolveWithWslPath(windowsPath: string): string | undefined { } } +function resolveWithWslPath(windowsPath: string): string | undefined { + return runHostProbe(["wslpath", "-u", windowsPath]); +} + function resolveWindowsUserProfile(): string | undefined { - try { - const result = Bun.spawnSync(["cmd.exe", "/d", "/c", "echo", "%USERPROFILE%"], { - stdout: "pipe", - stderr: "ignore", - }); - if (result.exitCode !== 0) return undefined; - const resolved = result.stdout.toString().trim(); - return resolved.length > 0 && resolved !== "%USERPROFILE%" ? resolved : undefined; - } catch { - return undefined; - } + const resolved = runHostProbe(["cmd.exe", "/d", "/c", "echo", "%USERPROFILE%"]); + return resolved && resolved !== "%USERPROFILE%" ? resolved : undefined; } /** Resolve the Windows host profile home exposed to WSL, if available. */ diff --git a/packages/coding-agent/test/skills.test.ts b/packages/coding-agent/test/skills.test.ts index 6675f583f..23b7ca10c 100644 --- a/packages/coding-agent/test/skills.test.ts +++ b/packages/coding-agent/test/skills.test.ts @@ -4,7 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { type Skill as CapabilitySkill, skillCapability } from "@oh-my-pi/pi-coding-agent/capability/skill"; import { getCapability } from "@oh-my-pi/pi-coding-agent/discovery"; -import { getWslWindowsHomeCandidate } from "@oh-my-pi/pi-coding-agent/discovery/agents"; +import { getWslWindowsHomeCandidate, runHostProbe } from "@oh-my-pi/pi-coding-agent/discovery/agents"; import { loadSkills, loadSkillsFromDir, @@ -298,6 +298,27 @@ describe("skills", () => { expect(resolved).toBe("/mnt/c/Users/alice"); }); + it("kills a host probe that never exits instead of blocking startup (#8402)", () => { + // Integration test against real OS timer behavior: the contract is that + // runHostProbe's spawnSync `timeout` actually kills a genuinely blocked + // child. That is a native process-lifecycle effect the kernel drives, so + // fake timers cannot exercise it. The child would sleep a minute (stand-in + // for a wedged WSL->Windows interop pipe); the 500ms probe timeout must + // kill it and report "unavailable" rather than hang the calling thread. + const start = performance.now(); + const result = runHostProbe([process.execPath, "-e", "await Bun.sleep(60_000)"]); + const elapsed = performance.now() - start; + expect(result).toBeUndefined(); + // Loose bound: proves the probe returned via its own timeout, not via the + // child completing; a broken timeout would block far past this ceiling. + expect(elapsed).toBeLessThan(5_000); + }); + + it("returns trimmed stdout for a host probe that succeeds (#8402)", () => { + const result = runHostProbe([process.execPath, "-e", "process.stdout.write(' host-home ')"]); + expect(result).toBe("host-home"); + }); + it("respects an explicit enableAgentsUser: false (#2401)", async () => { const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "pi-agents-home-off-")); const tempCwd = await fs.mkdtemp(path.join(os.tmpdir(), "pi-agents-cwd-off-"));