mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-11 23:53:19 -10:00
8f7e4a0748094de92aa17e534bf54839b2cecaab
93
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2f0e350dc7 |
Remove the retired Windows sandbox backend
Windows no longer enforces an OS-level shell sandbox, so the restricted-token and AppContainer backend in internal/winsandbox, the hidden helper subcommand dispatched by the CLI and Desktop entry points, the helper report channel, and the Spec fields only that backend read are deleted. Callers that classified helper failures or issued runner-authorization denial hints are simplified to the ordinary process-launch outcomes. The one part older installs still depend on moves to a leaf package, internal/winaclresidue: the conservative legacy credential DENY RX repair and a sweep of the crash-residue markers that v1.38.8 through v1.38.10 wrote before mutating ACLs. The sweep now runs in the background at process start instead of only before the next sandboxed command, and a marker is deleted only after every recorded ACE removal succeeded so a failed cleanup keeps its evidence. The repair no longer takes the sandbox root lock; live-owner markers still veto it. CI drops the sandbox wait knob and points the Windows credential ACL identity step at the new package. The Windows sandbox architecture document, its evidence directory, and the native verification script are removed with the backend. |
||
|
|
47ecb44df5 |
fix(doctor): adopt registry regressions and scope MCP aliases
Problem: Skill diagnostics could accept local MCP aliases that runtime preparation does not bind, or report another plugin's tools as ambiguous. Root cause: Diagnostic discovery omitted plugin ownership and reconstructed bindings from display names instead of preserving adapter identities. Fix: Share the runtime ownership filter, retain canonical adapter bindings, and preserve plugin discovery metadata. Adapt PR #9686's registered-builtin and unknown-name regressions with isolated registration. Remove dead code and use the standard HTTP status constant to satisfy lint. Verification: Root tests, affected-package race tests, full golangci-lint, repolint, and whitespace checks pass. Tests exercise real skill preparation, subagent tool scope, plugin ownership, and prefix-transformed metadata. Refs #9686 Co-authored-by: dadgrand <[email protected]> |
||
|
|
22291d4e78 |
feat(agent): land cache-stable use_capability speed and quality controls / 落地缓存稳定的能力代理速度与质量治理 (#9569)
* feat(agent): land cache-stable use_capability speed and quality controls Problem: MCP discovery and review loops inflate tools/list traffic, schema bytes, and automatic architecture review. The 1.33.0 speed-and-quality plan keeps a fixed use_capability proxy, but host validation, parallel read-only dispatch, stop-loss, child review budgets, and observability were incomplete. Root cause: The proxy schema was cache-stable, yet argument validation, scheduling class, review triggers, child sampling caps, facts packs, and list-layer metrics were not wired through. Architecture review fired too broadly, and the review 2048-token cap never reached child provider sampling. Fix: - Validate arguments against the real target schema before MCP tools/call. - Classify list/search/inspect and connected read-only MCP as parallel. - Fail closed when schema generation or safety class changes after scheduling. - Narrow auto independent review to architecture-worthy writes and cap at one first review plus one re-review. - Stamp review children with 8 steps / 2048 output tokens and a parent facts pack, without copying the parent transcript. - Record tool/phase/MCP list metrics and export three-layer list sources. - Keep first-party Tool Search preview plumbing default-off. Verification: - go test ./internal/agent/ ./internal/plugin/ ./internal/taskcontract/ ./internal/cli/ ./internal/tool/ ./internal/boot/ ./internal/eval/replay/ - go test -race on parallel MCP and listChanged tests - go vet ./internal/agent/ ./internal/plugin/ ./internal/cli/ - go run ./tools/repolint * fix(agent): harden capability speed controls Problem - Capability retries, proxy classification, and loop guards could duplicate canceled work, serialize safe reads, or repeat failed decisions. - MCP attribution, review context, native tool search, output validation, and desktop export wiring had correctness and CI gaps. Root cause - Wrapper layers dropped resolved metadata, per-turn state used cumulative or non-atomic accounting, and process-only metrics could not explain session behavior. - Provider support was inferred from names, while two same-stem export modules made TypeScript resolve the wrong implementation. Fix - Fail closed on cancellation and dynamic classification, bound reviews and repeated failures, validate output schemas, index discovery once, and emit session-scoped MCP list observations. - Gate native tool search by concrete first-party Responses capability, preserve host validation, and split lightweight export data from raster renderers. - Add deterministic concurrency, decision, dedupe, Windows path, frontend contract, and wire-format coverage. Verification - go test ./... -count=1 - go test -race ./internal/agent ./internal/plugin -count=1 - golangci-lint run ./... (darwin, linux, windows) - pnpm build - node scripts/run-tests.mjs (236 suites) * refactor(boot): preserve repository lint budgets Problem - The capability review wiring increased line and complexity counts in two already-baselined large files, so the repository lint gate rejected the otherwise clean change. Root cause - Review context preparation and observer binding were inlined at high-complexity call sites. Fix - Move review option preparation into the focused boot helper module and combine capability observer setup behind one call. - Preserve behavior while returning agent.go and boot.go to their prior lint budgets. Verification - go run ./tools/repolint - go test ./internal/agent ./internal/boot -count=1 - golangci-lint run ./... * fix(boot): remove ineffectual review budget result Problem Linux CI reports two ineffectual assignments when review subagent options are prepared. Root cause The extracted helper returned the adjusted step count after it had already embedded that value in agent.Options, while callers never read the returned count again. Fix Narrow the helper contract to return only the rewritten task and completed options, and update both call sites. Verification go test ./internal/boot -count=1 go run ./tools/repolint golangci-lint run --timeout=5m ./... * fix(agent): close speed and quality review gaps Problem: PR #9569 still allowed invalid proxy arguments to reach hooks or remote tools, attributed MCP list activity imprecisely, repeated equivalent decisions and outputs, and used a synthetic performance fixture as release evidence.\n\nRoot cause: Validation, dispatch telemetry, retry semantics, discovery attribution, loop guards, and release qualification were implemented at separate abstraction layers without a single resolved-tool contract.\n\nFix: Validate resolved tools before hooks and dispatch, preserve exact remote-dispatch telemetry, reject MCP application errors from transport retries, deduplicate decisions and outputs, bound inspect responses, attribute live MCP list changes to active sessions, enforce rolling soft budgets, and add a live paired-run release gate.\n\nVerification: go test ./...; targeted Go race tests; go vet ./...; golangci-lint; repolint; desktop and SDK Go tests; frontend build; all 239 frontend suites. * test(desktop): follow composer measurement mirror Problem: The latest main-v2 composer autosize change made the full frontend suite fail seven run-strip height assertions. Root cause: The regression test still mocked scrollHeight on the live textarea even though production now measures an out-of-flow mirror. Fix: Mock the measurement mirror while continuing to assert the live textarea and card geometry. Verification: The focused composer tests and all 239 frontend suites pass; the production build and bundle budgets also pass. * test(desktop): stabilize native tail fixture Problem: The Windows WebView2 smoke failed before its composer assertions when the initial history already exceeded the target row count. Root cause: The prepared multiline draft could shrink the native viewport one paint late while logical ownership remained tail-follow, leaving no jump-bottom control for fixture setup. Fix: Normalize only the logical tail-follow setup to the physical tail, continue requiring the product control in manual-reader mode, and verify the loaded tail after recovery. Verification: node syntax check; desktop Go tests; repolint; local native fixture passed the modified setup and reached native-event sampling. * fix(desktop): preserve tail during hydration resize WebView2 could keep logical tail-follow while a restored multiline draft shrank the transcript during history hydration, leaving the physical viewport 37-74px above the tail. Route those viewport resize events through the existing tail controller only while tail-follow ownership is active. Manual readers remain untouched, and the native smoke now verifies product behavior without a fixture-owned scroll write. Verified with the frontend build, all 239 frontend suites, transcript and composer suites, desktop Go tests, native contract syntax, repolint, and diff checks. * fix(desktop): repair hydrating footer tail gaps The Windows matrix confirmed the restored multiline draft changes the App footer while transcript hydration is active. The footer-height effect discarded that event, and the ResizeObserver could be recreated after the height change, leaving logical tail-follow 37px above the physical tail. Allow footer resize repairs whenever the transcript still owns tail-follow, including during hydration, while continuing to block manual readers. Use the canonical scroll mode for the viewport fallback as well. Verified with transcript and composer suites, frontend build, desktop Go tests, native contract syntax, repolint, and diff checks. * test(boot): isolate provider prefix effects * fix(desktop): pin composer footer tail before paint * fix(desktop): settle staged native footer growth * test(boot): fence history index cleanup * fix(desktop): make transcript tail physically reachable * fix(desktop): use painted WebView2 viewport extent * fix(desktop): learn reachable WebView2 transcript tail * fix(desktop): retain asynchronous WebView2 tail evidence * fix(agent): preserve MCP concurrency policy at runtime Problem: Configured MCP serial/parallel policy disappeared when the session runtime copied plugin entries, allowing explicitly serial stateful servers to be classified and executed concurrently. Root cause: runtimePluginEntry retained only name, source, and auto-start metadata, while both batch classification and the execution gate read Concurrency from that reduced entry. Fix: Normalize and retain Concurrency in the runtime entry. Add regression coverage through both ConfigureServers and UpsertServer so initial assembly and hot updates preserve explicit serial and parallel behavior. Verification: - go test ./internal/agent -run MCP concurrency tests -count=1 - go test -race ./internal/agent -run MCP concurrency tests -count=1 - go test ./... -count=1 - go vet ./... - go run ./tools/repolint |
||
|
|
64f1611e36 |
chore(mcp): extract tool result projection
Problem: The rich-result implementation extended plugin.go beyond its existing repolint file-size debt budget, causing the PR lint job to fail. Root cause: CallToolResult parsing remained in the legacy aggregate plugin file even though the new projection helpers already had a dedicated owner file. Fix: Move the complete text, image, and rich-result projection implementation into toolresult_projection.go without changing behavior or the repository lint baseline. Verification: - go run ./tools/repolint - golangci-lint run --timeout=5m ./internal/plugin/... - go test ./internal/plugin -count=1 - go vet ./internal/plugin |
||
|
|
e462495ca5 |
fix(mcp): project rich tool results safely
Problem: Ordinary MCP tool calls silently dropped structuredContent, resource links, embedded resources, audio, and unknown content blocks before the result reached the model. Root cause: The model-facing CallToolResult adapter only decoded text and direct image blocks. MCP Apps retained the complete raw result, but the normal agent path had no bounded fallback for the remaining standard content types. Fix: Add deterministic, provider-safe rich-result projection with per-item and aggregate budgets. Preserve canonical structured JSON and resource metadata, route embedded images through the existing image channel, summarize audio and binary resources without exposing inline base64 or _meta, and keep the MCP App transcript contract unchanged. Verification: - go test ./internal/plugin -count=1 - go test ./... - go vet ./internal/plugin |
||
|
|
e1958def9f |
fix(mcp): complete Apps compatibility across protocol versions
Merge the latest main-v2 baseline and resolve the release-note, bundle-budget, and repolint conflicts that left PR #9547 unmergeable. Negotiate MCP Apps in both directions, prefer stable nested UI metadata, preserve complete bounded CallToolResult payloads, support unambiguous legacy push elicitation, and cancel App-owned calls with their instance lifecycle. Add protocol-version, metadata, result-shape, and lifecycle coverage and wire the MCP App frontend contract test into CI. Verified with Go 1.26.6 tests and vet for both modules, targeted race tests, frontend production build and protocol tests, golangci-lint including cross-platform tags, govulncheck, repolint, release-note validation, and release workflow contracts. |
||
|
|
6fc731a2c4 |
feat(mcp): apps tool metadata, visibility split, rich results, instance registry
Parse MCP Apps tool metadata from tools/list _meta: visibility (default model+app) and the ui resource (nested ui.resourceUri with the flat legacy key fallback), carrying the declared CSP directives. The catalog splits into the model catalog (visibility includes model, unchanged for use_capability and provider requests) and a server-private App catalog (visibility includes app); a visibility change flips the catalog fingerprint, and enhanced caches persist both fields. tools/call results from App-capable tools now collect a bounded local presentation (standard CallToolResult + structuredContent + resource identity, 512 KiB cap, inline audio/video and oversized base64 dropped) through a per-call collector on the plan context; the agent attaches it as provider.Message.MCPApp, which projection strips from every provider request - leak tests added for the openai, anthropic and responses wire builders, and provider request bytes stay unchanged for text-only results. Host gains the bounded App Instance Registry: unguessable 24-byte tokens binding server, catalog generation, originating call and resource URI, max 32 live instances with oldest-eviction, reclaimed on server removal. Instance tool resolution refuses cross-server targets, non-App tools, and stale catalog generations. |
||
|
|
7cfda87a16 |
feat(plugin): host capability profiles, cache isolation, capability matrix
Introduce HostProfile (core-v1 / interactive-v1 / desktop-apps-2026-01-26-v1): the semantic client-capability surface fixed at Host creation. Interactive profiles declare form+url elicitation; Desktop additionally declares the io.modelcontextprotocol/ui extension with text/html;profile=mcp-app. The SDK default MRTR middleware replaces the hard-disabled mode, and a no-broker elicitation cancels instead of guessing. Cache identity follows the profile: core-v1 keeps the legacy v2 <slug>.json; capability-declaring profiles write an isolated v3 <slug>.host-<profileHash>.json carrying visibility and UI metadata, and treat the legacy file as a miss so catalogs negotiated under different capabilities never cross-read. Frontends pick their profile: chat TUI and serve interactive-v1, desktop desktop-apps (degrading to interactive when the Apps sandbox listener is unavailable), headless/bots core-v1. Host.CapabilityViews exposes the four-layer matrix (Protocol/Core/Interactive/Apps) over live negotiation state, surfaced via control.MCPCapabilityViews, desktop binding and /mcp status. |
||
|
|
67fd53de67 |
Split MCP lifecycle code along owner boundaries
Problem: The completed MCP session migration exceeded repository capacity limits in several long-lived owner files and tests. Root cause: SDK adapters, session diagnostics, argument normalization, and deterministic fixtures were initially colocated with broader product surfaces. Fix: Extract the new responsibilities into focused owner files, group capability and auxiliary refresh state by lifetime, and keep all runtime behavior and public contracts unchanged. Verification: go run ./tools/repolint go test ./... go test -race ./internal/agent/... ./internal/plugin/... ./internal/boot/... cd desktop && go test -race ./... pnpm --dir desktop/frontend test:all pnpm --dir desktop/frontend build |
||
|
|
d33bd3e085 |
Refactor MCP sessions onto the official Go SDK
Problem: Stateful Streamable HTTP servers could lose their pending session before Reasonix opened the listening stream, while the custom transport engine duplicated protocol correlation, cancellation, and recovery logic. Root cause: Reasonix initialized Streamable HTTP with POST requests only and maintained separate request loops for HTTP, SSE, and stdio. Session loss, concurrent rebuilds, pagination, cancellation, OAuth refresh, and cleanup therefore had inconsistent owners. Fix: Adopt modelcontextprotocol/go-sdk v1.7.0 for protocol sessions, add a generation-fenced session supervisor, preserve Reasonix transport security and stdio process isolation, and cover JetBrains activation, recovery, pagination, cancellation, OAuth, schema stability, and bounded close behavior. Verification: - go test ./internal/plugin -count=1 - go test -race ./internal/plugin -count=1 - go test ./... - go vet ./internal/plugin |
||
|
|
b41b4de9df |
fix(mcp): refresh tools after list change notifications / MCP 工具列表变更后自动刷新 (#8673)
* fix(mcp): refresh tools after list change notifications * fix(mcp): bound dynamic tool catalog refreshes Problem: Tool-list notifications could create an unbounded refresh loop, while catalog and execution locks allowed remote MCP calls to stall status reads. Failed refreshes also left adapters executable with stale safety metadata. Root cause: The refresh worker drained a self-replenishing pending bit, and toolsMu covered both remote I/O and adapter dispatch. Notifications were accepted without checking the advertised listChanged capability. Fix: Coalesce notifications into a debounced two-attempt cycle, publish immutable fingerprinted catalogs with generations, separate fetch/status/dispatch lock domains, gate notification support on initialize capabilities, and fail closed while a catalog is stale. Verification: go test ./... -count=1; go test -race ./internal/plugin ./internal/agent -count=1; go vet ./...; go run ./tools/repolint; git diff --check. * fix(mcp): preserve dynamic catalog convergence Problem: A tool catalog refreshed before the session runtime subscribed could leave stale adapters in its registry. Repeated notifications could also exhaust the two-attempt cycle, and refreshes ignored configured timeout budgets. Root cause: Host subscriptions did not replay current catalogs, the refresh worker stopped with a dirty revision after its second successful list, and it imposed the five-second interactive startup wait on background tools/list calls. Fix: Replay existing catalogs before later subscriber deliveries, keep dirty revisions converging under capped exponential backoff, and bound refreshes by the stricter resolved startup or call timeout. Verification: go test ./... -count=1; go test -race ./internal/plugin ./internal/agent -count=1; go vet ./...; go run ./tools/repolint; git diff --check. * fix(mcp): harden dynamic catalog refresh lifecycle Problem: A disabled session could retain replayed shared-Host MCP adapters, generic registry aliases bypassed runtime authorization, catalog resolution could deadlock with a pending refresh writer, and a self-notifying server could refresh forever. Root cause: Runtime inventory changes did not suspend registry prefixes, generic registry routing skipped MCP identity binding, resolve held dispatchMu across a re-entrant catalog callback, and refresh convergence had no attempt ceiling. Fix: Synchronize registry prefix suspension with runtime lifecycle, bind every registry-backed MCP adapter to the current runtime, release the dispatch lease before catalog lookup, and cap each refresh cycle while preserving fail-closed user-driven recovery. Verification: go test ./internal/plugin ./internal/agent -count=1; go test -race ./internal/plugin ./internal/agent -count=1; go test ./... -count=1; go vet ./...; go run ./tools/repolint; git diff --check. * fix(mcp): satisfy refresh loop lint Problem: CI rejects the newly added bounded refresh loops under the Go 1.22 intrange rule. Root cause: The loops used the classic counter form even though the repository requires integer ranges. Fix: Use integer ranges while preserving the four-attempt refresh behavior and test assertions. Verification: - go test ./internal/plugin -run 'TestToolListRefresh' -count=1 - go test ./internal/plugin ./internal/agent -count=1 - go test -race ./internal/plugin ./internal/agent -count=1 - go test ./... -count=1 - go vet ./... - go run ./tools/repolint - golangci-lint run ./... - git diff --check * test(mcp): drain startup cache before adapter mutation Problem: The reader safety-drift test can mutate a remote adapter while Start's detached schema-cache snapshot is still reading it, causing a race on the latest base merge tree. Root cause: The test models metadata drift by directly changing the adapter, but did not first wait for startup persistence to finish. Fix: Drain the host's startup background writes before the test-only adapter mutation. Verification: - go test -race ./internal/plugin -run '^TestReaderIntentRefusesDispatchAfterSafetyDrift$' -count=10 - go test -race ./internal/plugin ./internal/agent -count=1 - go test ./internal/plugin ./internal/agent -count=1 - go test ./... -count=1 - go vet ./... - go run ./tools/repolint - golangci-lint run ./... - git diff --check * test(agent): stabilize background fleet lifecycle assertion Problem: The CI race job can observe no running fleet job in TestBackgroundFleetProgressLifecycleUsesStableIDs even though registration and completion both work correctly. Root cause: The test released both blocked workers before inspecting RunningForSession, allowing the background job to finish and disappear first under runner load. Fix: Assert registration while the provider is still blocked, then release the workers and verify terminal completion. Verification: - GOTOOLCHAIN=go1.26.6 go test -race ./internal/agent -run '^TestBackgroundFleetProgressLifecycleUsesStableIDs$' -count=500 - GOTOOLCHAIN=go1.26.6 go test -race ./internal/agent -count=1 - GOTOOLCHAIN=go1.26.6 go test ./internal/agent -count=1 - go run ./tools/repolint - golangci-lint run ./... - git diff --check --------- Co-authored-by: SivanCola <[email protected]> |
||
|
|
f17fceb033 |
fix(catalogs): close projection race and migration gaps
Problem: Stale controller builds could roll back shared MCP instances owned by a newer build. Session tombstones could underfill paginated results and their same-revision refresh event could be ignored. Legacy topic migration markers could also miss same-size or event-log changes. Root cause: Registration scopes tracked creation without transferable instance claims, catalog pagination applied tombstones after SQL limits, and migration completion used only transcript names and sizes while explicit reconciliation still trusted that marker. Fix: Track exact MCP instance claims through scope commit or abort, scan catalog keyset pages until visible limits are satisfied, publish overlay refreshes that accept equal revisions, fingerprint bounded session artifacts and authoritative event logs, and add a marker-bypassing explicit migration path. Verification: go test ./internal/plugin ./internal/sessioncatalog go test -race ./internal/plugin ./internal/sessioncatalog go test ./desktop focused migration, tombstone, restore, clear-session, and registration cases pnpm typecheck npx tsx src/__tests__/project-tree-shell-race.test.ts |
||
|
|
01bc087862 |
fix(desktop): registration scopes, catalog tombstones, migration signatures
Problem: Three P1 merge blockers remained on the catalogs integration head: 1) Host registration journal used a Host-global regJournalActive flag, so sibling hot-adds during a stale build were attributed and rolled back. 2) RemoveSession stored tombstones only after mutex waits; a 150ms desktop context could expire mid-lock, leave SQL undeleted, and ListTopics still returned archived topics (race-mode restore test). 3) Topic migration markers used strict mtime ordering, which failed on Windows coarse timestamps for new CLI sessions after the marker. Root cause: 1) Ownership was time-window based rather than token-based. 2) Query paths trusted SQLite without an immediate removal overlay. 3) Marker validity compared file mtimes instead of directory content. Fix: - Replace the global journal with per-build RegistrationScope tokens propagated via context through EnsureConnected and LazyToolset; abort rejects late registrations; sibling writes without the token are ignored. - Do not hold a Host lock across boot.Build (scopes do not serialize builds). - Record removedPaths before any mutex; filter ListTopics/ListSessions/ GetSession through the tombstone; TryLock durable DELETE and retry async. - Persist migration markers as name+size directory signatures; new sessions invalidate without mtime ordering. - Extract registration scope, removal, migration marker, and legacy migration helpers to keep repolint budgets clean (baseline carry-forward only). Verification: - go test ./internal/plugin/ -run TestRegistration (-race) - go test ./internal/sessioncatalog/ -run 'TestRemoveSession|TestListTopics' (-race) - cd desktop && go test -run 'TestProjectTreeMigratesNewCLISessionAfterProjectDirMarker|TestRestoreGlobalTopicSessionReindexesProjectTree|TestClearSessionForTab' - cd desktop && go test -race -count=3 -run TestRestoreGlobalTopicSessionReindexesProjectTree - go run ./tools/repolint |
||
|
|
8cff50d08b |
fix(desktop): instance-scoped Host rollback and hydrate generics
Problem: Desktop CI failed with TS2305 because hydrateErrorState imported a non-existent Item type from ./types. Host generation-loss rollback still used name-diff snapshots, so a deterministic interleaving where sibling/new-generation resources appear after the snapshot incorrectly deleted those live clients. Root cause: 1. The helper was typed against a concrete Item export that does not exist in types (and created a fragile circular import surface). 2. Name-set diff has no ownership: any server name created after the snapshot was attributed to the lost build, including post-snapshot sibling work. Fix: - Make applyHydrateErrorState / hydratePlaceholderItems generic over TItem so the helper needs no types import. - Assign Host-local Client instance IDs, journal registrations during RunWithRegistrationJournal, and roll back only with RemoveIfInstance. - Controller builds wrap boot.Build in the journal; supersede/generation-loss paths call RollbackRegistration on journaled refs only. - Add deterministic instance-scoped regression tests (including post-journal sibling preservation and same-name newer-generation survival). - Make Client.close nil-safe for transport-less test clients. Verification: - go test ./internal/plugin/ -run 'TestRegistrationJournal|TestRunWithRegistration' -count=1 - go test -race ./internal/plugin/ -run 'TestRegistrationJournal|TestRunWithRegistration' -count=1 - cd desktop && go test -c . && go test -run TestClearSessionForTab -count=1 - cd desktop/frontend && pnpm typecheck |
||
|
|
f4d332b73f |
Merge origin/main-v2 into feature/mcp-oauth-sandbox-auth
Resolve the latest main-v2 controller/runtime changes in favor of the current base implementation while retaining the MCP OAuth and sandbox fixes. |
||
|
|
1c30748abc |
feat(mcp): add sandbox-safe OAuth authorization
Problem: - Remote MCP OAuth depended on externally injected credentials that sandboxed desktop and CLI sessions could not access. - Failed cached MCP tools could still be preferred by capability routing. Root cause: - Reasonix had no first-party user-initiated OAuth flow or private credential state. - Cached tool entries did not inherit failed or disabled server status. Fix: - Add standards-based OAuth discovery, DCR, PKCE, refresh rotation, proxy-aware HTTP, and resource-bound private state. - Serialize token rotation across processes and prevent stale transports from restoring cleared state. - Add CLI, TUI, and Desktop sign-in and reconnect flows. - Propagate failed and disabled lifecycle state to cached MCP tools. Verification: - go test -race ./internal/plugin ./internal/capability ./internal/boot ./internal/cli - go test ./... - (cd desktop && go test ./...) - (cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts) - (cd desktop/frontend && pnpm typecheck) - (cd desktop/frontend && pnpm lint:hooks) - go vet ./... - git diff --check Co-authored-by: andrei-kiparuk <[email protected]> |
||
|
|
3552822776 | feat: add generation-safe runtime composition | ||
|
|
4fbbca6a99 |
refactor(go): adopt modern stdlib idioms and gate them (#7831)
* refactor(go): adopt modern stdlib idioms across kernel and desktop Mechanical sweep with golangci-lint's modernize/intrange/copyloopvar/ usestdlibvars fixers plus hand-fixes for what they could not rewrite: slices.Contains, min/max, slices.Backward, WaitGroup.Go, reflect.TypeFor, maps.Copy, strings.Cut/CutPrefix/SplitSeq, strings.Builder, range-over-int. Net -549 lines with no behavior change. Three loops in session.go stay index-based: slices.Backward yields element copies, so the fixer's rewrite silently dropped writes to DecisionReceipts and ToolCalls (caught by TestDelivery* and TestDependentSameBatch*). All 49 slices.Backward sites were audited for that shape; only these three write through the index, and they carry a nolint with the reason. CaptureAfter's scan loop had an empty body and never ran any logic, so the fixer could not express it. Drop it and the accessor it was the only caller of. omitzero is deliberately untouched: turning omitempty into omitzero changes the JSON written for nested structs, which is a wire-format change to session files and the extension protocol. * build(lint): gate the modern-idiom linters Nothing stopped the codebase from drifting back to pre-1.22 idioms. Both modules are now clean for modernize, intrange, copyloopvar, and usestdlibvars, so enable them rather than leaving the sweep as a one-off. omitzero stays disabled with the reason inline; usetesting, perfsprint, and errorlint still have residue and land separately. |
||
|
|
e1079f2b58 |
fix(errors): compare errors through the unwrap chain, and gate it (#7834)
Every `err == Sentinel` and `err.(*T)` in the tree works today only because the error happens to arrive unwrapped. That is not a property anyone maintains: one `%w` added between producer and check silently stops the branch from firing, and this same change adds 54 of them. So the comparisons are converted first, then the wrapping. Three helpers turned out to be hand-rolled reimplementations of the standard library and are deleted rather than patched: remote.isPassphraseMissing, remote.asKeyError (which open-coded the Unwrap() walk), and forward.containsErr (which open-coded both the single- and multi-error walks). Call sites use errors.As/errors.Is. Two assertions deliberately keep `!=`: explainError and AnnotateToolSchemaError must return the *same* error object, and errors.Is would still pass if they started wrapping it. Both carry a nolint naming that. errorlint is enabled in .golangci.yml; both modules are clean under it. |
||
|
|
c969509d66 |
style: strip section-banner decoration from comments (#7840)
312 banner separators across 63 files, the thing REASONIX.md bans outright and nothing has ever checked. 64 were pure rules with no content and are gone; the other 248 carried a label inside the dashes, so the decoration is stripped and the label kept -- `// --- session/resume ---` becomes `// session/resume`, which still orients a reader in a 1000-line test file without drawing a line across it. The repolint ceiling for this rule drops 312 -> 0, so it is now zero-tolerance rather than merely frozen. |
||
|
|
f6be547f49 |
feat(cli): version metadata, shell completion, MCP source display
Problem - reasonix --version only printed a bare version string, which is hard to correlate with source builds and user bug reports. - No first-party bash/zsh/fish completion script existed. - /mcp status did not show which config plane registered a server/tool. Root cause - Version path was a one-line println; no completion registry; ServerStatus omitted Spec.ConfigSource. Fix - BuildInfo with version --verbose / --json (version, git commit, build time UTC, target, go). --version/-v stay single-line. No config path, no CST. - reasonix completion bash|zsh|fish from a shared registry with Run dispatch drift guard (adapted from #7425). - Plumb ConfigSource into ServerStatus and render source= on /mcp status and tool lists (from #6175 scope: display only). - Makefile ldflags inject version, gitCommit, buildTimeUTC. Verification - go test ./internal/cli -count=1 - go test ./cmd/reasonix ./internal/plugin -count=1 - TestBuildInfo*, TestCLICompletion*, TestRenderMCPStatusShowsConfigSource Refs #6476 #7425 #6175 #6578 #6833 Session lease / REASONIX_HOME / bash session-allow already covered on main-v2; no --home flag added. Co-authored-by: h4rK8s <[email protected]> Co-authored-by: Zhiyao Wen <[email protected]> |
||
|
|
4a989f4d6a |
fix(mcp): flush startup stderr before reporting
Problem: The Linux race job intermittently observed an initialize timeout before the stdio stderr copy goroutine had flushed, so structured startup diagnostics omitted the expected redacted stderr tail. Root cause: Startup failure paths sampled the bounded stderr buffer before closing and reaping the transport, even though transport close is the synchronization point for the copy goroutine. Fix: Close the failed transport first, then read and redact startup stderr for initialize and both tools/list paths. Verification: - go test -race ./internal/plugin -run ^TestStartupFailureReportsStageElapsedAndRedactedStderr$ -count=20 - go test -race ./internal/plugin ./internal/control ./internal/agent -count=1 - go test -p 4 ./... -count=1 - go vet ./internal/plugin - ./scripts/cache-guard.sh - git diff --check |
||
|
|
e94e549799 |
fix(mcp): fence background startup removal
Problem: A server removal could race the narrow gap between a background handshake finishing tools/list and publishing its client, allowing the removed server to reappear. Root cause: EnsureConnectedInBackground registered a deferred generation but did not pass that generation into the final Host publication check. Fix: Carry the generation through the shared connection path, retain configured server names in connection status, and cover removal during an in-flight background startup. Verification: - go test ./internal/plugin -count=1 - go test -race ./internal/plugin ./internal/control ./internal/agent -count=1 - go test -p 4 ./... -count=1 - git diff --check |
||
|
|
5c16cc1408 |
fix(mcp): keep slow startup alive in background
Problem: Cold MCP servers that exceeded the short interactive wait were canceled and restarted on every retry, obscuring the real startup stage and source. Root cause: The caller wait context also owned the shared initialize and tools/list lifecycle, so a five-second timeout terminated otherwise healthy cold starts. Fix: Separate the session-owned startup lifecycle from the caller wait, add bounded global and per-server startup timeouts, preserve pinned provider-visible tool schemas, and expose redacted structured diagnostics. Verification: - go test -p 4 ./... -count=1 - go test -race ./internal/plugin ./internal/control ./internal/agent -count=1 - ./scripts/cache-guard.sh - go vet ./internal/plugin ./internal/config ./internal/boot ./internal/capdiag ./internal/control ./internal/agent ./internal/installsource - git diff --check |
||
|
|
0f42b4b650 |
feat(agent): Planner and sub-agent trusted MCP via stable use_capability / Planner 与子 Agent 安装即信任 MCP (#6865)
* feat(agent): trust installed MCP in Planner and task sub-agents via stable use_capability Problem: The dual-model Planner and task/fleet children could not reliably query installed MCP servers (e.g. databases) without readOnlyHint or dynamic mcp__ schemas, so plans and delegated research were often ungrounded. Issue #6854. Root cause: Planner and strict/ordinary child registries either excluded MCP or required readOnlyHint, and MCP tools entered provider-visible schemas so connect churn broke prompt cache. Delivery alone had use_capability. Fix: - NewPlannerAgent with PlannerMCPExecution: authorized non-destructive MCP via use_capability without readOnlyHint; destructive left for Executor. - Session-shared MCPCapabilityRuntime (Host/specs/connections) with per-agent use_capability frontends (isolated ledger/audit). - Ordinary task/fleet children get full proxy (list/inspect/call); destructive follows normal writer permissions. - Strict read-only children share proxy schema/reuse but keep authorized + readOnlyHint + non-destructive execution. - Strip direct mcp__* from Planner and sub-agent registries; convert profile MCP allowlists to capability IDs; action=list for discovery. - NonDestructiveMCPExecutionIntent re-check before tools/call; Desktop MCP tool card + docs. Verification: - go test ./internal/agent ./internal/boot ./internal/control ./internal/plugin ./internal/capability - ./scripts/cache-guard.sh - pnpm --dir desktop/frontend typecheck - git diff --check Fixes #6854 * fix(agent): close trusted MCP auth, connect gate, and review proxy gaps Problem: PR #6865 left three P1 boundaries incomplete: deferred MCP targets treated !RequireLaunchApproval as authorized (bypassing Spec.Authorized), authorized mcp_connect__* still entered the ordinary permission Gate, and review/read_only_skill children never received the shared capability runtime or use_capability allowlist. Root cause: Connect helpers invented trust instead of using ServerAuthorized(); lifecycle connect targets were excluded from the installed-MCP fast path; boot skill runners still called registry builders without capRuntime and builtin review AllowedTools omitted use_capability. Fix: - Deferred MCP targets and ensureServerTools use only Spec.ServerAuthorized(). - Authorized MCP tools and lifecycle connects share the explicit-deny-only gate. - Wire capRuntime into read_only_skill and subagent skill runners; add use_capability to builtin review/security-review allowlists. - Regression tests for zero-start unauthorized non-project MCP and trusted first connect under deny-all ordinary gates. Verification: - go test ./internal/agent ./internal/skill ./internal/plugin ./internal/boot * fix(agent): stabilize restricted proxy prefix and isolate planner ledger Problem: Restricted use_capability proxies embedded dynamic capability IDs in their Description, so MCP install/tool-list changes altered provider-visible tool bytes (cache churn). Delivery dual-model still shared Executor use_capability and ledger with the Planner. Restricted action=list leaked every configured server. An empty if in subagent_registry_test triggered staticcheck SA4006. Root cause: Allowlist text was baked into tool descriptions; PlannerToolRegistry copied parent use_capability before the clone branch; attachSubagentCapabilityProxy returned early when a parent proxy was already present; list delegated to the unfiltered frontend. Fix: - Keep restricted proxy description/schema identical to the base proxy; enforce allowlists only in host checks and filtered list results. - Always strip and reinstall an isolated use_capability frontend for sub-agents and the Planner; give the Planner its own ledger and audit in boot. - Filter list output to allowlisted servers only. - Assert FilterReadOnlyRegistry keeps authorized RO MCP; add stability and isolation regression tests. Verification: - go test ./internal/agent ./internal/boot ./internal/skill - git diff --check * fix(agent): fail-closed restricted MCP list filtering Problem: Malformed allowed-tools entries such as "mcp-server:" produced a non-empty capability allowlist but an empty server set, so filterCapabilityListResult returned the unfiltered inventory. Unreadable list payloads also passed through. Root cause: Incomplete capability IDs were accepted literally, and empty-server / parse errors used fail-open returns. Fix: - Accept only complete mcp-server:<name> and mcp-tool:<server>/<tool> IDs. - Skip installing a restricted proxy when no servers resolve from the allowlist. - Empty server sets and JSON parse/encode failures return an empty servers list. - Regression tests for malformed IDs, empty servers, and bad list payloads. Verification: - go test ./internal/agent -run 'TestRestricted|TestMalformed|TestFilterCapability|TestPlannerTool|TestCustomProfile' * fix(ui): show resolved MCP capability effects Problem: The stable use_capability proxy made Desktop and CLI present MCP calls only by the proxy surface. Writer MCP calls could be grouped as quiet research, and terminal users saw the implementation-level proxy name instead of the selected capability. Root cause: Reasonix resolved the real MCP target only inside execution. That target name, capability ID, and read-only classification were not carried through durable session history, frontend events, or Remote history projections. Fix: - Persist optional resolved target metadata while keeping the provider-visible tool call unchanged. - Emit deterministic same-ID dispatch refreshes after MCP resolution and carry the same classification on tool results. - Classify Desktop MCP cards by the resolved effect, fail closed for legacy history, and preserve the metadata across Local and Remote reloads. - Render use_capability as MCP(capability_id) in the CLI TUI and headless output. - Add concurrency, serialization, history, protocol, Desktop, and CLI regression coverage. Verification: - env -u DEEPSEEK_API_KEY go test ./... - cd desktop && go test ./... - pnpm --dir desktop/frontend typecheck - pnpm --dir desktop/frontend test - go test -race ./internal/agent -run 'TestResolvedCapabilityDispatchRefreshesWriterClassification|TestResolvedCapabilityRefreshesParallelCallsInProviderOrder|TestUpdateToolCallResolutionPersistsAfterMidTurnSnapshot' - ./scripts/cache-guard.sh - go run ./cmd/remote-protocol-gen -check -root . - git diff --check * fix(agent): close trusted MCP proxy safety gaps Problem: Restricted sub-agent MCP allowlists could reveal sibling tool schemas, proxy-resolved writers could execute concurrently, and Balanced Executors could not directly call capability IDs discovered by their Planner. Root cause: Tool grants were widened to server IDs, batching trusted the proxy's read-only surface before resolving the real target, and dual-model Balanced installed use_capability only on the Planner registry. Fix: Keep tool grants exact and filter concrete tool inspection, serialize use_capability for every agent, and install isolated stable proxy frontends on both Balanced Planner and Executor while sharing the MCP runtime. Update bilingual contracts and add focused regressions. Verification: - env -u DEEPSEEK_API_KEY go test ./... - go test -race ./internal/agent - go vet ./... - ./scripts/cache-guard.sh - git diff --check * fix: enforce trusted MCP runtime boundaries Problem: Path-scoped sub-agents could resolve use_capability into unscoped MCP writers, and the stable MCP proxy kept boot-time specs after add, update, disable, or removal. Root cause: use_capability was classified as statically read-only before dynamic resolution, while MCPCapabilityRuntime and capability routing copied immutable boot snapshots that were not synchronized with controller lifecycle changes. Fix: Keep the provider-visible proxy contract stable while restricting explicit write_paths tasks to proven read-only MCP targets. Make each controller runtime an authoritative, race-safe MCP inventory, synchronize CLI/Desktop lifecycle changes and shared-host tabs, clear stale live tools, and route from live runtime snapshots. Verification: go test ./internal/agent ./internal/boot ./internal/control ./internal/plugin go test -race ./internal/agent ./internal/control env -u DEEPSEEK_API_KEY go test ./... cd desktop && go test ./... go vet ./... ./scripts/cache-guard.sh git diff --check * fix(agent): bind trusted MCP dispatch to live runtime identity Problem: Planner and stable capability frontends could reuse a same-name MCP client from another controller or stale configuration. Plan mode also blocked authorized opaque non-destructive MCP, lifecycle snapshots could mix generations, and legacy Desktop history lost built-in reader classification. Root cause: Shared Host reuse checked only the server name, resolved targets were not bound to the controller's current enabled/authorized spec, lifecycle mutations were not linearized with tools/call, and routing read configuration and live tools in separate snapshots. Documentation also described ordinary sub-agent MCP as using normal writer permission prompts even though installation is the trust boundary. Fix: - Compare complete host-local MCP runtime identity and require exact identity for Host/tool reuse. - Bind resolved MCP targets to the live runtime and re-check enablement, authorization, identity, and destructive safety before dispatch. - Linearize connect/call against update, disable, and removal; return atomic catalog/live-tool snapshots. - Preserve Planner Plan-mode access to authorized non-destructive MCP without readOnlyHint and safely complete connected server-directory calls. - Restore legacy Desktop reader classification and align bilingual contracts with explicit-deny-only installed MCP trust and direct-Executor cache limits. - Add identity, shared-Host, real opaque Planner, revocation, concurrency, and frontend regressions. Verification: - go test ./internal/agent ./internal/plugin ./internal/control ./internal/boot -count=1 - go test ./internal/agent -run 'TestPlannerPlanModeExecutesAuthorizedOpaqueMCPThroughRuntime|TestRuntimeDisableLinearizesWithInFlightMCPDispatch|TestMCPCapabilityRuntimeConcurrentUpdatesAndSnapshots' -count=1 - go test ./internal/plugin -run TestMCPRuntimeSpecMatchesExactHostIdentity -count=1 - ./scripts/cache-guard.sh - pnpm --dir desktop/frontend exec tsx src/__tests__/tool-subject.test.ts - pnpm --dir desktop/frontend typecheck - git diff --check * fix(agent): remove obsolete MCP runtime wrapper Problem: The latest PR head failed golangci-lint because ensureServerTools remained as an unused private wrapper after every dispatch path moved to the identity-bound ensureServerToolsForSpec helper. Root cause: The runtime-identity refactor replaced all callers but left the compatibility wrapper behind. Go compilation and vet allow unused methods, while CI's unused analyzer correctly rejected it. Fix: Delete the dead wrapper so all connect paths continue to require the already-authorized current runtime spec explicitly. Verification: - go test ./internal/agent -count=1 - go vet ./... - go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m - git diff --check * fix(agent): preserve MCP images and route IDs Problem: The first use_capability call to an unconnected MCP server dropped structured image results, and Planner transient routing rewrote concrete MCP capability IDs to source targets that use_capability cannot call. Root cause: onDemandMCPTool exposed only the text Execute interface, so Agent dispatch could not select ExecuteWithImages. RenderTransientBlock applied legacy source rewriting before accounting for Planner MCP proxy routing. Fix: Give the deferred MCP target a shared image-aware execution path that retains the runtime authorization lock through tools/call. Keep concrete MCP IDs for capability-proxy routes while preserving ordinary source routing for non-MCP capabilities. Add full Agent-path image coverage and focused route rendering regressions. Verification: - go test ./internal/agent ./internal/capability ./internal/control ./internal/boot - env -u DEEPSEEK_API_KEY go test ./... - go test -race ./internal/agent -count=1 - go vet ./... - ./scripts/cache-guard.sh - git diff --check |
||
|
|
b8f715b546 |
Drain lazy MCP cache writes on close / 关闭时等待 Lazy MCP 缓存写入 (#6840)
* fix(plugin): drain lazy schema writes on close Problem: Formal release tests intermittently failed while removing plugin cache temp directories because a lazy schema cache writer was still running. Root cause: Lazy MCP startup launched SaveCachedSchema in an untracked goroutine, so Host.Close could return before the detached write finished. Fix: Register lazy schema persistence with the Host background-write WaitGroup and add a deterministic close-wait regression test. Verification: - focused lazy lifecycle tests, 50 runs - go test ./internal/plugin -count=5 - go test -race ./internal/plugin - two consecutive REASONIX_RELEASE_CACHE_GUARD=1 go test ./... -count=1 * fix(plugin): close remaining cache write races Problem: PR review found that the synchronous lazy cache-hit path could still write after Host.Close returned, while Windows CI intermittently observed the SSE concurrency assertion before its handler published the active count. Root cause: The cache-hit path ended its deferred-spawn lifecycle before persisting the schema, and the SSE test closed its started signal before updating maxPosts. Fix: Queue cache-hit schema persistence before ending the deferred spawn, and publish the SSE started signal only after the concurrency counters are visible. Verification: - focused lazy lifecycle tests, 50 runs - SSE concurrency regression test, 5000 runs - focused race tests, 20 runs - go test -race ./internal/plugin - go test ./internal/plugin -count=10 - REASONIX_RELEASE_CACHE_GUARD=1 go test ./... -count=1 - go vet ./... && go build ./... - Windows/amd64 plugin test cross-compilation |
||
|
|
31f037d07a |
feat(mcp): make trusted servers zero-config and reliable / 让可信 MCP 服务零配置且可靠可用 (#6829)
* fix(mcp): make trusted servers zero-config and reliable Run authorized stdio servers on the host by default while retaining private state directories, bounded lifecycle cleanup, lazy schema restoration, and single-flight startup. Add durable activation, transactional readiness and rollback, CLI candidate probing, and inherited MCP access for child agents. Include a real chrome-devtools-mcp release smoke test. * feat(desktop): reduce MCP install to one input Accept a command, URL, or JSON definition in the common install path, surface structured readiness and authentication states, and keep transport and timeout controls in the advanced edit flow. * feat(desktop): unify MCP quick and advanced setup Problem: The zero-config MCP installer removed useful configuration controls and left the add-server page feeling too sparse. Design: Keep quick install as the default, restore manual and JSON modes in the same editor, carry pasted definitions into advanced modes, and make the automatic install outcomes explicit. Verification: npm run typecheck; npx tsx src/__tests__/capabilities-panel-actions.test.ts; npm run check:css; npm run build; git diff --check. * fix(plugin): remove obsolete deferred wrapper Problem: golangci-lint reported Host.addDeferred as an unused private method. Root cause: Lazy startup now calls EnsureConnectedWithLifecycle directly, but the old wrapper remained after the single-flight migration. Fix: Remove the dead wrapper and its stale comment without changing runtime behavior. Verification: go test ./internal/plugin; go test -race ./internal/plugin; scripts/check-cache-impact.sh; git diff --check. * fix(desktop): remove obsolete MCP intent helper Problem: The desktop golangci-lint job reported mcpStartIntent as an unused private function. Root cause: Legacy startIntent fields are now derived in withPluginConfigInWorkspace, but the previous helper remained after the activation-state migration. Fix: Remove the dead helper while preserving the derived compatibility fields. Verification: cd desktop && go test ./...; cd desktop && go vet ./...; scripts/check-cache-impact.sh; git diff --check. * fix(mcp): preserve trust and on-demand boundaries Problem: Explicit subagent tool allowlists could be widened by MCP inheritance, host-session readiness probes owned successful stdio processes, durable enable still connected eagerly, activation updates could clobber each other, quick names followed trailing arguments, and Windows asserted POSIX mode bits. Root cause: MCP inheritance bypassed the normal registry filter; the short probe context was reused as process lifetime; enable reused the live-connect API; activation stores only locked per instance; name derivation scanned from the command tail; and the permission test was not platform-aware. Fix: Keep explicit allowlists authoritative while preserving default MCP inheritance, split ExtraPlugin call and lifetime contexts, add cache-backed on-demand registration, serialize activation transactions with a cross-process lock, derive quick names from launcher operands in desktop and CLI, and scope mode-bit assertions to POSIX. Verification: go test -race ./internal/agent ./internal/boot ./internal/control ./internal/config; env -u DEEPSEEK_API_KEY go test ./...; cd desktop and run go test ./... plus go vet ./...; frontend MCP actions, typecheck, build, and CSS checks; go vet ./...; ./scripts/cache-guard.sh; git diff --check. |
||
|
|
7fe7d2b3f3 |
Simplify MCP authorization for installed servers / 简化已安装 MCP 授权 (#6767)
* fix(mcp): trust installed readers in subagents Problem: User-installed MCP tools that declared readOnlyHint could be exposed as readers but then fail before dispatch in planners and read-only subagents. Users were told to configure a second per-tool reader policy after already installing or authorizing the server. Root cause: Strict reader trust did not treat explicit install or an exact project launch grant as authority for non-destructive server metadata. Direct, lazy, and on-demand paths also duplicated cached-to-live safety reconciliation, while trusted_read_only_tools remained wired through configuration and Desktop contracts. Fix: Authorize non-destructive readOnlyHint tools for installed or launch-authorized servers, carry reader intent only for trusted readers, and share one cached-to-live safety reconciliation boundary across lazy and on-demand adapters. Remove trusted_read_only_tools from runtime, Desktop, examples, and docs while accepting and dropping legacy input on the next save. Verification: - env -u DEEPSEEK_API_KEY go test -count=1 ./... - cd desktop && go test -count=1 ./... - cd desktop/frontend && pnpm test:typecheck - cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts - ./scripts/cache-guard.sh - ./scripts/check-cache-impact.sh - git diff --check * refactor(mcp): simplify installed server authorization Problem: User-installed MCP servers still depended on official-catalog and per-tool reader trust layers, so direct, lazy, and on-demand paths could disagree and strict child sessions could reject an already authorized reader. Root cause: Server authorization, signed package status, cached reader trust, and legacy plan-mode allowlists were modeled as separate overlapping decisions. Fix: Remove the official MCP catalog, signature/revocation plumbing, package verification, per-tool cached trust, and obsolete reader settings. Use one server-level authorization source for user installs, host-session installs, and exact project grants while retaining live read-only, destructive, and capability-fingerprint checks. Verification: - go test ./internal/plugin ./internal/agent ./internal/boot ./internal/config ./internal/installsource ./internal/pluginpkg ./internal/mcplaunch ./internal/planmode - go test ./internal/acp ./internal/agent ./internal/plugin ./internal/boot - cd desktop && go test ./... - ./scripts/cache-guard.sh - git diff --check * refactor(mcp): make install authorization final Problem: Installed MCP servers could still encounter server, per-tool, writer, or destructive approval branches, and users had to understand settings that duplicated the install decision. Root cause: MCP authorization, cached reader trust, approval policy, and process sandboxing evolved as separate paths across direct, lazy, and on-demand execution. Fix: Treat install or exact project identity confirmation as the server authorization boundary, keep only explicit global deny plus Plan/read-only and sandbox safety checks, remove catalog and approval configuration, and unify the persistent MCP process sandbox. Verification: env -u DEEPSEEK_API_KEY go test ./... go build ./... cd desktop && go test ./... cd desktop/frontend && pnpm typecheck cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts cd desktop/frontend && pnpm exec tsx src/__tests__/decision-surface.test.tsx * chore(mcp): remove retired cache helper Problem: CI lint found writeBoolKV left unused after per-tool reader trust was removed. Root cause: The cached reader-map fingerprint was deleted, but its serialization helper remained. Fix: Remove the dead helper. Verification: go test ./internal/plugin go vet ./internal/plugin go build ./... * refactor(mcp): stop blocking schema-only drift Problem: Authorized MCP calls could be rejected when a cached tool schema differed from the live server even though authorization and safety classification were unchanged. Root cause: A capability fingerprint mixed schema compatibility with read-only and destructive execution safety across direct, lazy, and on-demand paths. Fix: Remove the tool-level schema fingerprint chain, keep live server authorization and read-only/destructive reclassification checks, and refresh changed schemas for the next session without interrupting the current authorized call. Verification: - go test ./internal/plugin ./internal/agent ./internal/tool - env -u DEEPSEEK_API_KEY go test ./... - go vet ./... - go build ./... - ./scripts/cache-guard.sh - pnpm --dir site test - cd desktop && go test ./... * fix(mcp): preserve short Windows temp paths Problem: Windows stdio MCP servers inherited a deeply nested Reasonix private TEMP path. Servers such as MATLAB create watchdog Unix sockets below TEMP and exit before initialization when the path exceeds the platform limit. Root cause: MCP private-state preparation redirected TMP, TEMP, and TMPDIR below the workspace-scoped mcp-state directory on every platform even though Reasonix does not currently provide an OS sandbox on Windows. Fix: Keep the host temporary-directory environment for Windows stdio MCP processes while retaining private cache and state directories. Preserve the existing private temporary directory behavior on other platforms and add focused regression coverage for both branches. Verification: - go test ./internal/plugin -count=1 - GOOS=windows GOARCH=amd64 go test -c ./internal/plugin - go test ./... - go vet ./internal/plugin - git diff --check * Simplify MCP runtime authorization state Use Spec.Authorized as the sole resolved server authorization result so installed and host-session MCP servers no longer depend on launch-manager presence. Keep exact identity grants for project sources, align planner and strict read-only registries with non-destructive readOnlyHint metadata, and document the cooperative trust boundary. Add source and agent-surface matrix coverage while preserving the main provider-visible MCP tool surface. * Limit MCP identity digests to project servers Problem: User-installed and host-session MCP servers still resolved and hashed their executable identity during every startup even though installation was already the authorization decision. Root cause: Project launch authorization and schema-cache ownership shared generic fingerprint terminology, and start() computed the project identity before checking whether launch approval was required. Fix: Skip project identity resolution for installed servers, remove dead and non-participating identity state, and rename the remaining concepts to ProjectLaunchIdentityDigest and SchemaCacheKey while preserving existing JSON keys and digest bytes. Verification: go test ./internal/mcplaunch ./internal/plugin ./internal/capability ./internal/boot ./internal/control ./internal/agent; go test ./...; cd desktop && go test .; go vet ./...; ./scripts/cache-guard.sh; git diff --check. * Restore MCP 0.53 compatibility surfaces Problem: MCP authorization was simplified, but the current client still lacked four compatibility surfaces present in the 0.53 line: legacy SSE, workspace roots, progress notifications, and registry browsing/install. Root cause: the Go transport abstraction dropped inbound notifications and rejected legacy SSE; runtime specs did not carry a separate workspace root; the registry UI and CLI had no official MCP Registry client. Fix: add inbound request/progress routing across transports, implement legacy SSE with same-origin endpoint protection, advertise roots/list, and add an explicit cached official Registry marketplace to CLI and desktop. Remove stale approval documentation and the dead helper that broke lint. Verification: go test ./...; focused go test -race for plugin/registry/CLI/control; go vet in root and desktop; desktop go test ./...; frontend production build and marketplace UI test; Windows amd64 cross-build; official Registry browse smoke test. * Avoid MCP progress capacity overflow * Align MCP authorization documentation Problem: CLI help and Chinese documentation still contained ambiguous or obsolete wording that implied installed MCP writer or destructive calls could require per-call approval. Root cause: The server-level authorization simplification removed MCP approval modes, but not every user-facing explanation was updated consistently. Fix: State explicitly that install or exact project identity confirmation authorizes the server, that writer and destructive hints do not prompt per call, and that deny, Plan, and strict read-only filtering remain separate boundaries. Verification: go test ./internal/cli; go vet ./internal/cli; reasonix mcp help smoke test; stale-wording scan; git diff --check. * Harden MCP registry installs and SSE replies Problem: Registry cache freshness was shared across queries, cached entries could flow into install paths, and legacy SSE server requests spawned an unbounded reply goroutine per request. Root cause: The cache stored one global timestamp, Resolve reused Search's offline fallback, the desktop installed browse payloads directly, and the SSE reader delegated every reply to a new goroutine. Fix: Track freshness per Registry query with legacy-cache compatibility, require live resolution before CLI and desktop installs, and serialize SSE replies through a bounded single-worker queue with finite deadlines. Verification: - env -u DEEPSEEK_API_KEY go test -count=1 ./... - env -u DEEPSEEK_API_KEY go test -race -count=1 ./internal/mcpregistry ./internal/plugin - env -u DEEPSEEK_API_KEY go test -count=1 ./... (desktop) - env -u DEEPSEEK_API_KEY go vet ./... (root and desktop) - pnpm run test:all - pnpm run build - env -u DEEPSEEK_API_KEY ./scripts/cache-guard.sh - git diff --check * Reject unavailable live registry installs Problem: A Registry entry could become non-installable between browsing and the desktop's live re-resolution, leaving the generic add flow with an invalid empty connection. The SSE concurrency regression also used a fixed delay after routing the client response. Root cause: The desktop resolver verified freshness but not the resolved entry's installability, and the test inferred worker startup from elapsed time. Fix: Validate the live entry through PluginEntry before returning it to the installer, and synchronize the SSE test on the first reply POST instead of sleeping. Verification: - env -u DEEPSEEK_API_KEY go test -count=5 ./internal/plugin - env -u DEEPSEEK_API_KEY go test -race -count=3 ./internal/plugin - env -u DEEPSEEK_API_KEY go test -count=1 ./... - cd desktop && env -u DEEPSEEK_API_KEY go test -count=1 ./... - git diff --check |
||
|
|
43993f5a10 |
Fix plugin skill MCP tool bindings / 修复插件 Skill 的 MCP 工具绑定 (#6705)
* Fix plugin skill MCP tool bindings Problem: Plugin skills can reference raw MCP names or Claude-qualified names that are absent from Reasonix's provider-visible tool registry, causing otherwise valid tool calls to fail. Root cause: MCP registration canonicalized names, while skill imports preserved upstream instructions and allowed-tools metadata without a runtime binding layer. Fix: Add package-aware MCP capability bindings, resolve portable aliases only when unique, inject authoritative bindings when a plugin skill is invoked, canonicalize allowed-tools safely, and keep permission, hook, plan-mode, and audit decisions on the canonical name. Preserve provider schemas and use valid cached metadata for on-demand servers. Verification: - go test ./... - go vet ./... - ./scripts/cache-guard.sh - git diff --check * Harden runtime MCP skill bindings Problem: The initial binding layer narrowed wildcard allowed-tools entries, trusted an authored Markdown heading as an idempotence marker, and could mix stale boot-cache entries with a live server surface. Root cause: Pattern translation replaced rather than augmented existing patterns, preparation state was inferred from untrusted content, and cached bindings were appended without checking the session registry's authoritative servers. Fix: Preserve wildcard allowlists while supplementing only unmatched canonical names, track preparation with host-only Skill state, and skip cached bindings whenever that server already has registry bindings. Add focused regressions for all three contracts. Verification: - go test ./internal/skill ./internal/boot ./internal/tool ./internal/agent ./internal/plugin ./internal/control - go test -race ./internal/skill ./internal/boot ./internal/tool ./internal/agent ./internal/plugin ./internal/control - go test ./... - go vet ./... - ./scripts/cache-guard.sh - git diff --check * Prepare MCP bindings for all subagent entries Problem: Structured multi-skill subagent invocations and the headless subagent profile API could pass plugin skills to their runners without runtime MCP bindings. Root cause: Those two host entry points bypassed the preparation calls already used by run_skill, slash rendering, and dedicated wrappers. Fix: Prepare each structured subagent immediately before dispatch and prepare headless profiles before selecting their runner. Add focused controller regressions for both paths. Verification: - go test ./internal/skill ./internal/boot ./internal/tool ./internal/agent ./internal/plugin ./internal/control - go test -race ./internal/skill ./internal/boot ./internal/tool ./internal/agent ./internal/plugin ./internal/control - go test ./... - go vet ./... - ./scripts/cache-guard.sh - git diff --check * Scope on-demand MCP proxies in subagents Problem: A plugin subagent with an MCP-specific allowed-tools entry could lose that tool when the session exposed it only through use_capability. Granting the ordinary proxy would instead widen the skill to every capability. Root cause: Prepared allowlists carried only canonical direct tool names, and subagent registry filtering had no capability-specific proxy boundary. Fix: Carry stable mcp-tool capability IDs as host-only allowlist entries and synthesize a restricted use_capability wrapper only when no direct MCP binding exists. Enforce the ID boundary in both resolver and direct execution paths, including read-only subagents. Verification: - go test ./internal/skill ./internal/boot ./internal/tool ./internal/agent ./internal/plugin ./internal/control - go test -race ./internal/skill ./internal/boot ./internal/tool ./internal/agent ./internal/plugin ./internal/control - go test ./... - go vet ./... - ./scripts/cache-guard.sh - git diff --check |
||
|
|
4bd70c9fc1 |
Streamline MCP install and launch authorization / 精简 MCP 安装与启动授权 (#6669)
* Fix MCP handshake and trust recovery
Problem:
- Stdio MCP servers could block initialization when they sent a request among startup notifications.
- Workspace re-verification could launch a second copy of a single-instance server, while first-time project approval was mislabeled as re-verification.
Root cause:
- The stdio read loop treated every inbound method message as a notification and never answered server requests.
- Desktop trust preflight ran before disconnecting the shared live client.
Fix:
- Distinguish notifications from server requests, answer ping and unsupported methods, serialize writes, and preserve the final message before EOF.
- Stop the live server before exact trust preflight, restore or reconnect shared registries safely, and label first-time approval as Authorize and connect.
Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/plugin
- cd desktop && go test -race . -run TestSetMCPTrustWorkspaceRefreshesEverySharedHostRegistry -count=1
- cd desktop/frontend && pnpm test && pnpm typecheck && pnpm test:typecheck
- go vet ./...
- GOOS=windows GOARCH=amd64 go test -c -o /dev/null ./internal/plugin
- ./scripts/cache-guard.sh
- ./scripts/check-cache-impact.sh
- git diff --check
* Reply to MCP server requests off the read loop
Problem: readLoop answered server-initiated requests by writing to the
shared stdin pipe under writeMu. With both pipe buffers full, readLoop
could wait on writeMu held by a client call blocked on the jammed stdin,
while the server blocked writing the stdout that only readLoop drains — a
four-way deadlock. A failed reply write also killed the whole transport
even when the read side was still healthy.
Root cause: the only goroutine draining stdout was allowed to block on
the stdin write path.
Fix: route server-request replies through a bounded queue owned by a
dedicated replyLoop goroutine. readLoop enqueues without blocking and
drops replies on overflow, degrading to the pre-existing
unanswered-request behavior; reply write failures stop further replies
but are no longer terminal, since pipe errors keep surfacing through the
next client call's own write.
Verification: go test ./internal/plugin -race -count=1; focused
TestStdioReadLoopStaysLiveWhenReplyWriterIsBlocked and
TestStdioInitializeHandlesNotificationsAndServerPing -count=5 -race;
go vet ./internal/plugin; GOOS=windows GOARCH=amd64 go test -c
./internal/plugin; gofmt clean.
* Serialize MCP lifecycle mutations with trust preflight
Problem: SetMCPTrust snapshots each tab's enabled state and holds
runtimeRebuildMu through the trust preflight, but SetMCPServerEnabled,
ReconnectMCPServer, and the other MCP mutation entry points did not take
that lock. A disable issued while a workspace re-verification preflight
was in flight returned immediately, and the trust reconnect then
re-registered the server's tools from the stale snapshot — exposing
tools the user had just disabled and desynchronizing the
provider-visible registry.
Root cause: the trust preflight and the MCP enable/reconnect/edit entry
points mutated the same Host/Registry state under different locks.
Fix: route every Wails-exposed MCP mutation (inspect/set trust, add,
update, remove, reconnect, clear auth, enable/disable, tier, catalog
revocation apply) through lockMCPMutation, which serializes on
runtimeRebuildMu with the documented lock order runtimeRebuildMu ->
App.mu -> Host/Registry. Also refresh the stale transport interface
comment: server notifications are ignored, ping is answered, and
unsupported server requests get JSON-RPC method-not-found.
Verification: desktop go test ./... passed; focused
TestSetMCPTrustSerializesConcurrentDisable and
TestSetMCPTrustRestoresConnectionAfterPreflightFailure with -race
-count=3 passed; go build ./...; go vet; gofmt clean.
* Hold the MCP lifecycle lock across plugin uninstall
Problem: RemovePlugin disconnected the uninstalled plugin's MCP servers
through its OnDisconnect callback before rebuild() acquired
runtimeRebuildMu. Racing a workspace trust preflight, the unlocked
disconnect could interleave so the trust reconnect relaunched the
just-removed server from its stale snapshot, leaving an orphaned process
attached to the shared Host.
Root cause: the uninstall flow was the one remaining Wails MCP mutation
entry point outside lockMCPMutation.
Fix: RemovePlugin now holds the MCP lifecycle lock across the uninstall
and calls rebuildSettingLocked("plugins") so the rebuild does not
re-acquire runtimeRebuildMu. A desktop-wide sweep confirmed no other
live-MCP mutation site remains outside the lock.
Verification: TestRemovePluginSerializesWithTrustPreflight fails on the
unlocked implementation and passes with the lock (mutation-tested); the
four MCP lifecycle regressions pass with -race; desktop go test ./...
passed; go build ./...; go vet; gofmt clean.
* Make plugin uninstall atomic and clean every runtime
Problem: RemovePlugin checked for active runtime work before waiting on
the MCP lifecycle lock, so work starting during a lock wait (up to the
30s trust preflight) was only detected by the rebuild after the plugin
was already deleted — the user got a failure for an uninstall that had
happened. Its OnDisconnect callback also only disconnected the active
tab's controller: sibling tabs on the shared Host kept provider-visible
tools backed by the closed client, and other workspaces kept running the
uninstalled server.
Root cause: the pre-lock active-work check was trusted across the lock
wait, and the uninstall treated a global plugin as active-tab state.
Fix: RemovePlugin re-checks active work under the lifecycle lock and the
active tab's turn gate, holds that gate through the uninstall and
rebuild (rebuildSettingTurnLocked is the turn-gate-free split of
rebuildSettingLocked), and fails before deleting anything when work
started mid-wait. OnDisconnect now sweeps every visible and detached
runtime tab: DisconnectMCPServer stops the shared client once per Host
and drops the stale tool prefix from every other controller's registry.
Verification: TestRemovePluginRechecksActiveWorkUnderLock and
TestRemovePluginDisconnectsEveryRuntime both fail against the previous
implementation and pass with the fix (mutation-tested); the full
TestSetMCPTrust*/TestRemovePlugin* set passes with -race; desktop
go test ./... passed; go build ./...; go vet; gofmt clean.
* Gate every affected runtime during MCP trust and uninstall
Problem: RemovePlugin gated only the active tab's turn gate while
disconnecting every visible and detached runtime, so a background job on
a sibling tab let the uninstall complete and strip the running work of
its plugin MCP. SetMCPTrust checked that sharing controllers were idle
without holding their turn gates, so a turn starting between the check
and UnregisterMCPServerTools/DisconnectMCPServer lost its MCP tools
mid-flight.
Root cause: idle checks were done lock-free or under a single tab's gate
while the guarded mutation spanned every runtime sharing the state.
Fix: lockRuntimeTurnGates snapshots the affected visible and detached
tabs under runtimeRebuildMu, locks each turnStartMu in stable tab-ID
order, re-verifies active work under the gates (reading tab.Ctrl under
App.mu so detached runtimes count), and releases in reverse order.
RemovePlugin gates all runtimes across the uninstall and rebuild;
SetMCPTrust gates every tab sharing the Host across preflight, restore,
and reconnect. No path acquires turnStartMu before runtimeRebuildMu, so
the multi-gate order is deadlock-free.
Verification: TestRemovePluginRejectsBusySiblingRuntime,
TestRemovePluginRejectsBusyDetachedRuntime, and
TestSetMCPTrustWaitsForSiblingTurnGate all fail against the previous
implementation and pass with the gates (mutation-tested); the full
TestSetMCPTrust*/TestRemovePlugin* set passes with -race -count=3;
desktop go test ./... passed; go build ./...; go vet; gofmt clean.
* Freeze runtime work admission during MCP lifecycle mutations
Problem: lockRuntimeTurnGates assumed the runtime set was stable under
runtimeRebuildMu, but OpenTopicTab inserts tabs under App.mu only and
startTabControllerBuild attaches controllers from an unguarded goroutine.
A runtime appearing after the gate snapshot could start work while a
plugin uninstall was mid-flight, and during the 30s trust preflight a
newly built tab could attach to the same shared Host — relaunching a
single-instance server or leaving a registry the reverify never saw.
Root cause: the gates froze existing runtimes but nothing stopped new
work admission, so snapshot-then-gate had an unclosable enumeration race.
Fix: add runtimeAdmissionMu, a work-admission RW barrier. Turn admission
(beginTabTurn through finishTabTurnStart) and the async controller build
(buildTabControllerWithContext, covering the shared-Host attach and swap)
hold the read side; lockRuntimeTurnGates takes the write side before its
snapshot, making work admission — not tab-set stability — the invariant:
tabs created mid-hold stay inert until release. SetMCPTrust's affected
predicate now evaluates Host membership inside the gated snapshot and the
controllers list is computed after the gates, so controllers attached
just before the barrier are included. Writers acquire the barrier only
while holding runtimeRebuildMu (mutually exclusive), read holders never
acquire runtimeRebuildMu, and the order runtimeRebuildMu ->
runtimeAdmissionMu -> turnStartMu -> App.mu is one-directional, so the
barrier introduces no deadlock.
Verification: TestRemovePluginBlocksLateTurnAdmission (real beginTabTurn
path) and TestSetMCPTrustBlocksLateControllerBuild (real
startTabControllerBuild path) both fail with the barrier acquisitions
removed and pass with them (mutation-tested); the full
TestSetMCPTrust*/TestRemovePlugin* set passes with -race -count=3;
desktop go test ./... passed; go build ./...; go vet; gofmt clean.
* Harden MCP runtime lifecycle admission
Problem:
Concurrent turn admission, workspace repair, runtime teardown, and shared-host MCP edits could leak admission locks, deadlock behind a queued writer, close a captured Host mid-operation, drift to a newly active workspace, or leave sibling registries stale.
Root cause:
Admission ownership was split across callers, workspace repair recursively acquired the RWMutex read side, teardown unlinked some runtimes before entering the lifecycle barrier, MCP config helpers re-read active-tab state, and reconnect paths refreshed only one controller registry.
Fix:
Use an idempotent turn-admission token and admission-held build cores, extend the lifecycle barrier across unlink and teardown transactions, carry the invocation workspace root through config edits, and refresh every enabled controller sharing an MCP Host while preserving per-tab disabled state.
Verification:
go test ./...
cd desktop && go test ./...
go test -race ./internal/config ./internal/plugin
cd desktop && go test -race ./...
go vet ./...
cd desktop && go vet ./...
GOOS=windows GOARCH=amd64 go test -exec=true ./internal/plugin -run ^$
cd desktop && GOOS=windows GOARCH=amd64 go test -exec=true ./... -run ^$
./scripts/cache-guard.sh
git diff --check
* Fix desktop lifecycle lint findings
Problem:
The Desktop lint job rejected five dead admission wrappers left after the lifecycle refactor and an empty lock section in the queued-writer regression.
Root cause:
All production call sites had moved to admission-held cores, while the test used lock acquisition itself as an implicit signal that staticcheck could not observe.
Fix:
Remove the unused wrappers and publish an explicit channel signal while the writer owns runtimeAdmissionMu.
Verification:
cd desktop && go test -race -run ^TestBeginTabTurnWorkspaceRepairDoesNotRecursivelyLockAdmission$ -count=5
cd desktop && go test ./...
cd desktop && go vet ./...
git diff --check
* Stabilize MCP trust recovery and runtime teardown
Problem:
- Project MCP launch approval could invalidate itself after the first restart.
- Reconnect and trust recovery could select a shadowed user entry instead of the effective project entry.
- Provider teardown and shutdown could race runtime/MCP lifecycle work and leak shared-host references.
Root cause:
- Reasonix-owned offline launcher flags were included in the approved server identity.
- Lifecycle reloads used the edit-source lookup instead of merged runtime precedence.
- Several teardown paths bypassed the runtime admission barrier and did not release tab host ownership.
Fix:
- Separate canonical launcher identity arguments from enforced process arguments for npx, bunx, and uvx.
- Reload effective merged MCP entries for reconnect, toggle, and trust restoration.
- Serialize provider teardown and shutdown with runtime admission, gate live turns, and release affected shared hosts.
- Add deterministic regression coverage for authorization stability, source shadowing, lifecycle ordering, and host cleanup.
Verification:
- go test -race ./...
- cd desktop && go test -race ./...
- go vet ./... in both Go modules
- frontend tests, test typecheck, and production build
- cache guard
- Windows and Linux cross-compilation of affected packages
* Close MCP trust and runtime lifecycle gaps
Problem:
- Runtime teardown could close a controller while a foreground turn was still entering it.
- A stored uvx --from launcher lock split the option from its package value.
- Revoking project MCP trust left the authorized process alive and hid the recovery action.
Root cause:
- Ordinary teardown used the shared admission side, launcher enforcement inserted at the locator value, and connected revocation only updated trust receipts.
Fix:
- Serialize teardown with the exclusive runtime mutation barrier, keep uvx --from pairs adjacent, and disconnect every shared registry while preserving an actionable launch-approval status after revocation.
Verification:
- go test -race ./... (root and desktop modules)
- go vet ./... (root and desktop modules)
- pnpm test:all && pnpm build
- ./scripts/cache-guard.sh
- make cross and Windows desktop test compilation
* Synchronize job migration notice test
Problem:
- Linux race CI found concurrent writes to a test-owned notice slice while exercising artifact migration failure reporting.
Root cause:
- The manager can emit migration and completion notices from different goroutines, but the FuncSink test collector appended and read the slice without synchronization.
Fix:
- Guard notice collection with a mutex and inspect a synchronized snapshot.
Verification:
- go test -race ./internal/jobs -run ^TestSetActiveSessionPathReportsMigrationFailure$ -count=100
- go test -race ./internal/jobs -count=10
- go test -race ./...
- go vet ./...
* Bound bot config concurrency regression
Problem:
- Windows Go 1.26.5 CI repeatedly crashed with found pointer to free object while this lock-discipline test ran, including on main-v2 and unrelated pull requests.
Root cause:
- The test used a stop-driven background writer whose total work depended on reader and runner timing. A prior 1 ms throttle reduced but did not eliminate the unbounded allocation and GC pressure.
Fix:
- Start fixed reader and writer loops together, yield between iterations, and wait explicitly for the bounded writer to finish. The loops remain unordered so race detection still covers missing map locks.
Verification:
- focused normal test count=500
- focused race test count=200
- internal/bot normal count=20
- internal/bot race count=5
- go test -race ./...
- go vet ./...
* fix(mcp): simplify install and authorization flow
Problem:
Installing an MCP server could still lead to redundant launch and tool trust prompts, while the desktop surfaced low-level trust, isolation, and catalog maintenance controls during normal setup.
Root cause:
Project-scoped install_source entries lost their live provenance, durable launch grants did not imply ordinary-call approval, and cached placeholders were built before the established grant was applied.
Fix:
Preserve install provenance, record and roll back durable project launch grants, apply exact matching grants to live and cached tools, and reduce the desktop and CLI setup flow to add-and-connect with one confirmation only for repository-discovered servers.
Compatibility:
No persisted schema or config fields changed. Existing receipts and legacy trust, untrust, verify, and catalog commands remain supported, and MCP tool names, schemas, and registration order stay unchanged.
Verification:
- go test ./...
- pnpm test:all
- pnpm build
- git diff --check
* refactor(mcp): remove legacy trust workflow
Problem:
MCP installation and use still exposed session trust receipts, capability snapshots, re-verification, revocation, and catalog maintenance steps that interrupted normal development workflows.
Root cause:
Launch authorization and tool-call safety were coupled to a large trust state machine, so user-configured servers and repository-discovered servers shared unnecessary security UI, CLI commands, event payloads, and persisted authority.
Fix:
Replace the trust state machine with exact project launch grants, remove legacy Desktop and CLI trust surfaces, keep add-and-connect direct for user-configured servers, preserve old raw state for cross-version compatibility, and retain destructive approval, sandbox, signed revocation, and explicit read-only boundaries.
Verification:
- go test ./... and go vet ./... in root and desktop modules
- pnpm test:all and pnpm build
- focused race tests for MCP launch, plugin, agent, authorization, reconnect, and update paths
- cache guard and Windows cross-compilation
- browser verification of the simplified MCP settings flow
- git diff --check
* fix(mcp): allow incomplete drafts to return to form
Problem:
Switching an untouched or structurally valid incomplete MCP JSON draft back to the form editor was blocked.
Root cause:
Mode switching reused submission-level validation, which requires a non-empty command or URL even for the generated empty template.
Fix:
Allow incomplete connection fields only while converting JSON drafts back to the form, preserve untouched form state, and keep final submission validation strict. Add regression coverage for empty and edited incomplete drafts.
Verification:
- pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
- pnpm typecheck
- pnpm test:typecheck
- Manual in-app browser mode-switch verification
* fix(mcp): honor install authorization for destructive tools
Problem:
MCP servers explicitly installed or launch-authorized by the user still prompted again whenever a tool declared destructiveHint, preventing the intended add-and-use experience.
Root cause:
The MCP permission gate evaluated destructive metadata before the effective approve policy, so the source-aware authorization default could never apply to those calls.
Fix:
Keep explicit deny as the highest priority, then let an effective approve policy run directly before destructive review. Preserve fresh review for destructive calls under auto, prompt, and writes; update lazy safety messages, tests, and public documentation to match the policy.
Verification:
- go test ./...
- go test ./internal/permission ./internal/control ./internal/plugin ./internal/agent
- pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
- pnpm typecheck
- pnpm test:typecheck
- git diff --check
* fix(mcp): remove overwritten launcher preparation
Problem:
The PR lint job failed with staticcheck SA4006 in authorizeSpecLaunch.
Root cause:
The prepared spec was initialized before an if/else where both branches assigned it again, making the initial value unused.
Fix:
Declare the prepared spec without the redundant initial assignment while preserving both launch preparation paths.
Verification:
- go test ./internal/plugin
- go vet ./internal/plugin
- git diff --check
* test(mcp): make launch checks portable on Windows
Problem: Windows CI failed on POSIX-only permission semantics and could not remove a hard-linked running test executable.
Root cause: the tests assumed Unix mode bits and reused the active test binary as an MCP launcher fixture.
Fix: limit the permission-bit assertion to non-Windows hosts and use an inert executable fixture that is never held open by the test process.
Verification: go test ./internal/mcplaunch ./internal/plugin; go test ./...; GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go test -c for both affected packages.
|
||
|
|
8f2c209ac1 |
Fix MCP persistent sessions and simplify trust / 修复 MCP 持久会话并简化授权 (#6612)
* fix(mcp): preserve stateful connections and simplify trust Problem: MCP identity drift invalidated existing trust receipts, while per-call writer processes repeatedly prompted stateful servers and added startup latency. Root cause: Host-local isolation and config-source details leaked into stable identity matching, and stdio writers used a separate one-shot process path that discarded server state. Fix: Migrate compatible legacy receipts, keep identity stable across host-policy changes, apply source-aware launch authorization, and reuse one persistent stdio process for reads and writes without an extra writer-isolation setting. Verification: - env -u DEEPSEEK_API_KEY go test ./... - cd desktop && env -u DEEPSEEK_API_KEY go test ./... - cd desktop/frontend && pnpm test:typecheck - cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts - ./scripts/cache-guard.sh - git diff --check * test(mcp): use real sandbox paths in migration coverage Problem: The Ubuntu CI runner rejected the MCP migration test before startup because bubblewrap could not bind a hard-coded /workspace path that did not exist. Root cause: The test used illustrative absolute paths for enforced sandbox roots instead of creating filesystem fixtures. Fix: Create real temporary workspace and forbidden-read roots, then use them consistently for the trust manager and sandbox policies. Verification: - go test ./internal/plugin -run TestLegacyPolicyCoupledIdentityAndWorkspaceSourceMigrate -count=1 - go test ./internal/plugin -count=1 - git diff --check * fix(mcp): complete add-and-use trust flow Problem: - Windows trust-state writes could fail during lock-file delete/create races. - /mcp add did not receive user-authorized policy until the next restart. - Project MCPs kept showing launch authorization after connecting and offered two scopes for a one-time launch gate. Root cause: - Windows can report access or sharing violations instead of file-exists during exclusive-create lock handoff. - The Controller add path connected an entry before marking its saved user-config provenance. - Desktop exposed source policy as current blocked state and reused the generic trust-scope modal. Fix: - Retry transient Windows lock handoff errors within the existing deadline. - Mark explicit Controller adds as user-configured before live connection. - Show project launch authorization only while blocked and collapse it to one durable workspace action. - Document the persistent writer-sandbox tradeoff accurately. Verification: - env -u DEEPSEEK_API_KEY go test ./... - cd desktop && env -u DEEPSEEK_API_KEY go test ./... - cd desktop/frontend && pnpm test:typecheck - cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts - go test ./internal/mcptrust -count=20 - GOOS=windows GOARCH=amd64 go test ./internal/mcptrust -c - ./scripts/cache-guard.sh - ./scripts/check-cache-impact.sh - git diff --check * fix(agent): resolve on-demand MCP approval through the shared spec policy The delivery-mode on-demand proxy re-derived the approval mode from raw Spec fields and missed ImplicitApproval, so a user-configured server that runs with direct approval on the normal MCP path prompted again under the global Ask posture. Export plugin.Spec.ToolApprovalMode as the single resolution and route every MCPApprovalPolicy implementer through it. * fix(desktop): keep a revoke entry for authorized project MCP servers Hiding the whole trust panel once a launch-gated server connects also hid the only Revoke trust button, leaving persistent project authorization irrevocable from the desktop UI. Split the static launch-gate governance flag from the blocked-now flag and show a calm revoke row for servers whose workspace/session grant is currently established. Revoke stays hidden for user-config and official sources where auto-trust would silently re-trust on the next evaluation. |
||
|
|
0998795927 |
fix(desktop): surface MCP re-verification action (#6605)
Problem: MCP identity drift was blocked before startup, but the server list still offered Retry, which could only repeat the same preflight failure. Root cause: Recorded MCP failures kept only a display string, so the desktop could not distinguish re-verification requirements from ordinary connection failures. Fix: Preserve identity-drift failures as structured state, surface Reverify directly in the server row, keep OAuth and ordinary retry precedence, and reconnect failed servers after explicit trust confirmation. Verification: - go test ./internal/plugin - go test ./... (with the live DeepSeek probe disabled) - cd desktop && go test ./... - pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts - pnpm typecheck - pnpm test:typecheck - pnpm check:css |
||
|
|
9bbd138695 |
fix(agent): make reader authorization tamper-proof at MCP dispatch
Reader-authorized calls now carry an immutable execution intent into dispatch: the agent pins the reader basis plus the capability fingerprint in the call context, and remoteTool.ExecuteWithImages re-verifies both linearizably under the owning client's toolsMu — the same lock every trust mutation (catalog revocation, live security reconciliation) takes. A call approved as a non-destructive reader can therefore never promote itself into the one-shot writer lane or reach tools/call after a concurrent revocation or reclassification; it returns an actionable re-verify error with zero extra process starts and zero tool calls, while writer-approved calls keep their lane unchanged. Covered by a helper-counter regression that flips trust between authorization and dispatch and asserts both counters stay flat, plus a stale-fingerprint refusal. Strict read-only execution now also requires a positive trust authority for MCP-backed tools: the no-TrustManager compatibility paths (server hints, legacy read-only config) keep their historical classification for ordinary sessions but can no longer admit tools into a strict child's registry or its on-demand host-startup gate. A missing trust store fails closed with zero starts and zero executions. NewReadOnlyAgent joins RunReadOnlySubAgentWithSession as the shared construction pairing (permanent ReadOnlyExecution flag plus the final registry filter), and the two-model planner now builds through it instead of assembling the pieces by hand. |
||
|
|
5f5c869a94 | Merge remote-tracking branch 'origin/main-v2' into codex/pr6498-readonly-boundary | ||
|
|
60fc21f9d2 |
fix(mcp): migrate legacy receipts at the pre-start gate, widen credential keys
The pre-start identity gate returned before initialize/tools-list while the legacy URL migration only ran inside the post-handshake evaluation, so eager and cache-miss servers with a pre-rollout receipt were blocked with a manual re-verification instead of migrating. start() now upgrades a receipt whose identity exactly matches this spec's legacy URL fingerprint before treating the identity as changed; no capabilities exist at that point, so the drift comparison is skipped there (the tool snapshot is untouched and the post-handshake evaluation still runs the full drift check). Covered by a real StartAll regression over the Streamable HTTP harness: legacy receipt starts and migrates, credential rotation keeps starting, and a moved resource scope still blocks before any network handshake. The credential query classifier now recognizes whole families by normalized suffix (*token, *secret, *password/*passwd, *apikey, *signature) plus an explicit key/auth family (key, access_key, secret_key, private_key, auth_key, app_key, client_key, subscription_key, shared_key, bearer, credentials), so auth_token, refresh_token, id_token, and x-api-key values stay out of identity/cache hashes while workspace, tenant, region, and similar resource parameters remain identity-bearing. |
||
|
|
a7536482c3 |
fix(mcp): credential-aware identity fingerprints and exact launcher pins
- normalizeIdentityURL redacts URL userinfo and credential query values (token, access_token, api_key, authorization, auth, password, passwd, secret, client_secret, credential, signature, sig — matched case-insensitively after removing separators) while keeping resource scope parameters such as workspace and tenant; query keys sort stably and fragments drop. Only the placeholder structure survives, so credential rotation keeps trust while a scope change still re-verifies. Network requests keep using the raw configured URL. - The previous normalization survives as a read-only legacy calculator: managerEvaluate migrates receipts and LoadCachedSchemaForSpec rewrites schema-cache entries in place when nothing else drifted, so the rollout and credential rotation never force a re-trust or a re-handshake. Removal window: two minor releases (docs/MIGRATING.md). - managerEvaluate is the single evaluation entry for every executing path — live handshake, lazy cache preflight, cached-tool trust, and the trust refresh — and routes official servers through EvaluateOfficial with the current signed reader allowlist. - Git launcher pins accept exactly complete 40- or 64-hex object names; intermediate lengths resolve through git ls-remote like any ref. uvx pins reject PEP 440 wildcard versions while keeping exact prerelease, post, dev, epoch, and local pins, and the official catalog validation shares both predicates with mutable launcher locks. - slug() suffixes Windows-reserved device stems (CON, PRN, AUX, NUL, COM1-9, LPT1-9) so cache JSON, stats files, and MCP state directories never name a DOS device; ordinary safe names stay byte-identical and old reserved-name paths are not migrated (they never worked on Windows). |
||
|
|
3212786e45 |
fix(mcp): harden identity, cache, and transport trust boundaries
Sweep of the remaining trust-boundary gaps around the reviewed MCP security fixes, each with a focused regression test: - Official catalog servers: reverify the installed package digest before any process or network startup, bind the catalog transport to the configured transport, and require immutable launcher locators (exact npm/PyPI versions or full Git commits). Desktop and CLI inspect/revoke flows resolve the same official trust metadata as boot, so they can no longer downgrade an official server to a custom spec, and a manual catalog refresh applies fresh revocations to every live shared host immediately. - Identity fingerprints: environment key case folds only on Windows (env names are case-sensitive on Unix), forbid_read roots join the identity, display-field stripping no longer erases user-defined schema properties named like annotations, and receipts never match across different config sources. - Schema cache: SpecFingerprint binds the server name and normalized URL, sanitized slugs get a content hash so confusable names cannot share cache files or MCP state directories, and cache writes go through the shared atomic-write helper so Windows replaces work under concurrent readers. - Tool listing: reject empty or duplicate raw tool names before they can alias each other's approvals, and give one-shot writer lanes the same bounded settle window as the reader lane. - HTTP transport: configured credential headers are no longer resent when a server redirects to a different origin. - Concurrency: remoteTool security fields are read through a snapshot taken under the owning client's toolsMu; the trust preflight builds its cached snapshot outside that lock (a snapshot inside the critical section deadlocked SetSpecTrust). Lowercase-safe cache slugs keep their filenames; mixed-case or punctuated server names re-fetch their schema cache once after upgrade. Portable tree digests now exclude POSIX modes and disable autocrlf so signed catalog packages verify identically on Windows checkouts. |
||
|
|
83639eb0e5 | fix(mcp): close trust and lifecycle review gaps | ||
|
|
3ff177d268 | fix(agent): unify strict read-only MCP execution boundary | ||
|
|
5fcca24382 | fix(mcp): preserve trusted reader preflight snapshots | ||
|
|
c636f017a8 | feat(mcp): enforce trusted execution boundaries | ||
|
|
4f89c15c2e | Add layered MCP approval policy | ||
|
|
bd0dc0c079 | feat(mcp): simplify settings and align tool approvals | ||
|
|
ed113b4909 | fix(mcp): quarantine tools with invalid schemas | ||
|
|
3cfc30d421 | fix delivery capability enforcement gaps | ||
|
|
f6496b3ecb |
fix: canonical MCP tool names, delivery-aware routing, strict review evidence
Address the second review round on the Delivery capability runtime: - canonical names (P0): export plugin.ModelToolName (collision-hash aware) and use it for every proxy-resolved target; the proxy-local normalizeToolToken is deleted, so permission/hook deny rules written for the executed name (e.g. raw tools with @, /, spaces) always match - delivery routing (P1): RouteDecision carries a Delivery flag; the transient route block keeps concrete mcp-tool ids and instructs use_capability(action="call", ...) instead of connect_tool_source, which is not registered in Delivery - catalog persistence (P1): the proxy snapshots host-observed live tools of on-demand connected servers (never touching the registry); boot and controller pass the snapshot as CatalogOptions.ProxyTools so mcp-tool entries stay routable after the server turns ready, without duplicating registry-backed servers - review evidence (P1): HasReadEvidenceForPath replaces the observed- path check — only read receipts (exact or fuller-path slash-suffix) and content-revealing commands (diff/cmp/cat/head/tail, git diff/ show) count; git status/echo mentions, write receipts, and reverse basename suffix matching are rejected - windows CI (P1): review_report error text lists reviewed paths in slash-canonical form so messages and tests match across OSes - metrics (P2): action=call proxy audit moves to the agent loop's single post-execution point (no double count, successes now counted); capabilityGateFailure tracks per-turn misses and records RequireRecovered/PreferRecovered via the new RecordGateRecovery when the gate later passes clean Provider-visible schemas and tool ordering remain unchanged; on-demand tools still stay off the main registry (cache-guard 8/8 pass, tail hit rate 92-95%). |
||
|
|
270c948724 |
fix: address capability diagnostics review findings
Make static collection truly read-only (LoadForRootReadOnly), harden error redaction for paths/PATH/tokens, fix Windows pipe capture deadlock, surface unknown Hook events, use HasTools for no-tools warnings, skip live MCP cache persistence, and take an atomic desktop tab snapshot with last-request-wins UI. |
||
|
|
2e854a5cb7 |
fix(mcp): carry tool result images structurally instead of inline markdown
Rework of the previous two commits, addressing the review blockers: - Tool output text passes truncateToolOutput (32KB head+tail splice) before providers see it, so markdown-inlined base64 larger than the budget was spliced and the extraction regex captured corrupted payloads across the gap - an API 400 for every real screenshot. Images now ride outside the text: parseToolResult returns validated data URLs (mime whitelist, 4MB/5-image budget, base64 check) plus a short text placeholder, remoteTool implements the new tool.ImageTool optional interface, and the agent stores the images on the RoleTool session message untouched by truncation. - The OpenAI API rejects image_url parts under role "tool"; tool images are now injected as a user message after the turn's tool-result run. - Non-vision models (e.g. DeepSeek) no longer receive raw base64 text; they see only the placeholder, matching the existing user-image gate. Anthropic keeps the tool_result array-content shape (text + image blocks); text-only results serialize byte-identical to before, guarded by regression tests on both providers. |
||
|
|
238e0afdf3 |
fix(plugin): include MCP image content in tool results (#6169)
parseToolResult only handled MCP content items of type 'text', silently dropping type 'image' items that carry base64-encoded image data. This caused MCP tools returning images (e.g. photoshop_get_preview, take_screenshot) to have their images removed before reaching the LLM. Now parseToolResult recognizes type 'image' content and converts it to a markdown image reference (data URI) so the LLM can see it. |
||
|
|
90e7b1d531 | fix mcp tools list startup race |