StdioTransport.connect() unconditionally passed detached:true to
Bun.spawn. POSIX needs this so terminal job-control signals (SIGTSTP,
SIGTTIN) cannot stop stdio servers such as chrome-devtools-mcp; Windows
has no equivalent signals, but detached:true maps to
CreateProcess(DETACHED_PROCESS), which strips the parent's inherited
console. windowsHide:true (#3536) hides the direct child's window but
nothing else — when the direct child was a hidden cmd.exe wrapper that
later spawned a console grandchild (node wrapper, npx.cmd -y mcp-remote,
similar nested shells) the grandchild allocated a brand-new visible
conhost and its stdout no longer routed through OMP's pipe. The proxy
terminal reported the MCP bridge was healthy while OMP timed out
waiting for the MCP initialize response.
Move detached into StdioSpawnCommand alongside windowsHide so every
platform-derived spawn flag is resolved in one place:
resolveStdioSpawnCommand returns detached:false on every Windows return
shape (direct .exe, cmd.exe-wrapped batch/unresolvable command, npm
cmd-shim launched through node) and detached:true on POSIX. connect()
consumes the resolved flag. Add a regression test for the reporter's
exact shape (cmd.exe /C node wrapper) and assert detached on every
existing Windows / POSIX case so the contract cannot regress per-path.
Fixes#3544
- Surface the exception type and message in the error display by prepending the formatted error string to the traceback array.
- Prevent the host from hiding the actual error by ensuring the traceback is not empty, consistent with other language runners.
`discoverOAuthEndpoints` probes `/.well-known/oauth-authorization-server` at
the origin root before path-prefixed candidates and returns on the first hit.
Plane hosts a root issuer (`https://mcp.plane.so/`) at origin root and a
separate path-scoped issuer (`https://mcp.plane.so/http`) at the path-prefixed
well-known. The `/http/mcp` endpoint advertises only the path-scoped issuer
through protected-resource metadata, so discovery should follow that issuer's
metadata — instead it accepted the wrong origin-root document and routed the
grant to `https://mcp.plane.so/authorize`, which rejects every request with
`server_error=An unexpected error occurred` before the consent screen.
RFC 8414 §3.3 requires the metadata's `issuer` to equal the URL the client
used to construct the metadata URL. Validate it in the discovery loop: when
the queried well-known is the official authorization-server or OpenID Connect
document, skip metadata whose `issuer` doesn't match (after trailing-slash
normalization). Documents without an `issuer` field keep the existing
permissive behavior so legacy/nonstandard servers continue to work.
Verified live against `https://mcp.plane.so/http/authorize` with the same
client/PKCE: pre-fix `302 -> /callback?error=server_error`, post-fix
`302 -> /http/consent?txn_id=…`. Adds an `oauth-discovery.test.ts`
regression suite covering Plane's wrong-issuer origin-root document plus
trailing-slash and no-issuer paths.
Fixes#3537
Set windowsHide for every Windows stdio MCP spawn path so direct .exe servers no longer open a visible cmd.exe window.
Added a regression test covering direct Windows executable MCP server launch options.
Fixes#3535
refreshMCPOAuthToken now takes a trailing { authorizationUrl, stripSameOriginResource } options object (issue #3502 follow-up). Update the two stale per-profile binding assertions to expect it, and assert the fallback resource is not persisted (resource: undefined) since it is re-derived from config.url on each refresh.
- Clarify the auto-advancement logic for the in-progress pointer in the documentation and output.
- Add an overall completion count summary to the task list view.
- Update the list format to use standard checkbox indicators and explicit tags for task statuses.
Mid-turn renderSessionContext (settings overlay close, focus attach during streaming) now hands the rebuilt todo snapshot back to the EventController via the new inheritDisplaceableTodo method instead of sealing it. Idle rebuilds keep the historic seal path.
Added a regression test that asserts the trailing todo snapshot is published to the controller and stays displaceable while session.isStreaming is true.
Fixes#3516
Dropped eager todo snapshot displacement from tool_execution_start, streaming message_update, and the rebuild assistant-iteration step. Displacement now runs only when the next todo's successful result lands, so a failed follow-up leaves the last-good todo panel on screen.
Added regression coverage for the failed follow-up case and updated the streamed-second-todo test to drive displacement from the success result.
Fixes#3516
Resolved any tracked todo snapshot before storing a fresh one in the rebuild paths so an assistant message replaying multiple todo tool calls collapses to the final snapshot.
Added a renderSessionContext regression test for two todo tool calls in one rebuilt assistant message.
Fixes#3516
Resolved existing todo components before tool execution so streamed tool-call previews can still displace stale todo snapshots.
Added regression coverage for pre-created todo calls replacing a prior todo panel after intervening tool output.
Fixes#3516
Kept successful todo result blocks live until a later todo update replaces them or the turn ends.
Added regression coverage for same-turn todo snapshot replacement after intervening tool output.
Fixes#3516
Tracked the highest delivered severity per note so a nit can later land as a concern or blocker without being silently dropped.
De-escalation back to nit/concern stays treated as a duplicate so the model cannot flap severities to bypass dedupe.
Fixes#3511
Cleared delivered-note memory when the advisor session state resets across conversation boundaries.
Added coverage that repeated advice is allowed again after the dedupe state resets.
Fixes#3511
Deduplicated advisor notes inside the advise tool so a model cannot enqueue the same advisory repeatedly in one session.
Added focused regression coverage for duplicate advisory suppression.
Fixes#3511
Restored the pi-ai OAuth device-code helper expected by legacy provider packages and rewrote extension-owned bare dependencies to file URLs during validation so compiled binaries do not rely on Bun's runtime bare resolver.
Excluded worker entry modules from the compiled legacy bundled registry so validation does not import worker-only code on the main thread.
Fixes#3508
Reviewer caught: the macOS file-URL loop returned after the first
image-shaped path, silently dropping the rest of a multi-image
selection. The bracketed-paste handler in `CustomEditor.handleInput`
already iterates every extracted image path; the keybind path now does
the same. Mixed selections (one .pdf + two images) still attach all
images and skip the non-images.
Tests cover (a) multi-image selection (3 attached), (b) mixed selection
with the file-URL fallback owning the outcome (text fallback MUST NOT
run when at least one file URL was an image).
Refs #3506
Reviewer caught: `extractImagePathFromText` reused the bracketed-paste
splitter, which treats unescaped spaces as separators. macOS screenshot
filenames default to names like
`/Users/me/Desktop/Screenshot 2026-06-25 at 1.23.45 PM.png` — the
splitter shredded those into 5 segments, the second segment failed the
explicit-path check, and the helper returned undefined, so the keybind
fallback pasted the path verbatim instead of attaching the image.
Add a whole-text-as-path stage gated on a new ABSOLUTE_PATH_PREFIX_REGEX
(matches `/`, `~/`, `file://`, `\\`, or a drive letter), used
only when the splitter found nothing (otherwise multi-path text like
`/tmp/a.png /tmp/b.png` would be mis-joined). Prose containing a
path-shaped fragment ("see /tmp/x.png") fails both passes and still
pastes as text.
Tests cover (a) macOS screenshot names with spaces, (b) ~/Pictures and
Windows paths with spaces, (c) anchored prose fragments not hijacking
the fallback, and (d) end-to-end real-file integration via
handleImagePaste.
Refs #3506
Reviewer caught (correctly) that the #3506 text fallback relied on
`clipboard.readText()`, which on Darwin shells out to `pbpaste(1)` —
pbpaste only surfaces plain text / RTF / EPS, so a Finder Cmd+C
pasteboard (`public.file-url` only, no plain text, no raw image bytes)
made readText() return empty and the new path-detection never ran.
Add a Darwin-only `readMacFileUrlsFromClipboard` helper that pipes a
small AppleScript through `osascript -` to coerce the pasteboard via
`«class furl»` and emit POSIX paths one per line. Wire it into
`InputController.handleImagePaste` between the readImage and readText
calls; the first image-shaped path routes through
`handleImagePathPaste`, non-image file URLs (e.g. a copied .pdf) fall
through to the existing text fallback. The clipboard interface field is
optional so existing test fixtures keep working without changes.
Tests: covers (a) Darwin file-URL pasteboard with empty pbpaste,
(b) non-image file URLs falling through to text, (c) the helper itself
on darwin/non-darwin and when osascript fails.
Refs #3506
When the macOS pasteboard's text representation forwards a
`file:///Users/.../img.png` URL (Ghostty/iTerm2/etc. forwarding the
`public.file-url` representation after a Finder copy), the smart
bracketed-paste / keybind fallback recognized it as a path but
`loadImageInput` then tried to read a literal `file://` path and
failed. `normalizePastedPath` now decodes `file://` URLs via
`node:url.fileURLToPath` before the explicit-path check, mirroring
Codex's `normalize_pasted_path` in
`codex-rs/tui/src/clipboard_paste.rs`. Both the bracketed-paste path
and the new `extractImagePathFromText` keybind path benefit.
Refs #3506
When the clipboard exposes only a file URL for an image (e.g. Finder
`Cmd+C` on a `.png`, certain screenshot tools), arboard's
`get_image()` returns `ContentNotAvailable`. `handleImagePaste` then
fell through to the #1628 smart-paste text fallback and pasted the path
verbatim, while the terminal-mediated paste round-tripped through
bracketed-paste's `extractBracketedImagePastePaths` and attached the
image — producing the asymmetric "for image I need control+v which is
very odd" symptom on macOS.
Refactor `custom-editor.ts` to share the bracketed-paste path-detection
logic via a new `extractImagePathFromText` export, then route the text
fallback through `handleImagePathPaste` whenever the clipboard text is
exactly one explicit image file path. Both keybind- and terminal-mediated
paste now agree.
Fixes#3506
Review on PR #3503 caught the last provenance edge case: some servers can
explicitly advertise an origin-only resource equal to the authorization-server
origin. That value is still authoritative provider metadata and must be sent;
only OMP-synthesized fallback resources should be stripped.
- `filterResourceIndicator` now strips same-origin values only when `stripSameOriginResource` is set. Provider-advertised `oauth.resource` and authorization-URL `?resource=` values preserve both origin-only and path-scoped forms.
- Updated grant tests to preserve advertised origin resources, trailing-slash origin resources, and URL-embedded origin resources while still stripping fallback origin/path resources for Plane.
- Updated refresh tests to preserve advertised origin resources and strip only fallback origin/path resources.
- Updated changelog wording to describe fallback-only stripping.
Fixes#3502
Review on PR #3503 caught one remaining provenance hole: a provider can embed a
path-scoped same-host `resource` directly in the authorization URL while
`oauth.resource` remains undefined. The controller marks its separate
`config.url` resource as fallback, but the URL-embedded parameter is still
provider-authored and must not inherit the fallback same-origin stripping
policy.
- `generateAuthUrl` now filters an existing `?resource=` from the authorization URL with the path-preserving default, regardless of `stripSameOriginResource` on the caller-supplied fallback resource.
- Added a regression that simulates `authorize?resource=https://gateway.example.com/svc/mcp` plus fallback `resource=https://gateway.example.com`; the authorize URL, `flow.resource`, and token request all preserve `/svc/mcp`.
- Updated the changelog to explicitly mention authorization-URL-embedded path resources.
Fixes#3502
Review on PR #3503 caught that the broad same-origin filter broke valid
protected-resource discovery shapes such as
`https://gateway.example.com/my-service/mcp`: same host as the authorization
server, but a distinct MCP service identified by path. Those advertised
resources must be preserved for audience selection.
- Replaced the unconditional same-origin filter with a provenance-aware policy: exact auth-server-origin resources are always stripped, path-scoped same-origin resources are preserved by default, and only OMP-synthesized fallback resources opt into same-origin path stripping.
- Added `stripSameOriginResource` to `MCPOAuthConfig` and `RefreshMCPOAuthTokenOptions`; quick-add/reauth set it only when the resource came from `config.url` / `runtimeBaseConfig.url` fallback rather than `oauth.resource` or an existing auth resource.
- Refresh uses the same flag when `MCPManager.prepareConfig` falls back to `config.url`, and no longer persists fallback resources into the credential as if they were provider-advertised material.
- Updated RFC 8707 tests to cover both sides: gateway path resource preserved, Plane-style fallback `/http/mcp` stripped, refresh path mirrors the same distinction.
Fixes#3502
With `autolearn.autoContinue` on, the controller fires a synthetic
turn whose only user-role payload is `autolearn-nudge.md`. The old
prompt opened with "Before you finish:" and gave no terminal
contract, so after the `learn`/`manage_skill` call the agent read
its own unanswered prior question (e.g. "Want me to commit and
push?") as accepted and continued — pushing commits, running tools,
etc. — without the user ever answering.
Split the nudge into two prompts and pick at fire time:
- passive (rides the user's real next message) keeps additive
framing — "answer the user normally; the capture is in addition
to, not a replacement for, the work the user just asked for".
- auto-continue (`autolearn-nudge-autocontinue.md`) is explicitly
terminal — "not a user reply; do not treat this as approval or
acceptance of any pending action; capture, then stop; do not run
other tools, resume prior work, or answer your own pending
questions; wait for the user's next prompt".
Attribution stays `user` so llama.cpp keeps reusing the warm
prefix (#3456). Regression tests pin the load-bearing terminal
language in both branches.
Fixes#3504
Plane also rejects `resource=https://mcp.plane.so/http/mcp`, not only the bare
origin forms. That means the MCP OAuth resource filter must treat any resource
URL on the authorization-server origin as redundant for these MCP servers, not
just exact origin/origin-slash values.
- Broadened the filter to compare `new URL(resource).origin` with the persisted authorization-server origin.
- Updated grant and refresh RFC 8707 tests: same-origin path resources such as `/http/mcp` are now stripped from authorize, token exchange, and refresh; cross-origin resources remain preserved.
- Updated docs/changelog wording from exact self-referential origin to same-origin resource indicators.
Verified live against `https://mcp.plane.so/authorize`: patched `MCPOAuthFlow` with `resource=https://mcp.plane.so/http/mcp` generates no `resource` parameter and Plane redirects to `/consent?txn_id=…`.
Fixes#3502