Commit Graph
159 Commits
Author SHA1 Message Date
esengine ec866730e0 fix(gitcmd): host git never runs repository-configured programs
Every git invocation the host makes goes through gitcmd, and each one must
leave the programs a repository's own configuration names unexecuted:
filter and merge drivers, textconv and external diff, fsmonitor, hooks,
signature verifiers, submodule-boundary programs and lazy-fetch transport.
Driver neutralization now covers every subcommand, not diff alone, and takes
driver names from git itself rather than from a separate reading of .git/config.

- Fixed keys are overridden with -c: core.fsmonitor (empty, not "false",
  which git before 2.35.2 reads as a hook name), core.hooksPath,
  log.showSignature, merge.verifySignatures, diff.submodule, gc.auto and
  maintenance.auto and safe.bareRepository=explicit; submodule.recurse=false
  for checkout, switch, restore, reset, merge and read-tree (host branch
  switches and merges never update submodules, whatever the user's global
  submodule.recurse), and elsewhere only where the repository's own config
  sets it.
  GIT_NO_LAZY_FETCH=1 stops a missing object becoming a fetch through the
  repository's remote (git 2.44+); a historical patch that fails for that
  reason returns gitcmd.ErrObjectNotLocal, decided by git's own
  --missing=print listing.
- diff, log and show carry --no-ext-diff and --no-textconv; status and diff
  carry --ignore-submodules=dirty. The subcommand is found past global
  options, so a call passing -C inside args is hardened too.
- Filter and merge drivers defined at local or worktree scope are listed by
  git itself before each invocation (includes followed) and overridden empty;
  user and system scope stay live; a name -c cannot address fails closed
  (ErrRepositoryDrivers).
- worktree add uses --no-checkout and populates through gitcmd inside the new
  worktree, so a conditional include keyed on the new gitdir or branch is
  seen by the driver listing there rather than run by the add's checkout.
  The reset takes the worktree-mutation timeout, and a failed reset removes
  both the worktree and the branch the add created.
  worktree remove uses --force after the existing hardened clean-tree check,
  so git does not run status inside the linked worktree with its config.
- Launcher ls-remote runs Detached with gitcmd's environment (only PATH is
  taken from the launcher's declared env), requires an https://, ssh:// or
  git:// remote (case-insensitive; http:// and local paths are refused), and
  passes the remote and ref past -- so a leading-dash value is an operand.
- worktree git calls with extra environment keep gitcmd's environment.

- Repository identity is resolved once, when a session opens: boot runs
  gitcmd.Open (rev-parse --absolute-git-dir --git-common-dir --show-toplevel)
  before any command, the controller carries the resulting gitcmd.Repo, and a
  rebuild of the same workspace passes it on. Every later host call runs
  through the Repo with GIT_DIR, GIT_COMMON_DIR and GIT_WORK_TREE set, so a
  nested .git, a commondir file, a rewritten .git file or a damaged HEAD
  written afterwards cannot substitute another repository or its config; a
  damaged HEAD fails closed. Threaded through the TUI status line, cli
  review (resolved once before the review agent runs), the desktop changes
  panel, change detail, history, commit detail, branch list/checkout/create
  and tab branch label (the tab session's identity; a tab with no session
  yet resolves at display), worktree Inspect/Create/RollbackCreate, and the
  whole merge-back flow: the source and worktree identities are recorded in
  the merge metadata at creation (the worktree's resolved right after
  worktree add) in metadata.repo.json beside metadata.json, whose shape
  older builds decode strictly and so is unchanged; every inspect,
  auto-commit, merge and cleanup call runs against them, and a worktree
  without that file resolves them from the roots.
  InspectMerge finds the allocation root from the managed layout instead of
  asking git in the worktree.
- Repo.Verify applies Open's test to a stored identity; the identities read
  from metadata.repo.json are verified before the merge flow uses them.
- Auto-commit staging uses gitcmd Repo.StageAll: gitlinks present as
  directories and untracked nested repositories are excluded with
  :(exclude,literal) pathspecs, because add -A checks a submodule by running
  git inside it under its own config. A gitlink keeps the commit the index
  records; an untracked nested repository leaves the worktree unverifiable
  and the auto-commit refuses. The worktree state token no longer runs git
  inside a changed nested repository.
- Open accepts only a git dir belonging to the work tree that holds the
  directory: the work tree's own .git (sharing no other common dir) or a
  linked worktree's admin dir under <common>/worktrees whose gitdir file
  names that work tree's .git. A .git file or core.worktree pointing at
  another checkout is ErrNotRepository. Submodule checkouts and
  --separate-git-dir repositories opened directly are therefore treated as
  not version-controlled.

Regression tests use real git with payload scripts: the gitcmd config-key
matrix (signatures, lazy fetch, submodule boundary), the worktree call sites
(conditional includes on Create, linked-worktree config on RollbackCreate),
the status readout, the launcher's non-URL remote rejection, identity
pinning after a nested .git, a commondir file and a damaged HEAD (config
origin and filter marker), Open refusing another checkout's git dir (.git
file, core.worktree, foreign admin dir), StageAll with nested repositories
named like pathspec magic or globs, metadata.json read by a strict older
decoder, merge-back without the identity file, StageAll and merge-back auto-commit past a gitlink
with its own filter, merge-back after the worktree's .git file is pointed at
another git dir, the status line and desktop changes panel of a workspace
that later gains its own .git, and a global submodule.recurse=true against
host checkout and reset. The list-then-run race is left to a follow-up that
denies sandboxed writes to .git config and hooks.
2026-09-27 17:35:16 -07:00
esengine 9083943095 fix(plugin): decode an MCP server's code-page stderr before reporting it
On a Chinese Windows, cmd.exe reports a missing command in the console
code page (GBK), not UTF-8. The stdio transport kept the stderr tail as
raw bytes, so an MCP startup failure such as "'read-only-mysql-mcp-server'
is not recognized as an internal or external command" reached the settings
page as U+FFFD mojibake and the user could not see why the server failed.

The stderr tail now goes through encoding.DecodeOutput, the one decoder
for process output. The buffer keeps the last 16 KiB, so only its head
can be cut: tailBuffer records when it dropped bytes and passes
Cut{Head: cut}. The end is never cut, because it is whatever the server
last wrote. Output that is not UTF-8 is decoded from the raw bytes, so a
GBK tail that begins with a CJK character keeps that character.

Tests:
- TestStdioFailureDecodesCodePageStderr starts a real child that writes
  GBK to stderr and exits.
- TestStderrTailDecodesTheServersCodePage covers GBK that starts with a
  CJK character, both uncut and cut, plus UTF-8 cut mid-character.
All of them fail on origin/main-v2 and pass with this change.

Refs #7857
2026-09-26 13:52:39 -07:00
yhh 7d9e05a587 fix(provider): root object schemas always carry a required array (#10614)
Relays that re-serialize tool schemas into typed structs turn an omitted
"required" into null, and strict upstreams then reject the whole request:
"Invalid schema for function 'get_goal': null is not of type 'array'".
get_goal's schema has no required list, and neither do many MCP tools.

CanonicalizeSchema now adds "required": [] to every root object schema
that lacks one (including the no-argument fallback), so builtin and MCP
tools reach the provider with an explicit array. The effect test checks
every tool in the real provider request.

Cache impact: the tool schema bytes change once (get_goal gains
"required": []), so ToolsHash and PrefixHash move in both golden
baselines and the first request after upgrade misses the prefix cache.
Goldens regenerated with REASONIX_UPDATE_GOLDEN=1.
2026-09-25 03:45:56 -07:00
yhh 4ba2fde3d1 Merge #8204: fix(plugin): fail closed when confined MCP loses its sandbox backend 2026-09-24 23:03:27 -07:00
SivanCola 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.
2026-09-20 12:53:32 +08:00
SivanCola 08ca31641a Fix Windows path identity and lock layering
Problem:
Windows native tests failed every identity-aware file lock with ERROR_NOACCESS, while the first path-identity integration also exceeded repository size, complexity, and layering limits.

Root cause:
The Windows implementation queried FileCaseSensitiveInfo through GetFileInformationByHandleEx, which is rejected on the CI runner. The utility-layer filelock package also imported the higher-level pathidentity package directly, and several identity workflows remained embedded in oversized owners.

Fix:
Query per-directory case sensitivity with NtQueryInformationFile, preserving explicit unsupported-information fallbacks. Add an identitylock composition layer that resolves access paths and passes comparison keys into filelock's explicit local-queue API, then migrate production file locks to it. Keep custom session, repair, and workspace lock protocols on explicit keys, and extract path and acquisition logic into focused owner files.

Verification:
- go run ./tools/repolint
- go test -run '^$' ./...
- go test -race ./internal/filelock ./internal/identitylock ./internal/workspacelease
- Windows amd64 cross-compile for affected root packages
2026-09-18 19:32:41 +08:00
SivanCola 9ec875a684 Stabilize Windows release validation / 稳定 Windows 发版验证 (#10468)
* Stabilize Windows release validation

* Respect test file size baseline

* Make lazy cache-miss test await publication

Problem:
The race CI could observe the MCP host client before the lazy registry swap completed, making TestLazyCacheMissAsyncSpawn fail even though the product state machine was still progressing correctly.

Root cause:
The test polled Host.ServerNames, but host registration occurs before lazySpawn publishes real tools and closes its completion signal.

Fix:
Drive the advertised second-turn connect call, which waits on the production completion signal, before asserting the registry contents.

Verification:
- go test -race ./internal/plugin -run '^TestLazyCacheMissAsyncSpawn$' -count=50
- go test -race ./internal/plugin
- go run ./tools/repolint
- git diff --check

* Remove machine-speed budget from transcript test

Problem:
The Windows control CI failed when a correct transcript history preparation exceeded a test-local five-second deadline on a loaded runner.

Root cause:
The regression tied the durable-cut contract to filesystem and runner throughput even though adjacent lifecycle coverage already uses the test context and the product operation correctly honors caller cancellation.

Fix:
Use the test lifecycle context while preserving every ready-state, durable-sequence, page-boundary, and final-message assertion.

Verification:
- go test ./internal/control -run '^TestTranscriptFollowInitialHistoryIsReadyAndPinnedToDurableCut$' -count=50
- go test ./internal/control
- GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go test -c ./internal/control
- go run ./tools/repolint
- git diff --check
2026-09-18 02:25:37 +08:00
SivanCola 8f93ea2b2e Adopt SessionID-only Desktop persistence / 升级 Desktop SessionID 会话架构 (#10326)
* feat(session): add immutable desktop session headers

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* feat(desktop): add durable workspace session registry

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* refactor(desktop): use one canonical session service

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* feat(desktop): bind canonical sessions to workspaces

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* feat(desktop): make session lifecycle registry transactional

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* feat(desktop): complete session id lifecycle cutover

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* feat(desktop): replace topic sidebar with session browser

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* docs(desktop): document session v5 authority

Problem: Desktop session identity and persistence diverged across tabs, topics, and workspaces.

Root cause: Local routing depended on project paths and disposable Topic projections instead of one durable SessionID authority.

Fix: Advance the staged SessionID-only Desktop cutover while preserving remote and provider-visible behavior.

Verification: Focused owning-package tests cover this stage; the assembled branch is validated by local gates and the pull request CI matrix.

* refactor(desktop): isolate session persistence plumbing

Problem: The SessionID cutover added lifecycle, fallback, and persistence logic directly to legacy hotspot files, exceeding incremental repository quality budgets.

Root cause: New Desktop session authority state and controller fallback paths were represented as independent App and WorkspaceTab scalars and inline branches.

Fix: Group Desktop session state by lifetime, extract controller/model fallback and prune persistence helpers, relocate the focused prune regression test, and ratchet repolint baselines to the new lower aggregate counts.

Verification: go test focused Desktop SessionID persistence/migration/fallback cases; go vet for root session/control and Desktop modules; frontend typecheck, hooks lint, workspace browser test, CSS checks; repolint and git diff checks.

* fix(frontend): account for SessionRef bridge payload

Problem: App memory preparation failed because the SessionID cutover exceeded the initial raw JavaScript and CSS budget by 4.6 KiB, while every compressed and per-chunk budget remained green.

Root cause: SessionRef bridge methods plus Workspace Session browser locale and type bindings add a small unavoidable startup payload.

Fix: Ratchet only the initial raw payload ceiling from 2434.2 KiB to 2439.0 KiB, preserving 0.2 KiB headroom and leaving gzip, CSS, and chunk limits unchanged.

Verification: pnpm build passes all lint, architecture, CSS, typecheck, Vite build, and bundle budget checks at 2438.8 KiB measured raw payload.

* fix(desktop): complete SessionRef navigation contract

Publish an explicit hydration-ready signal after an exact SessionRef commits, and expose active identity plus bounded expansion in the Workspace Session browser.

Move all local browser benchmarks onto one layout-independent session navigation helper, align the browser mock with native hydration semantics, and refresh generated inventory and lint contracts.

* fix(bench): await session projection readiness

Treat Composer readiness and Workspace Session projection readiness as independent phases before deciding whether a benchmark target requires bounded expansion.

* fix(frontend): preserve tab identity on mock session open

* fix(session): close projection workers at shutdown

* test(desktop): close session services deterministically

* fix(desktop): fence canonical sessions by SessionRef

Problem:
Canonical Desktop tabs could still key caches, hydration and imported workspace membership through compatibility paths. Empty paths could collide, and legacy imports could publish a session before immutable workspace ownership existed.

Root cause:
SessionRef was not carried through every Meta and clear transition, async fences compared partial identities, and the legacy import publication did not atomically include the v5 SessionHeader.

Fix:
Use full SessionRef plus generation for frontend cache and hydration identity; return SessionRef from Meta and clear; validate registry attachment against immutable header cwd; publish legacy and paired-preview imports with manifest and header in one atomic directory publication; add targeted identity, import and workspace tests. Merge the latest main-v2 and regenerate protocol and Desktop inventory artifacts.

Verification:
- go test ./internal/session ./internal/control ./tools/desktopinventory -count=1
- go test ./desktop targeted protocol/session/workspace regressions
- go test -race targeted internal/session and desktop identity tests
- frontend SessionRef/hydration/cancel suites and test:typecheck
- pnpm build with existing bundle budgets
- go run ./tools/repolint

* fix(session): remove obsolete import wrapper

Problem:
The SessionHeader import refactor left the old headerless publication wrapper unreachable, and CI's unused check correctly rejected the branch.

Root cause:
All callers had moved to publishLegacyImportWithHeader, but the compatibility helper remained after the cutover.

Fix:
Delete the dead wrapper and document the single atomic Header-aware publication path.

Verification:
- golangci-lint run --timeout=5m ./...
- go test ./internal/session targeted import and query tests
- git diff --check

* fix(session): publish host-owned headers on legacy rebuild import

Problem:
Desktop model and settings changes that rebuilt a legacy-path session failed
with "session header does not match requested ownership", so the workspace
could not restart with the corrected root and history stayed unloadable.

Root cause:
Rebuild imported the legacy transcript through the one entry point that
published no immutable SessionHeader. The later create validation compared the
requested Desktop ownership against a session directory that carried no header,
so every rebuild-imported session was permanently unusable rather than merely
unlabelled.

Fix:
Carry SessionCreateOptions through boot.Options and rebuildWithPrevious so the
rebuild import publishes header and manifest in the same atomic transaction as
the other two Desktop import entry points. Desktop derives those options from
the canonical runtime root, the resume path uses the Header-aware control call,
and the superseded headerless rebuild wrapper is deleted now that it has no
callers. Header validation reports which field disagrees instead of one opaque
message.

Verification:
- go test ./internal/boot -run TestRebuild -count=1
- cd desktop && go test . -count=1 -run 'TestModelSettingsRunningToolContinuationKeepsOldConnection|TestEnsureBlankTab'

* fix(desktop): pin the canonical v5 session root at App construction

Problem:
Fork creation failed with "publish fork workspace membership: session: nil
session query", and canonical sessions could be created without the immutable
workspace header the Workspace registry attaches by.

Root cause:
desktopSessionState left root empty until initializeDesktopSessionRoot ran
during startup. Any path that reached the session service before that point -
fork membership publication in particular - resolved a nil service and could
not query or publish canonical state at all.

Fix:
Give newDesktopSessionState the production v5 root so a constructed App always
owns one canonical root, and keep the legacy-derived fallback only for
zero-value Apps in narrow tests. Cover the construction-time invariant
directly, and make the fork and v3 catalog fixtures create sessions through
real workspace ownership instead of headerless shortcuts.

Verification:
- cd desktop && go test . -count=1 -run 'TestCreateForkForTab|TestNewAppPinsDesktopV5SessionRootBeforeStartup|TestDesktopSessionServiceIsSharedAcrossWorkspaces|TestDesktopV3CatalogResumeRenameAndDeleteUseSessionIdentity|TestShutdownReleasesCachedSessionOwner'

* fix(session): stop cold-query workers before closing live runtimes

Problem:
TestDesktopSessionServiceIsSharedAcrossWorkspaces failed only on Windows, where
the temporary session store could not be removed after shutdown.

Root cause:
Shutdown ran CloseAll first and closed the query side afterwards. A cold-query
worker could open a recovery projection after CloseAll had already collected
the runtime set, so that Bolt handle outlived shutdown and kept the file locked
on Windows.

Fix:
Close and join the query workers before CloseAll, so no worker can reopen a
projection once live runtimes are being released.

Verification:
- go test ./internal/session -count=1
- cd desktop && go test . -count=1 -run TestDesktopSessionServiceIsSharedAcrossWorkspaces

* fix(desktop): keep the unavailable fork action a single stable control

Problem:
The app-memory gate reported a persistent three-node DOM drift after GC in
every shard, so the memory attribution check never settled.

Root cause:
An unavailable fork action rendered a second, id-bearing sr-only element whose
useId changed on each transcript remount. Tooltip and React lifecycle kept the
superseded text nodes referenced briefly, so repeated navigation left three
extra nodes behind every settle.

Fix:
Move the unavailability reason into the button's accessible name and delete the
generated description node and its id, so the action is one control with no
remount-sensitive identity.

Verification:
- cd desktop/frontend && node --test src/__tests__/turn-fork-transcript.test.tsx

* test(desktop): target the visible new-session action in browser fixtures

Problem:
The app-settings-motion browser group timed out for 30s waiting for
.sidebar__quick-action after the remote-project assertion.

Root cause:
The fixture hard-coded the old sidebar markup. The Workspace session browser
renders the new-session action as .sidebar__new, so the locator could never
resolve once that surface replaced the Topic sidebar.

Fix:
Resolve the new-session action through one shared helper that accepts either
visible surface, so layout changes do not silently strand the fixture.

Verification:
- cd desktop/frontend && pnpm test:app-browser

* fix: keep allocation size hints free of arithmetic

Problem:
CodeQL reported four high-severity "size computation for allocation may
overflow" alerts on this branch.

Root cause:
Three capacity hints added a length to a constant or to a second length. The
sum is only a hint, but CodeQL correctly flags unchecked arithmetic that feeds
an allocation size.

Fix:
Size each allocation from the single length that is known to bound it and let
append grow the rare extra element.

Verification:
- go build ./...
- go test ./internal/plugin -count=1

* fix(session): make Shutdown release runtimes a client never unbound

Problem:
desktop-windows-go failed every fork and shared-service test with
"TempDir RemoveAll cleanup: ... being used by another process" on the session
ownership lock and the recovery Bolt file, alongside
"close session service err=session runtime still has client bindings".

Root cause:
Shutdown is the documented boundary a host must cross before releasing the
store root, but it delegated to CloseAll, which refuses a bound runtime and
returns without closing it. A binding a client failed to release therefore
stranded the writer lease and recovery handles for the rest of the process.
POSIX hides this because unlinking an open file succeeds; Windows reports it
as an undeletable directory.

Fix:
Split the refusal policy from the teardown. CloseAll keeps refusing bound
runtimes, because tearing one down under a live client is worse than leaving
it. Shutdown is terminal: it drops the leaked binding, closes the runtime so
the lease and recovery handles are released, and still reports ErrRuntimeBound
so the leak stays visible rather than being swallowed.

Verification:
- go test ./internal/session -count=1
- go test ./internal/session -race -run 'TestService(CloseAll|Shutdown)' -count=1

* fix(desktop): keep workspace runtime activity visible in the session browser

Problem:
The desktop-browser group failed on "background jobs keep project activity
visible" once the Workspace session browser replaced the Topic sidebar in the
workbench layout.

Root cause:
The activity indicator lived inside ProjectTree, so replacing that surface
silently dropped the capability: a workspace running background jobs no longer
showed activity anywhere in the workbench sidebar. The paired assertion that
activity clears also became vacuous, because an element that never renders
trivially satisfies a count of zero.

Fix:
Move the rule to its own owner. RuntimeActivityIndicator matches the runtime
store against a workspace identity instead of a ProjectNode, so both sidebars
render the same activity state from one implementation, and the indicator
class is no longer named after one surface. Both fixtures now assert the
shared indicator, so the clearing check has something real to observe.

Verification:
- cd desktop/frontend && node bench/runtime-state.mjs
- pnpm test:typecheck && pnpm lint:hooks && pnpm build

* test(desktop): warm every measured fixture before the memory baseline

Problem:
All three app-memory shards reported post-gc-dom-or-listener-drift: the node
count stepped up by exactly 3 on the first windowed round trip and then stayed
flat for the remaining 128+ round trips across every phase.

Root cause:
The prelude warms the geometry and full fixtures with real round trips, but
the only windowed visit before the baseline is the safety excursion, which
runs with deliberately invalid geometry. The windowed surface therefore
reached its first healthy render after the baseline was taken, so its one-time
bounded setup was measured as displacement from a baseline that was never warm
for that fixture. Per-navigation instrumentation confirmed the step happens
once and never repeats, and the same sequence on the legacy sidebar is flat.

Fix:
Warm the windowed fixture the same way as the other two before sampling. The
gate keeps its full accumulation power: every phase still samples against this
baseline every 32 round trips, and the drift rule is unchanged.

Verification:
- cd desktop/frontend && REASONIX_APP_MEMORY_PROFILE=short node bench/app-memory.mjs
  (baseline 6533 stable, flat through full/windowed/safety/mixed/settled, verdict PASS)
2026-09-15 17:47:41 +08:00
SivanCola aec747b866 fix(desktop): enforce rooted file boundaries and stable CI shutdown
Problem: CodeQL flagged archive/upload sinks and a shared session-delete
input flow. Coverage exposed a test racing the SSE EOF, and the Windows
packaged smoke used a window-close signal for a graceful application quit.

Root cause: File operations used absolute paths after custom validation,
the SSE test did not wait for client-observed EOF, and taskkill could trigger
the product's legitimate background-window behavior.

Fix: Bind archive extraction and upload staging to os.Root, reject size
overflow, validate session basenames with filepath.IsLocal, mark the loopback
fixture cookie Secure, synchronize the EOF test, and exercise the packaged
shell's real quit lifecycle with child-process cleanup checks.

Verification: Rooted-file traversal/symlink/size tests under race; plugin
full race/coverage and 100 repeated SSE EOF tests; session-delete HTTP/race
tests and Windows cross-compilation; real Chromium fixture login; focused
lint and repository checks; 17 packaging tests and a real macOS packaged
app quit with both Electron and Go PIDs gone. Final-head CI verifies
platform-specific gates.
2026-09-09 13:07:05 +08:00
SivanCola f7cce8468f fix(mcp): retire subprocesses before cancelling their contexts
Problem: Native Windows qualification intermittently failed to reconnect a single-instance MCP helper, leaving its listening port and temporary files occupied after forced termination.

Root cause: SDK teardown cancelled the process context before the transport could offer stdin EOF. The graceful path also retained its Windows Job handle and released bounded instance slots before process retirement.

Fix: Retire the current session and endpoint before cancelling their contexts, finish the Windows Job after a graceful exit, and return instance capacity only after cleanup. Cover shutdown and invalidation ordering plus Windows Job and slot retirement.

Verification: The ordering regression failed on both old paths and passes 20 race repetitions. Native Windows reconnect failed 3 of 100 repetitions before the fix and passed 100 afterward; native Job and shutdown regressions pass 20 repetitions. Full root Go, plugin/proc race, Desktop MCP tests, root lint and repository lint pass. Final full Desktop qualification is still in progress.
2026-09-09 05:39:51 +08:00
SivanColaanddadgrand 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]>
2026-09-08 22:32:26 +08:00
SivanCola 871038711d fix(mcp): harden Streamable HTTP compatibility / 修复 Streamable HTTP 兼容性 (#9602)
* fix(mcp): harden streamable HTTP compatibility

Problem:
Stateless Streamable HTTP servers could block startup on a buffered subscriptions/listen request. Transport-level 404 and structured 4xx responses could also poison or misclassify the shared session, while failed protocol negotiation leaked an allocated server session.

Root cause:
The optional SEP-2575 subscription call remained on the synchronous connect path, and go-sdk v1.7.0 lacked request rejection, stateless subscription 404, and failed-negotiation cleanup fixes. Reasonix also relied only on the SDK session-missing sentinel.

Fix:
Run Streamable HTTP subscriptions asynchronously with session-scoped cancellation, pin the upstream SDK commit containing the three transport fixes, and narrowly classify explicit JSON-RPC session-loss errors plus sessionless endpoint 404s. Add compatibility coverage for buffered and live subscription streams, stateless 404s, per-call 4xx responses, negotiation cleanup, and legacy sessionless POST-only servers.

Verification:
- go test ./internal/plugin -count=1
- focused HTTP compatibility tests repeated 30 times
- focused go test -race repeated 5 times
- go test ./... -count=1
- go mod tidy -diff
- git diff --check

* fix(mcp): require transport proof before replay

Problem:
Application-level JSON-RPC errors containing session-loss wording could be mistaken for an expired MCP transport session, allowing a side-effecting tools/call to be replayed. The desktop nested module also retained the previous MCP SDK version and failed CI tidy checks.

Root cause:
Session recovery trusted server error text without requiring HTTP transport-rejection evidence, and the root SDK bump was not propagated through desktop's replace-based module graph.

Fix:
Require an SDK transport-rejection marker plus HTTP 404 evidence for structured session-loss recovery, scope sessionless 404 handling to Streamable HTTP, add no-replay regression coverage, and synchronize desktop go.mod/go.sum.

Verification:
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- focused MCP race tests repeated 5 times
- go vet ./...
- go run ./tools/repolint
- root and desktop go mod tidy -diff
- ./scripts/cache-guard.sh
- git diff --check

* chore(mcp): document exact error-tree traversal

Problem:
CI errorlint rejected the deliberate direct inspection used to distinguish multiple wrapped JSON-RPC errors.

Root cause:
errors.As returns only the first matching RPC error, while session recovery must observe both the server error and the separate SDK transport-rejection sentinel.

Fix:
Document the invariant and scope the errorlint exception to the exact tree-walking helper.

Verification:
- golangci-lint run --timeout=5m ./...
- go run ./tools/repolint
- go test ./internal/plugin -count=1
- git diff --check

* chore(mcp): format lint directive

Problem:
The scoped errorlint directive was not gofmt-clean because its explanatory comment required a separating blank comment line.

Root cause:
The CI formatter enforces gofmt over the complete root module before running tests.

Fix:
Apply gofmt to the directive comment block.

Verification:
- root-module gofmt check
- golangci-lint run --timeout=5m ./...
- go run ./tools/repolint
- git diff --check
2026-08-30 09:23:39 +08:00
SivanCola 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
2026-08-29 23:52:41 +08:00
SivanCola 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
2026-08-29 14:27:18 +08:00
SivanCola 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
2026-08-29 14:23:43 +08:00
SivanCola 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.
2026-08-29 10:15:20 +08:00
SivanCola 4150e2836b test(mcp): follow Chrome page ID routing
Problem: The opt-in Chrome DevTools MCP canary failed against the latest server because page-scoped tools now require pageId.

Root cause: The test relied on the selected-page side effect and called page-scoped tools without the ID returned by new_page.

Fix: Parse the selected page ID from new_page output, validate malformed or ambiguous output, and pass the ID to every page-scoped call.

Verification: go test ./internal/plugin -count=1; go test -race ./internal/plugin -run '^TestParseSelectedChromePageID$' -count=1; go vet ./internal/plugin; REASONIX_LIVE_CHROME_MCP=1 go test ./internal/plugin -run '^TestChromeDevtoolsMCPLive$' -v -count=1 -timeout=3m
2026-08-29 00:46:06 +08:00
SivanCola fa007b590f fix(mcp): complete desktop Apps host lifecycle 2026-08-28 23:25:09 +08:00
SivanCola 5e1bf10c49 feat(desktop): MCP Apps sandbox, AppBridge surface, app tool channel
Desktop Apps run in the double-iframe model: each Apps server gets its
own 127.0.0.1 loopback origin (cookie/storage/origin isolation between
servers) serving only the outer relay page and the validated resource
proxy. The relay hardens the channel - instance-nonce binding on the
first parent message, event.source checks on every frame, an 8 MiB frame
cap, and no RPC before the inner sandboxed frame loads; top navigation,
popups, objects, and downloads never load. Resources are decoded with a
4 MiB cap, must be ui:// with an html/mcp-app mime, are served with the
declared CSP (deny-all default; only exact http/https origins, no
wildcards or credentials), and carry a SHA-256 header so history can
refuse to restore UI across a changed upstream.

The frontend mounts the official AppBridge (ext-apps 1.7.5 pinned) over
PostMessageTransport inside a lazy chunk: app tools/call routes through
Controller.MCPAppCallTool (instance-gated same-server/visibility/
generation resolution, hooks around dispatch, nested ToolDispatch/
ToolResult events under the originating call, LocalOnly transcript
record - never model context), ui/open-link opens the system browser
after the UI confirms the target, and size changes clamp the inline
height to the 120-720px band. Unmount closes the bridge and reclaims
the host instance.

Bundle: ToolCard's expand-to-open surface adds +0.286 KiB gzip initial
JS and +1.8 KiB raw (attributed ratchets); the AppBridge dependency
stays lazy. repolint carries the new bridge.ts/types.ts wire surface.
2026-08-28 22:29:38 +08:00
SivanCola 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.
2026-08-28 22:10:28 +08:00
SivanCola 24a6c73fb3 feat(mcp): interaction broker, MRTR elicitation, form/URL prompts
Wire MCP 2026-07-28 multi-round-trip elicitation end to end. The SDK
default MRTR middleware now fulfills server input requests through the
new internal/mcpinteraction Broker, which travels with the per-call
context (never the shared Host) so concurrent tabs cannot cross wires.
Without a broker the request cancels - the model never guesses.

internal/control gains the pending-elicitation queue mirroring the ask
machinery: register-before-lock visibility, durable MCPInteractionRequest
(mcp_interaction wire kind) with synchronous turn-event persistence,
PromptAnswered-before-release resolution, and replay on frontend
reconnect. The controller implements Broker and installs it on the
executor and planner agents.

Frontends: desktop renders a shared StructuredForm (flat primitive
schemas with defaults/required/bounds) or a URL confirmation card
(server + origin shown, browser opens only on click) as a decision
surface; serve exposes POST /mcp-interaction; the chat TUI gains a
keyboard elicitation card with typing mode. Form values and URL targets
never enter logs; decision receipts record only kind and action.

E2E tests drive real in-memory SDK sessions: form elicitation retries
the original call with the user's answers, decline/cancel still complete
the round trip, multiple input requests fulfill in one retry, dangerous
URLs cancel before any UI sees them, and the no-broker path cancels.

Bundle budgets: +0.2 KiB initial JS gzip (reducer wiring for the new
prompt surface), +0.1-0.2 KiB locale chunks (14 labels per locale),
+2.0 KiB initial raw; repolint carries the exact new-code footprint on
the debt-saturated App.tsx/types.ts/bridge.ts/chat_tui.go/controller.go
(no existing debt hidden; deltas equal the added lines).
2026-08-28 21:48:25 +08:00
SivanCola 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.
2026-08-28 20:46:27 +08:00
SivanCola 28e6303e48 test: make release gates portable on Windows (#9494)
* test: make release gates portable on Windows

Problem:
The main-v2 release candidate failed Windows CI because path JSON was matched as raw POSIX text, Unix permission bits were asserted on Windows, and active test ledgers were left open during TempDir cleanup.

Root cause:
The tests encoded platform-specific assumptions and did not close persistent test-only file handles.

Fix:
Decode event frames before comparing canonical paths, retain the permission assertion only where Unix modes exist, and register ledger cleanup for active-handle tests.

Verification:
go test -p 4 -timeout=8m ./...
go test -race ./internal/serve ./internal/topicstate ./internal/turnevent
GOOS=windows GOARCH=amd64 go test -c for all three affected packages
go run ./tools/repolint

* test: stabilize native scrollbar replay

Problem:
The Linux transcript browser gate could misclassify a held scrollbar-track auto-repeat as a real thumb drag, then fail when later pointer movement did not reach the physical bottom.

Root cause:
Thumb discovery checked only an absolute scroll distance after one delayed move, and asynchronous DOM probes interrupted the proven browser-owned pointer transaction before it was completed.

Fix:
Require two consecutive pointer increments to produce native progress and finish the same physical drag before mutating or inspecting row geometry.

Verification:
pnpm test:transcript-browser
pnpm test:motion
pnpm test:transcript
pnpm build
go run ./tools/repolint

* test: target transcript wheel input deterministically

Problem:
The Linux transcript browser gate could send its hydration wheel event to nested content at the viewport center, leaving the transcript unchanged until the fixture timed out.

Root cause:
The physical input probe did not verify that its pointer target belonged to the outer transcript reader surface.

Fix:
Reuse the visible-row padding hit-test before sending the hydration wheel while retaining the manual-mode and physical-displacement assertions.

Verification:
pnpm test:transcript-browser
node --check bench/transcript-scroll-stability.mjs
go run ./tools/repolint

* test: harden native release fixture activation

Problem:
The Linux native-scrollbar replay could still accept a short track auto-repeat as thumb motion, while the WebView2 selection fixture could activate its topic before the final virtual row was mounted.

Root cause:
The headed Chromium probe lacked a sustained third movement check, and the native WebView2 contract assumed one early synthetic click plus the active class guaranteed a settled tail viewport.

Fix:
Require sustained native progress across a third pointer segment, retry the delayed topic click until active, and hold fixture setup at the physical bottom until the final selection target mounts.

Verification:
node --check for both browser contracts
injected WebView2 contract replay in Chromium at 1200x800
pnpm --dir desktop/frontend test:motion
cd desktop && go test ./...
go run ./tools/repolint

* test: assert cleared OAuth invariant
2026-08-28 11:31:11 +08:00
SivanCola 9f00da2001 Fix MCP CI portability regressions
Problem:
The first PR matrix exposed lint regressions and an intermittent macOS startup diagnostic that could miss a short-lived stdio server's stderr.

Root cause:
Removed transport interfaces left dead test helpers, the roots compatibility path needed an explicit deprecation waiver, and startup read the stderr tail before bounded process shutdown had drained the copy goroutine.

Fix:
Remove obsolete helpers, satisfy the repository's current lint rules while documenting the legacy roots requirement, and close/reap a failed endpoint before capturing its stderr tail.

Verification:
- golangci-lint run --timeout=5m
- go test ./internal/plugin -run '^TestStdioFailureCapturesStderr$' -count=20
- go test -race ./internal/plugin -run 'Test(StdioFailureCapturesStderr|HTTPTransportCloseCancelsActiveRequest|HTTPTransportEstablishedSessionless404DoesNotRebuild)$' -count=1
- go test ./internal/plugin ./internal/agent -count=1
- go vet ./internal/plugin ./internal/agent
- go run ./tools/repolint
2026-08-25 20:28:56 +08:00
SivanCola 44222afeba Harden MCP session failure and shutdown boundaries
Problem:
Sessionless HTTP 404 responses were treated as expired sessions, and a non-returning HTTP tool request could outlive bounded session shutdown.

Root cause:
The SDK uses ErrSessionMissing for every HTTP 404, while the supervisor did not require an established session ID. The SDK also detaches Streamable HTTP from the Connect context and waits for active calls before closing, so CloseIdleConnections could not force active requests to unwind.

Fix:
Classify a 404 without a session ID as a protocol error with no automatic rebuild. Bind active GET and POST requests to the Reasonix session lifetime, preserve the bounded DELETE cleanup path, and forward idle-connection cleanup through the protected round tripper.

Verification:
Added deterministic sessionless-404 and active-request shutdown regressions. Ran focused normal and race tests, the JetBrains standalone GET/DELETE regression, repolint, and diff checks.
2026-08-25 20:14:38 +08:00
SivanCola 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
2026-08-25 19:59:34 +08:00
SivanCola 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
2026-08-25 19:21:55 +08:00
SivanCola 4f1cae1061 feat(runtime): replace execution modes with adaptive standard policy / 用自适应标准执行替代轻量·均衡·交付模式 (#8866)
* feat(runtime): replace execution modes with adaptive standard policy

Problem
Reasonix exposed Light/Balanced/Delivery (and legacy TokenMode) as a
user-selectable execution mode. That split the product into three
workflows, leaked mode labels into UI/ACP/CLI, and let low-risk work
pay Delivery ceremony or high-risk work skip closed-loop evidence.

Root cause
Planner route, verification, review, and completion were branched on
AgentPreset/TokenMode/deliveryProfile instead of task risk.

Fix
Derive one TaskPolicy per turn from intent, risk, structure, and user
constraints. Planning, verification, review, and evidence closure follow
that policy and may only ratchet upward. New UI/CLI/ACP surfaces no
longer offer an execution mode. Deprecated flags, slash commands, ACP
options, and Wails SetAgentPreset*/SetTokenMode* stay accepted for one
compatibility version as no-ops. New persisted compat fields pin
agentPreset=balanced and tokenMode=full. Isolated worktrees keep their
on-disk directory and no longer force Delivery.

Verification
- go test ./...
- cd desktop && go test .
- go test -race ./internal/control ./internal/acp ./internal/taskpolicy
- frontend tsc --noEmit plus Composer/isolated-worktree/send-failed tests

* fix(ci): restore desktop bindings and satisfy repolint

Problem
CI lint failed on comment, file-size, and complexity overruns. Desktop
jobs failed TypeScript because Wails still exports deprecated
SetAgentPreset*/SetTokenMode* and DeliveryWorktree aliases that
AppBindings no longer listed.

Root cause
The standard-execution change kept the one-version Go compatibility
methods bound, but the frontend drift check requires every generated
Wails method on AppBindings. The same change also grew several
already-baselined files past comment and size budgets.

Fix
Bind the deprecated Wails methods as no-op aliases in AppBindings and
the browser mock. Extract ExecutionPolicyBlock and boot test helpers,
split Derive helpers, and compress comments and tests so repolint stays
inside existing budgets.

Verification
- go run ./tools/repolint
- go test ./internal/taskpolicy ./internal/acp ./internal/boot ./internal/control ./internal/evidence ./internal/cli ./internal/capability ./internal/agent
- cd desktop && go test . -run 'TestLoadTabSessionProfile|TestSetTokenMode|TestSetAgentPreset|TestDeliveryWorktree|TestCreateDelivery|TestIsolated'
- desktop/frontend tsc --noEmit
- isolated-worktree and composer-profile frontend tests

* fix(ci): drop unused mode leftovers and preset UI assertion

Problem
After repolint went clean, golangci-lint failed on an ineffectual
ACP workMode assignment, an unused CLI footer helper, a desktop
token-mode snapshot helper, and a strings.Builder modernization
in boot tests. The desktop frontend job failed because the change
panel no longer renders a Balanced preset label.

Root cause
Execution-mode removal left unused compatibility helpers and an
assignment that was immediately overwritten with the pinned
compat value. The workspace-changes test still required the old
preset localization.

Fix
Pin ACP status WorkMode to balanced without reading the unused
runtime profile, delete the unused helpers, join the boot
transcript with strings.Builder, and assert only the localized
verdict on the change panel.

Verification
- go run ./tools/repolint
- golangci-lint run ./internal/acp ./internal/cli ./internal/boot
- go test ./internal/acp ./internal/cli ./internal/boot
- desktop go test -c
- pnpm exec tsx src/__tests__/workspace-changes-errors.test.tsx

* fix(runtime): close standard execution review gaps

Problem:
- Capability routing could run before TaskPolicy was frozen.
- Atomic edits could finish without a mutation, and later low-risk edits could hide earlier high-risk changes.
- Explicit full verification did not force full planning and review.
- Legacy execution modes remained discoverable in CLI, desktop styling, docs, telemetry, and benchmark axes.

Root cause:
- Policy metadata and evidence checks were evaluated at inconsistent points in the turn lifecycle.
- Compatibility controls were registered directly on public surfaces.
- Benchmark and documentation cleanup stopped short of the last profile-specific paths.

Fix:
- Freeze policy before routing, enforce atomic mutation readiness, and ratchet risk across the complete turn ledger.
- Promote explicit full verification to full planning and forced review.
- Hide one-version compatibility parsing behind non-discoverable adapters and remove dead UI, telemetry, documentation, and benchmark profile surfaces.
- Add regression coverage for routing, readiness, risk, compatibility help, and standard benchmark commands.

Verification:
- go test -p 4 -timeout=8m ./...
- go vet ./...
- go run ./tools/repolint
- ./scripts/cache-guard.sh
- go test -race on release-critical packages and repeated plugin refresh coverage
- desktop Go vet/tests, frontend pnpm test:all/build, and SDK Go vet/tests/race

* test(desktop): tolerate slow Windows autosave

Problem:
- The Windows desktop job could fail while the asynchronous turn snapshot was still completing.

Root cause:
- Autosave assertions used a two-second deadline, shorter than legitimate Windows antivirus and indexer delays observed in CI.

Fix:
- Use a shared ten-second autosave test deadline without changing production retry behavior.

Verification:
- Autosave persistence test passed 100 consecutive runs.
- Autosave persistence test passed 20 race-enabled runs.
- Full desktop Go test suite passed.

* fix(desktop): reconcile partial trash before restore

Problem: A committed topic archive can leave a sidecar at the live path when Windows temporarily blocks one step of the multi-artifact move. An immediate restore then reports the leftover sidecar as an unrelated conflict.

Root cause: RestoreSession preflighted destination artifacts without honoring the durable cleanup-pending marker used by committed archive recovery.

Fix: Reconcile pending desktop cleanup before restore conflict checks and retain the cleanup-in-progress response when reconciliation cannot finish. Add a deterministic partial-move regression test.

Verification: desktop go test ./...; desktop go vet ./...; targeted restore tests x100; targeted race tests x30; repolint; git diff --check.

* test(plugin): synchronize no-op refresh cycle

Problem:
The no-op tool-list refresh test could observe cycleDone after a fast refresh had already completed, causing the race job to report that the cycle never started.

Root cause:
The controlled transport did not block tools/list in this test, so the refresh goroutine could finish and clear the cycle handle before the test captured it.

Fix:
Block the controlled tools/list call, capture the active cycle, then release the refresh and assert its no-op behavior.

Verification:
go test -race ./internal/plugin -run '^TestToolListRefreshNoOpPreservesAdapterGeneration$' -count=1000
go test -race ./internal/plugin/...
go run ./tools/repolint
golangci-lint run --timeout=5m ./internal/plugin/...

* fix(runtime): preserve adaptive risk across absolute paths

Problem
Deep absolute mutation paths could lose sensitive owner directories during risk classification, allowing a stale low-risk policy to survive the first write. Verification allowlists also recognized only test commands, so vet, lint, and typecheck could bypass an explicit user restriction.

Root cause
Risk classification truncated absolute paths without workspace context, and parent, child, and background evidence did not share a workspace-aware classifier. The task-policy gate maintained a narrower verifier list than the evidence subsystem.

Fix
Normalize paths lexically against the owning workspace while preserving deep sensitive owners and conservative out-of-workspace behavior. Propagate host-only workspace context through subagents and background task/fleet evidence without serializing it. Reuse the canonical verification classifier in the pre-execution policy gate.

Verification
- go test ./internal/evidence ./internal/taskpolicy ./internal/agent ./internal/jobs ./internal/control
- go test -race ./internal/evidence ./internal/agent ./internal/jobs ./internal/control
- go vet ./internal/evidence ./internal/taskpolicy ./internal/agent ./internal/jobs ./internal/control
- go run ./tools/repolint
- git diff --check

* fix(ci): align latest base contracts

Problem:
The latest main-v2 reasoning display mode was not represented in the startup settings contract, and the new path helper tripped the modernize linter.

Root cause:
PR #8732 added the expanded reasoning option after this branch was created, while the path helper retained a manual membership loop.

Fix:
Require the expanded reasoning option and locale key in the startup contract, and use slices.Contains for exact risk-path segment checks.

Verification:
pnpm exec tsx src/__tests__/startup-settings-contract.test.ts
pnpm test:all
golangci-lint run --timeout=5m ./...
wails generate module
2026-08-15 00:19:50 +08:00
cyqandSivanCola 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]>
2026-08-14 11:39:47 +08:00
SivanColaandttmouse 37e9547d38 fix(desktop): stabilize rendering, workspace, catalog, and WebView2 / 系统治理桌面端显示稳定性 (#8827)
* fix(desktop): stabilize display ownership and recovery

Problem:
Desktop conversations could flicker, jump, clip content, retain stale workspace data, hide partially indexed sessions, flash background command windows, and fail to recover cleanly from WebView2 renderer failures.

Root cause:
Scroll position, workspace freshness, catalog completeness, process visibility, DPI synchronization, and WebView2 recovery each had multiple competing owners or treated incomplete refresh state as empty authoritative state.

Fix:
Centralize transcript scrolling in a deterministic Virtuoso coordinator; add keyed stale-while-revalidate workspace resources, responsive overlay layout, bounded code views, and secure Markdown media resolution; expose partial catalog projections with per-directory completeness and authoritative persist indexing; and add hidden-by-default Windows commands plus a rebased WebView2 integration with single-owner DPI synchronization and tiered crash recovery.

Verification:
- pnpm test:all
- pnpm test:transcript-browser
- pnpm build
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/sessioncatalog ./internal/history ./internal/control ./internal/proc
- cd desktop && go test -race . -count=1
- go run ./tools/repolint
- Windows amd64 cross-compilation for desktop, proc, update-helper, windows-resource, and WebView2 edge

Co-authored-by: ttmouse <[email protected]>

* fix(desktop): close display stability review gaps

Problem:
The initial stability integration still cleared restored workspace selections, replaced working-tree data on refresh failures, allowed background command constructors outside desktop/ to bypass the Windows hidden-window policy, and could serve a media token after its authorized file was replaced. Critical renderer failures could also be dropped behind auxiliary WebView2 diagnostics, while cold Windows CI combined two slow suites under one timeout.

Root cause:
Workspace refresh and project restoration still had overlapping reset paths; the process gate covered only the desktop directory; media tokens were bound only to a path; the diagnostics queue treated recovery and telemetry equally; and the Windows job shared one aggregate test budget.

Fix:
Extract a keyed workspace-changes SWR hook and preserve per-project selections, bind media tokens to the authorized file identity with restrictive response headers, extend proc constructors and static enforcement across desktop-used internal packages, preserve hidden flags for batch commands, prioritize renderer recovery, fix upstream formatting/lint findings, and split the Windows vendor test budget without skipping coverage.

Verification:
- go test ./...
- cd desktop && go test ./...
- root concurrency-package race suite
- cd desktop && go test -race ./...
- golangci-lint on native, Linux, and Windows targets
- pnpm test:workspace
- pnpm test:transcript
- pnpm test:all
- pnpm build
- go run ./tools/repolint
- actionlint .github/workflows/ci.yml
- Windows cross-compilation for desktop, hook, and WebView2 edge tests

* chore(security): document intentional command boundary

* chore(deps): update Go toolchain to 1.26.6

---------

Co-authored-by: ttmouse <[email protected]>
2026-08-14 10:43:06 +08:00
Will a09a33ad47 fix(plugin): fail closed when confined MCP loses its sandbox backend 2026-08-11 20:23:50 +08:00
SivanCola 7be950ff15 merge(main-v2): integrate catalog and runtime updates
Problem:
- The integration branch lagged main-v2 and conflicted in Desktop startup, boot wiring, and frontend guardrails.
- In-memory shared-cache projections could return SQLITE_LOCKED during concurrent reconcile/write operations.
- A timed-out session catalog close could leave its database open, while macOS updater tests shared raw descriptor ownership with production-style helpers.

Root cause:
- The branch and main evolved the same runtime owners independently.
- SQLite shared-cache table locks do not honor busy_timeout across pooled in-memory connections.
- Session catalog close completion and test file-descriptor ownership lacked explicit lifetime fences.

Fix:
- Resolve the merge semantically, preserving catalog task-store and registration-scope behavior alongside upstream config warnings and agent runtime updates.
- Use one database/sql connection for memory projections, make session catalog close eventually complete exactly once, and duplicate updater handoff descriptors for exclusive ownership.

Verification:
- go test and go test -race ./internal/projectiondb ./internal/sessioncatalog
- Desktop session catalog regression: 100 ordinary runs and 30 race runs
- macOS updater handoff/TempDir regression: 100 ordinary runs and 20 race runs
2026-08-11 13:56:01 +08:00
SivanCola 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
2026-08-11 13:13:55 +08:00
SivanCola 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
2026-08-11 12:11:13 +08:00
SivanCola 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
2026-08-11 11:32:28 +08:00
SivanCola 6db0574e44 test: make three CI-red tests deterministic across platforms
Problem: main-v2 push CI has been red since 08-08 15:15. Two Windows
legs fail deterministically on permission assertions Windows cannot
satisfy, and one race leg fails on a startup-timing assumption that
loaded race runners break.

Root cause:
- oauth_test.go asserted the private-token file mode equals 0600, but
  Windows has no Unix permission bits and always reports 0666.
- autoresearch store_test chmod(0)'d a file expecting LoadTask to
  reject it; on Windows chmod cannot make a file unreadable.
- plugin_test asserted a startup error carried the child's redacted
  stderr immediately after a 40ms caller-wait deadline; a
  race-instrumented child takes ~800ms to start, so the failure was
  snapshotted before its stderr reached the pipe buffer.

Fix:
- Gate the mode assertion on non-Windows and skip the chmod-based
  test on Windows; the Unix legs still cover both contracts.
- Assert only stage and elapsed on the caller-wait error; stderr
  redaction is re-asserted whenever the structured record carries
  stderr. Exit-path stderr plumbing and redaction stay covered by
  TestStdioFailureCapturesStderr and TestFailureSummaryRedactsCredentials.

Verification: go test ./internal/plugin/ ./internal/autoresearch/,
go test -race ./internal/plugin/ -count=10 on the affected tests,
GOOS=windows go vet both packages, repolint — all clean.
2026-08-09 20:18:12 +08:00
SivanCola 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.
2026-08-08 20:37:58 +08:00
SivanColaandAndrei Kiparuk ba26737fa8 fix(mcp): close OAuth eligibility and refresh race gaps
Problem:
- Explicit URL and header credentials could be combined with stale OAuth state.
- Remote HTTP endpoints and failed semantic IDs could be offered as usable capabilities.
- Token refresh held the cross-process state lock during network I/O.

Root cause:
- OAuth eligibility used incomplete string heuristics and refresh used one lock scope for read, request, and write.

Fix:
- Parse URL userinfo/query credentials and sensitive headers, restrict OAuth to HTTPS or loopback HTTP, and filter failed/disabled semantic candidates.
- Add a process-local refresh gate plus two-phase locked compare-and-apply with generation checks.
- Add regression coverage for credentials, transport eligibility, routing status, and clear-during-refresh.

Verification:
- go test -race ./internal/plugin ./internal/capability ./internal/mcpdiag ./internal/control ./internal/cli
- go vet ./...
- go run ./tools/repolint
- pnpm exec tsx src/__tests__/mcp-oauth-eligibility.test.ts
- pnpm typecheck

Co-authored-by: Andrei Kiparuk <[email protected]>
2026-08-08 19:30:13 +08:00
SivanCola 5880af7d65 fix(mcp): close OAuth lifecycle races and auth conflicts
Problem:
- OAuth authorization held the MCP state lock across browser interaction and could race with removal.
- Existing OAuth state could be loaded alongside static MCP credentials.

Root cause:
- The authorization flow used one lock scope for the entire interactive exchange and had no invalidation generation.
- HTTP transport only excluded OAuth when an Authorization header was present.

Fix:
- Capture and compare a private OAuth generation around interactive authorization, and advance it during removal reconciliation.
- Treat all explicit MCP authentication configuration as authoritative when constructing HTTP transports.
- Add deterministic regression coverage for browser-lock release, removal invalidation, fallback reconciliation, and static API keys.

Verification:
- go test -race ./internal/plugin ./internal/mcpdiag
- go test ./internal/cli ./internal/control ./internal/capability
- go test ./...
- cd desktop && go test ./...
- git diff --check
2026-08-08 17:32:20 +08:00
SivanCola 3b66c67cb2 fix(mcp): harden OAuth lifecycle routing
Problem:
- Failed or disabled cached MCP tools could remain ready after catalog deduplication.
- stdio, legacy SSE, and statically authenticated servers could enter the HTTP OAuth flow.
- Removing an MCP declaration retained Reasonix OAuth client and token state.

Root cause:
- Registry-backed MCP tool entries did not inherit host failure state.
- Authentication diagnosis and UI actions trusted auth failures before checking transport and configured credentials.
- Remove paths did not reconcile private OAuth state with the effective fallback declaration.

Fix:
- Propagate failed and disabled status through the real catalog assembly path.
- Gate native OAuth consistently in diagnostics, CLI, TUI, Desktop, frontend, and the plugin owner.
- Reconcile OAuth state under the cross-process lock, preserving only same-resource fallbacks and covering open Desktop workspaces.

Verification:
- go test ./...
- go test -race ./internal/plugin ./internal/capability ./internal/mcpdiag ./internal/cli ./internal/control
- cd desktop && go test ./...
- cd desktop && go test -race ./...
- frontend focused test, typecheck, ESLint, and CSS checks
- go vet ./...
- go run ./tools/repolint
- git diff --check
2026-08-08 15:52:39 +08:00
SivanColaandandrei-kiparuk 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]>
2026-08-08 13:20:47 +08:00
SivanCola 3552822776 feat: add generation-safe runtime composition 2026-08-08 01:03:45 +08:00
YHH 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.
2026-08-07 15:30:33 +08:00
YHH 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.
2026-08-07 15:05:46 +08:00
YHH 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.
2026-08-07 14:39:41 +08:00
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]>
2026-08-06 18:29:53 +08:00
SivanCola 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
2026-08-02 08:35:39 +08:00
SivanCola 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
2026-08-02 03:15:34 +08:00
SivanCola 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
2026-08-02 03:07:24 +08:00
SivanCola 2c1df5b4f9 Trust configured MCP and Hooks by default / 默认信任已配置的 MCP 与 Hooks (#6889)
* fix(mcp): trust and surface project servers

Problem:
Project-provided MCP servers were blocked behind a separate launch authorization flow and were not clearly distinguished from global servers in Desktop or CLI management views.

Root cause:
Project scope was coupled to launch approval, while configured-server lookup and presentation did not preserve project provenance consistently.

Fix:
Treat project MCP configuration as user-authorized by default, preserve workspace-scoped activation and relative working directories, load project config for reconnects, and group project, global, and managed servers consistently across Desktop and CLI.

Verification:
- ./scripts/cache-guard.sh
- go test ./...
- cd desktop && go test ./...
- pnpm build
- pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts

* fix(mcp): preserve config scopes and precedence

Problem:
Global MCP installs and project declarations shared persistence paths, so Desktop and CLI actions could write the wrong config, store activation against the wrong workspace, or remove same-name declarations across scopes.

Root cause:
Runtime config merging preserved the effective entry but persistence call sites did not consistently carry its owning source or the controller workspace.

Fix:
Write explicit installs to the user-global config, keep project edits in reasonix.toml or .mcp.json, apply deterministic project-over-global precedence, and restore the next same-name fallback after removing an override. Align Desktop, CLI, TUI, imports, Planner-facing runtime state, and documentation with the same rules.

Verification:
- go test ./...
- go vet ./...
- cd desktop && go test ./...
- cd desktop && go vet ./...
- cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
- cd desktop/frontend && pnpm run typecheck
- cd desktop/frontend && pnpm build
- ./scripts/cache-guard.sh
- git diff --check

* test(mcp): satisfy staticcheck in handler

Problem:
The PR lint job rejected the new MCP test handler with staticcheck QF1003.

Root cause:
The handler used an if/else-if chain on the same request method value.

Fix:
Use a tagged switch for initialize and tools/list responses without changing test behavior.

Verification:
- go test ./internal/control
- git diff --check

* feat: trust configured project hooks by default

Problem:
Project hooks required a separate workspace trust action even though configured MCP servers and installed plugin hooks already use low-friction trusted defaults. This added a second confirmation model and left Desktop, CLI, diagnostics, and documentation describing a trust state users should not need to manage.

Root cause:
Hook loading and inspection were gated by a persistent trust.json store, with that state propagated through boot, slash commands, Wails settings, diagnostics, and compatibility payloads.

Fix:
Load project hooks automatically whenever a project root is present, while preserving Safe Mode as the emergency disable path. Remove the trust store and user-facing trust controls, and keep legacy CLI commands, Wails methods, LoadOptions.Trusted, and trusted_project JSON as backward-compatible no-ops or enabled values. Update tests, diagnostics, localization, and documentation to describe automatic loading.

Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- cd desktop && go test ./...
- pnpm typecheck
- pnpm check:css
- pnpm exec tsx src/__tests__/diagnostics-settings.test.tsx
- ./scripts/cache-guard.sh
- Desktop browser validation for project Hooks
- git diff --check

* fix: keep MCP auth cleanup in the active workspace

Problem: The TUI clear-auth action followed the process working directory instead of the active controller workspace, and Desktop CI was blocked by an orphaned helper.

Root cause: Authentication cleanup bypassed the workspace selection used by the MCP manager, while the removed project-trust flow left an unused Desktop path helper behind.

Fix: Centralize TUI MCP workspace selection, add a cross-workspace regression test, remove the unused helper, and document the executable repository-config boundary with Safe Mode recovery guidance in English and Chinese.

Verification: go test ./internal/cli; go vet ./...; go test ./...; cd desktop and run go vet ./..., go build ./..., and go test ./...; ./scripts/cache-guard.sh; git diff --check.

* fix(mcp): preserve advertised cold-cache connect calls

Problem:
A provider could call a cold-cache MCP connect placeholder that had been advertised at request serialization time, but background schema discovery could replace it before dispatch and produce an unknown-tool failure.

Root cause:
Tool calls are resolved against the current registry after the provider returns, while cache-miss discovery intentionally swaps the synthetic connect placeholder for the server's real tools.

Fix:
Recognize a stale synthetic connect call as completed when the same MCP namespace now exposes real tools, and let already-resolved lazy placeholders report the same controlled result. Keep genuine unknown calls unchanged and add regressions at both dispatch and lazy-lifecycle boundaries.

Verification:
- go test ./...
- go test -race ./internal/plugin ./internal/agent
- go vet ./internal/plugin ./internal/agent
- ./scripts/cache-guard.sh
- delayed cold-cache LongCat MCP integration test
2026-07-24 16:00:36 +08:00