Commit Graph

18651 Commits

Author SHA1 Message Date
roboomp 985e4ad515 fix(commit): preserve binary patch terminators in split-commit round-trip
parseFileDiffs split the captured `git diff --cached --binary` on
"
diff --git ", consuming the newline that terminates each file block, and
patch.join ended with `.replace(/\n+$/, "")`. Both dropped the blank line
that terminates a `GIT binary patch` block, so rebuilding a split-commit
patch produced a corrupt binary patch rejected by `git apply --binary`.

Split on a line-start lookahead so blocks keep their terminators verbatim,
and concatenate join parts without stripping trailing newlines. Both
trailing and mid-diff binary blocks now round-trip byte-exact.

Fixes #8899
2026-08-19 10:08:40 +00:00
can1357 5d18fd814d chore(changelog): normalized entries and added /mcp reauth OAuth discovery note
Covers merged PR #8989, whose branch omitted a CHANGELOG entry (issue #8922).
2026-08-19 12:00:15 +02:00
can1357 a349650bac Merge PR #8990: fix(tui): scroll the /model Roles view so clipped rows stay reachable (@roboomp) 2026-08-19 11:59:55 +02:00
can1357 f2e11a3d72 Merge PR #8989: fix(mcp): run oauth discovery on reauth when handshake needs no auth (@roboomp) 2026-08-19 11:59:55 +02:00
can1357 46c019fd6c Merge PR #8988: fix(catalog): collapse Cursor Grok 4.5/4.6 effort siblings (@roboomp) 2026-08-19 11:59:55 +02:00
roboomp 8fc4555914 fix(catalog): recover gmi-cloud model params from canonical index
GMI Cloud's /v1/models returns only bare {id} rows, so dynamic discovery
resolved every model except the single bundled DeepSeek-V4-Flash seed with
a null context window, zero pricing, and no reasoning/thinking config.

Give the gmi-cloud mapper the same cross-provider canonical fallback that
SiliconFlow uses for the identical open-weight models: recover context
window, output limit, reasoning, and thinking ladder from the bundled
reference index while never borrowing another provider's pricing.

Fixes #8890
2026-08-19 09:57:19 +00:00
roboomp 8ea64a6a8d fix(mcp): run oauth discovery on reauth when handshake needs no auth
`/mcp reauth <name>` probed the server with `{ oauth: false }` and treated a
successful unauthenticated `initialize` as proof that OAuth was unnecessary,
hard-erroring with "Server connection succeeded without OAuth; reauthorization
is not required." Per the MCP spec a server may allow unauthenticated
`initialize` while requiring a bearer token for `tools/call`, so this left no
way to acquire a credential for such servers.

When the handshake succeeds without an in-band tool challenge, fall back to
`discoverOAuthEndpoints(config.url)` and proceed with the flow if the server
advertises OAuth metadata; only refuse when no OAuth endpoint is discoverable.

Fixes #8922
2026-08-19 09:53:26 +00:00
can1357 3a01e97953 chore(changelog): normalized catalog entries after merges 2026-08-19 11:53:17 +02:00
can1357 a720b8d6ac fix(coding-agent): bound TinyFish locale regex to whole subtags
Unanchored regex mapped BCP-47 script subtags to bogus regions
(lang:zh-hans -> location=HA) and prefix-matched 3+ letter codes
(lang:eng -> language=en). Require a subtag boundary, matching the
Perplexity provider's parsing.
2026-08-19 11:52:55 +02:00
can1357 d61c33349a Merge branch farm/5a737841/tinyfish-honor-lang-directive: fix(coding-agent): honor lang: directive in TinyFish search (@roboomp) 2026-08-19 11:52:55 +02:00
can1357 0253c85026 Merge PR #8985: fix(sdk): thread response model into after_provider_response context (@roboomp) 2026-08-19 11:52:55 +02:00
can1357 e966b4aa4d Merge PR #8983: fix(mcp): refresh broker-backed MCP OAuth credentials (@roboomp) 2026-08-19 11:52:54 +02:00
can1357 45d8bca2ee Merge PR #8982: fix(ai): serve IPv4-only OAuth callback when IPv6 is disabled (@roboomp) 2026-08-19 11:52:54 +02:00
can1357 d6d7e0f56c Merge PR #8981: fix(catalog): route copilot grok-4.6 through responses api (@roboomp) 2026-08-19 11:52:54 +02:00
can1357 3319e8bbad Merge PR #8980: fix(catalog): route opencode-go muse-spark to responses api (@roboomp) 2026-08-19 11:52:54 +02:00
can1357 8557366401 Merge PR #8979: fix(coding-agent): paint optimistic row for idle /skill submits (@roboomp) 2026-08-19 11:52:54 +02:00
can1357 9ffc0b2a17 Merge PR #8978: fix(compaction): reject stale pre-compaction anchor in context breakdown (@roboomp) 2026-08-19 11:52:53 +02:00
can1357 43f7b09348 Merge PR #8977: fix(tui): honor tui.input.submit remap onto ctrl+enter (@roboomp) 2026-08-19 11:52:53 +02:00
can1357 b4c04ef0b7 Merge PR #8976: fix(commit): fail loudly when staged binary truncates split-commit diff (@roboomp) 2026-08-19 11:52:53 +02:00
can1357 31b97de9f4 Merge PR #8975: fix(catalog): self-heal a corrupt models.db model cache (@roboomp) 2026-08-19 11:52:53 +02:00
can1357 53a7da5e4c Merge PR #8973: fix(ai): surface litellm concurrency-admission 429 immediately (@roboomp) 2026-08-19 11:52:53 +02:00
can1357 0b20c251eb Merge PR #8970: fix(tui): pin anchored regions under an unpinned streaming seam (@roboomp) 2026-08-19 11:52:52 +02:00
can1357 17faeb0cda Merge PR #8968: fix(ai): hoist assistant message interleaved in responses tool batch (@roboomp) 2026-08-19 11:52:52 +02:00
can1357 9a8b0ef80a Merge PR #8967: docs: clarify bash.patterns gates the bash tool only, not eval (@roboomp) 2026-08-19 11:52:52 +02:00
can1357 764afb8cbb Merge PR #8966: fix(task): initialize extension runtime on subagent revival (@roboomp) 2026-08-19 11:52:52 +02:00
roboomp 466f769cc8 fix(catalog): collapse Cursor Grok 4.5/4.6 effort siblings
Cursor advertises Grok 4.5/4.6 as per-effort sibling ids
(cursor-grok-4.6-low|-medium|-high|-xhigh plus -fast variants), but
VARIANT_COLLAPSE_TABLES had no cursor entry, so the model hub showed 14
unrouted siblings instead of one logical model with effort routing.
GetUsableModels ships no thinkingDetails and the bundled references read
reasoning:false, so the picker also treated them as non-reasoning.

- Add CURSOR_VARIANT_COLLAPSE_TABLE folding each service-tier lane
  (standard + -fast) into one logical model with effort routing onto the
  live wire ids, mirroring Devin's grok-4-5 collapse.
- Rename the generic devinTierFamily/DevinTierRoutes helper to
  tierFamily/TierRoutes now that both Devin and Cursor tables use it.
- Mark versioned cursor-grok-<version> ids as reasoning during discovery
  (grok-code-* coding models stay non-reasoning).

Fixes #8803
2026-08-19 09:52:51 +00:00
roboomp aed63e7bd5 fix(tui): scroll the /model Roles view so clipped rows stay reachable
The Roles panel renderer looped from the first row with a hard height cap and no scroll offset, so roles and model-keyed fallback chains past the visible height were never drawn and unreachable by keyboard, with no truncation indicator. Unlike the sibling provider list (model-browser windows via #windowStart/#ensureSelectedVisible), the Roles panel had no equivalent.

Window #renderRolesView around #roleIndex via #ensureRoleVisible, offset mouse hit-testing by the scroll start bounded to the visible count, and draw an up/down '+N more' hint when the list is clipped.

Fixes #8817
2026-08-19 09:52:41 +00:00
roboomp 78ef6805f3 fix(sdk): thread response model into after_provider_response context
ExtensionRunner.emitAfterProviderResponse accepted the response model but
discarded it, calling createContext() with no model. Response-scoped hooks
therefore saw the primary session model in ctx.model and ctx.models.current()
even when the response came from a cross-provider side request, so an extension
that revokes a credential on an HTTP 402 could target the wrong provider.

Call createContext(model) to match emitBeforeProviderRequest, plus a regression
test asserting both fields expose the response model.

Fixes #8955
2026-08-19 09:44:22 +00:00
roboomp 7150f122f5 fix(coding-agent): honor lang: directive in TinyFish search
The shared query pipeline parses a lang:/language: directive into StructuredQuery.lang, which sibling providers (DuckDuckGo, Perplexity, SearXNG) map onto their native locale params. The TinyFish provider dropped parsed.lang entirely, so every request fell back to the API's US/English default and non-US locales were silently lost.

Map parsed.lang onto TinyFish location (ISO 3166-1 alpha-2) and language (ISO 639-1): lang:it-it yields location=IT&language=it, lang:it yields language=it only. Behaviour is unchanged when no locale directive is present.

Fixes #8913
2026-08-19 09:43:54 +00:00
roboomp 9cc881ce5b fix(mcp): refresh broker-backed MCP OAuth credentials
Remote OAuth MCP servers dropped out of /mcp under `omp auth-broker
serve` once their access token expired: neither the client nor the
broker could complete the refresh.

- Client: the MCP manager threw on the broker-redacted refresh sentinel
  (REMOTE_REFRESH_SENTINEL) instead of asking the broker to refresh. It
  now routes redacted MCP refreshes through
  AuthStorage.forceRefreshCredentialById, which calls back to the broker
  (the real refresh token never leaves the broker host).
- Broker: the serve process had no mcp_oauth:* refresh path, so
  POST /v1/credential/:id/refresh answered "Unknown OAuth provider". Its
  AuthStorage is now built with a refreshOAuthCredential override that
  refreshes MCP credentials with a generic refresh_token grant from the
  credential's embedded token endpoint and client id. The background
  refresher keeps MCP tokens live through the same path.

Extract shared refreshManagedMcpOAuthCredential and
mcpOAuthServerUrlFromCredentialId helpers so both paths use identical
refresh material selection and RFC 8707 fallback-resource logic.

Fixes #8933
2026-08-19 09:34:51 +00:00
roboomp 8ae547091f fix(ai): serve ipv4-only oauth callback when ipv6 is disabled
The `::1` companion listener added in #8081 cannot bind on hosts with
IPv6 disabled at the kernel (ipv6.disable=1). Bun reports that failure
with its generic "Is port X in use?" message (oven-sh/bun#7187), which
isAddressInUse misread as a real collision, tearing down the healthy
IPv4 listener and throwing a bogus "port 1455 is in use"
ConfigurationError that blocked Codex login.

#createServer now probes os.networkInterfaces() for an internal IPv6
loopback up front and serves IPv4 alone when none exists, instead of
relying on Bun error classification the message ambiguity defeats.

Fixes #8814
2026-08-19 09:33:46 +00:00
roboomp 565d09b13a fix(catalog): route copilot grok-4.6 through responses api
GitHub Copilot serves grok-4.6 / grok-4.6-1m only via /responses, but
isCopilotResponsesModelId matched grok-4.5 exactly, so both the static
generator and dynamic discovery classified grok-4.6 as openai-completions
and requests 400d with unsupported_api_for_model.

- match grok-4.6 in isCopilotResponsesModelId
- add grok-4.6 / grok-4.6-1m to COPILOT_CACHE_INVALIDATED_MODEL_IDS so
  stale cached completion routes drop on refresh
- regenerate github-copilot/grok-4.6 to api openai-responses (compat
  block dropped, xhigh effort added by the responses policy)
- cover discovery routing and cache migration in tests

Fixes #8807
2026-08-19 09:27:16 +00:00
roboomp 1b65e471fb fix(catalog): route opencode-go muse-spark to responses api
The OpenCode Go gateway serves muse-spark-1.2 and
muse-spark-1.2-contributor only at /zen/go/v1/responses, but the
/zen/go/v1/models discovery omits the provider.npm hint, so the
resolver fell through to openai-completions. The completions parser
then closed the stream without a finish_reason on every tool-call turn.

Pin both ids to openai-responses in OPENCODE_GO_API_RESOLUTION, mirroring
the existing deepseek-v4-flash override, and add a resolver regression.

Fixes #8957
2026-08-19 09:24:54 +00:00
roboomp 0808226ca3 fix(coding-agent): paint optimistic row for idle /skill submits
InputController.#invokeSkillCommand cleared the draft and awaited the full
promptCustomMessage dispatch with no transcript render. AgentSession.#promptWithMessage
runs awaited preflight (memory recall, before_agent_start hooks, auto-thinking
classification, pre-prompt compaction) before the message reaches the agent, so a slow
step such as a Hindsight auto-recall timeout left the composer cleared with no pending
row, unlike a normal prompt's optimistic row from startPendingSubmission.

Idle skill submissions now paint an optimistic skill row before the awaited dispatch;
the canonical message_start reconciles it in place via EventController instead of
appending a duplicate. Streaming submissions still queue and show their chip.

Fixes #8895
2026-08-19 09:19:47 +00:00
roboomp e6c0cf90a4 fix(compaction): reject stale pre-compaction anchor in context breakdown
getContextBreakdown used message position (anchorIndex >= pending.cutoffCount) as a proxy for usage freshness. After a mid-run compaction rebased the in-flight snapshot, an in-flight provider response whose request predated the compaction landed past the rebase cutoff carrying pre-compaction usage, so it out-ranked the rebased estimate and reported the pre-compaction token count (~2.6x the real one). That phantom overflow tripped the "freed too little context to make progress" guard and drove the frame-rescue path on a byte-identical tokensBefore.

Assistant context snapshots now carry a monotonic compaction epoch, bumped in rebaseAfterCompaction and stamped at message-record time. A post-cutoff anchor whose epoch predates the pending snapshot's epoch is no longer trusted over the rebased estimate.

Fixes #8887
2026-08-19 09:13:33 +00:00
roboomp caace28295 fix(tui): honor tui.input.submit remap onto ctrl+enter
The multiline editor's key dispatch checked a hardcoded Ctrl/Shift+Enter
-> newline branch before the config-driven tui.input.submit branch, so a
user remap of submit onto Ctrl+Enter was swallowed as a newline and never
submitted. Gate the hardcoded newline fallbacks behind an explicit submit
binding; the bare-LF (iTerm2 Shift+Enter) case stays exempt because its
canonical form is indistinguishable from plain Enter.

Fixes #8906
2026-08-19 09:10:10 +00:00
roboomp 6996b36f4a fix(commit): fail loudly when staged binary truncates split-commit diff
Split-commit captured the staged diff with `git diff --cached --binary`,
whose stdout is hard-capped at GIT_COMMAND_OUTPUT_LIMIT_BYTES (8 MiB) by
readCappedText. A single large binary (base85-encoded inline) crossed the
cap; the capture was truncated silently, so files sorting after the binary
were absent from the parsed diff and stage.hunks threw a misleading
`No diff found for <path>` naming an innocent file.

Surface truncation as GitCommandResult.truncated, add a requireComplete
diff option that throws the new GitOutputTruncatedError instead of
returning a silently truncated diff, and have runSplitCommit request a
complete diff and abort with a clear message pointing at the real cause.

Fixes #8897
2026-08-19 09:07:29 +00:00
roboomp 289cc19325 fix(catalog): self-heal a corrupt models.db model cache
The shared SQLite model cache wrapped every read/write in a blanket catch that swallowed unrecoverable SQLITE_CORRUPT*/SQLITE_NOTADB failures as best-effort misses, and getSharedDb cached the broken handle. A physically corrupt models.db therefore permanently disabled cached catalogs across processes: a successful live discovery could never overwrite the corrupt cache, so a runtime extension with no bundled catalog was stuck with only its bootstrap model.

On those unrecoverable codes the cache now self-heals: close the handle, quarantine models.db(+-wal/-shm) to models.db.corrupt-<ts>, recreate a fresh database, and retry the operation once. SQLITE_BUSY, permission, and unrelated errors keep their existing best-effort paths. The SQLITE_BUSY/corruption classifiers moved to @oh-my-pi/pi-utils so the credential store and model cache share one implementation.

Fixes #8867
2026-08-19 09:00:07 +00:00
roboomp 3d844bf3b2 fix(ai): surface litellm concurrency-admission 429 immediately
The OpenAI-wire transport called fetchWithRetry with maxAttempts: 6 and
the default 60s maxDelayMs cap, so a LiteLLM max_parallel_requests
rejection (HTTP 429, Retry-After: 60) was slept-and-retried up to six
times before TurnRecovery ever saw it. A 60s hint equals the cap, so
fetchWithRetry never bailed early and one turn could stall ~300s,
bypassing the user's retry.maxDelayMs/maxRetries and the session-level
CONCURRENT_LIMIT backoff + model fallback.

postOpenAIStream now opts out of transport-level retry for this
concurrency-admission response class via fetchWithRetry's shouldRetryResponse
gate, detecting the rate_limit_type=max_parallel_requests marker in the
response header or structured body. The 429 surfaces on the first attempt
so session recovery owns retry/fallback. Genuine RPM/quota 429s carry no
such marker and keep honoring Retry-After.

Fixes #8854
2026-08-19 08:55:17 +00:00
roboomp 7cff20cfd0 fix(tui): pin anchored regions under an unpinned streaming seam
TUI.render merged live-region seams with a topmost-seam-wins rule that
adopted only that seam's pin policy for the whole frame. While the primary
turn streams, the transcript reports the topmost, unpinned seam, so the
frame-wide pin flag becomes false and a pinned AnchoredLiveContainer below
it (the /btw panel, the working HUD) loses its pinning: once its content
grows past the viewport, the emit path commits the scrolled-off rows as
frozen snapshots on every growth frame, piling duplicates into native
scrollback.

Track a pinnedBoundary (start row of the topmost pinned region)
independently of which seam wins the topmost merge, and cap every commit
ceiling at it. Equivalent to the prior behavior for a fully-pinned frame
and for a frame with no pinned region; only the mixed case changes.

Fixes #8793
2026-08-19 08:51:13 +00:00
roboomp 4b07f409f6 fix(ai): hoist assistant message interleaved in responses tool batch
opencode-go's Console Go gateway rejects Responses input where an assistant message sits between a function_call batch and its function_call_output items, 400ing with "No tool output found for tool call ..." and permanently poisoning the session in history. This happens whenever a model streams a trailing text/demoted-thinking block after its tool calls: the block-encode path preserves stream order, emitting the message between the calls and the outputs appended afterward.

buildResponsesInput and buildOpenAiNativeHistory now hoist such interleaved assistant messages ahead of their call batch (canonical message(s) -> calls -> outputs); content is unchanged. OpenAI's Responses API is order-tolerant so this is a no-op there.

Fixes #8789
2026-08-19 08:46:28 +00:00
roboomp a0b7ca6708 docs: clarify bash.patterns gates bash tool only, not eval
bash.patterns only feeds the bash tool's approval decision. The eval
tool declares the exec tier and can spawn a shell via subprocess, so a
deny rule there does nothing for the same command run through eval;
under yolo the exec call resolves to allow. Note the scope and point at
tools.approval.eval as the lever that closes the path in
bash-tool-runtime.md, approval-mode.md, and settings.md.

Fixes #8838
2026-08-19 08:46:21 +00:00
roboomp 3acc57de8c fix(task): initialize extension runtime on subagent revival
Both subagent revivers rebuilt the session but never wired the extension
runtime, leaving it pre-init where every action method throws
ExtensionRuntimeNotInitializedError. An extension with a tool_call handler
touching a runtime action then tripped the fail-closed gate in emitToolCall
and blocked every tool, including the hidden yield, so the revived agent
could neither finish nor exit and looped until killed.

Both the warm lifecycle reviver (executor.ts) and the cold persisted
reviver (persisted-revive.ts) now call the shared initializeExtensions
helper on the rebuilt session, restoring runtime actions, onError, and the
session_start event.

Fixes #8824
2026-08-19 08:44:55 +00:00
can1357 c5642a5da1 config(python/robomp): corrected agent filename mount path in docker compose
- Updated the agent configuration mount to correctly map AGENTS.md instead of AGENT.md.
2026-08-19 10:24:35 +02:00
can1357 d94bdfa1bb test: hardened new spinner and title-latch suites against full-suite pollution
- Exported stopSharedSpinnerTicker() and wired it into InteractiveMode.stop(): a live block missed by per-component stopAnimation kept the shared 80ms interval alive as a lingering event-loop handle; the spinner suite uses it to observe a freshly armed ticker instead of one leaked by earlier files
- Title-disposal tests now save/clear/restore PI_NO_TITLE (main() in ACP/RPC mode sets it process-wide), matching the prewarm and orphan-submit precedent
2026-08-19 03:20:50 +02:00
can1357 aa98ea9ed5 test: settled in-flight auto-title request between orphan-submit iterations
The title latch from PR #8911 dedupes a second title start while one is in flight; the orphan-submit loop implicitly relied on the first mocked request having settled. Drain its promise chain at a macrotask boundary before the next submit.
2026-08-19 02:04:51 +02:00
can1357 9bc810b36c style: cargo fmt for snapcompact zero-glyph regression assert 2026-08-19 01:48:19 +02:00
can1357 3566bd9b41 fix(ai): unified Cursor interaction-query handling after merging #8889 and #8830
- Kept the shared cursor/interaction-query module as the single handler and deleted the duplicate local implementation in cursor.ts
- Added the named webFetchRequestQuery approval case (field 9 is named under the regenerated proto)
- Preserved the deliberate no-fake-VM-success semantics for setupVmEnvironmentArgs (review of #8047)
- Updated the field-9 regression test to assert the named decode of the raw same-field reply, which also pins the LEN-prefix wire framing
2026-08-19 01:47:20 +02:00
can1357 20bd4ab97b chore(changelog): normalized [Unreleased] sections after merges and added missing entries
- Repaired union-merge artifacts in packages/coding-agent/CHANGELOG.md (duplicated 17.3.6/17.3.7 blocks; promoted the new entries back to [Unreleased])
- Added missing [Unreleased] entries for PRs #8833, #8866, #8879, #8903, #8905, #8915, #8916, #8917, #8920, #8923, #8928, #8929, #8937
2026-08-19 01:42:50 +02:00
can1357 e61775a470 fix(coding-agent): mention oauth exemption in apiKey validation error; add validation tests 2026-08-19 01:39:18 +02:00