Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
Problem:
Network probes bypassed permission prompts, and local background jobs could not be listed or stopped while Remote Workbench was active.
Root cause:
The shared read-only classifier conflated workspace mutation with permission safety, while job APIs selected the active remote target before honoring an explicit tab owner.
Fix:
Separate workspace-nonmutating commands from permission-read-only commands, keep Test-NetConnection outside read-only subagents, and route job listing and cancellation by explicit local or remote tab ownership.
Verification:
Ran focused permission, evidence, agent, and Desktop tests, both Go module suites, race tests, vet, and the cache guard.
Problem:
The stable helper could consume immutable version tags if main-v2 advanced while exact-SHA CI or the protected relay was starting. Manual CLI and Desktop recovery also still accepted RC tags despite the official-only release contract.
Root cause:
The helper sampled main-v2 once, pushed only tag refs, and the orchestrator required the tags to equal the later branch tip. The candidate was not proven to be the commit that changed the target reviewed Notes record.
Fix:
Bind normal candidates to their reviewed Notes change, revalidate exact push CI from the protected control plane, include main-v2 as a guarded ref in the atomic tag transaction, and accept the immutable candidate after later protected branch advances. Restrict manual CLI and Desktop recovery to official versions while retaining historical reusable-workflow parsing.
Verification:
- bash scripts/release-stable.test.sh
- bash scripts/release-workflows.test.sh
- node --test scripts/release-notes.test.mjs
- actionlint on all active release workflows
- site contract tests, SignPath contract tests, Desktop control-plane test, cache guard, and git diff --check
The Desktop CI still opened release-desktop-trigger.yml after the public prerelease relay was intentionally removed, causing the first PR head to fail.
Keep the protected Stable relay assertions and replace the deleted relay expectation with explicit absence checks for all three retired public prerelease workflows.
Verification:
go -C desktop test . -run TestProductionSigningRunsOnlyFromProtectedControlPlane -count=1
bash scripts/release-workflows.test.sh
git diff --check
Problem: main-v2 advanced after the lint repair was prepared, so the PR branch would otherwise become stale again before push.
Root cause: PR #7226 merged the shared site GitHub navigation contract during final verification.
Fix: Merge the latest base without changing the Desktop update implementation.
Verification: pnpm test in site passes all 48 contract tests; git diff --cached --check passes.
Problem: The PR lint job failed because lockRepairStateFile was unused after the StartupTracker write path was removed.
Root cause: The Unix, Windows, and fallback platform helpers were left behind when startup recovery state became read-only diagnostics.
Fix: Delete the three dead platform implementations so no Guard-era repair-state lock surface remains.
Verification: golangci-lint v2.12.2 reports 0 issues; go test and go vet pass for internal/repair; the package cross-compiles for Windows amd64.
The one-time migration completed successfully in Actions run 30754751693. All seven packages now resolve latest, canary, and next to 1.19.2 with candidate provenance c46e3af1c2.
Remove the temporary manual workflow and its actionlint entry so the normal developer surface contains only ongoing release operations.
Verification:
Re-ran release workflow contracts, site tests, SignPath contract validation, actionlint, cache guard, and diff checks after removal.
Problem: main-v2 advanced again while the updater conflict resolution was under verification, leaving the branch behind the current base.
Root cause: PR #7222 merged an npm alias propagation fix after the first integration merge was prepared.
Fix: Merge the new base commit without altering the resolved Desktop updater semantics.
Verification: node --test scripts/finalize-npm-official-release.test.mjs; bash scripts/release-workflows.test.sh; git diff --cached --check.
Problem:
The dual-channel release controls and the proposed GitHub App activation add operator choices, owner-only setup, and failure modes that are unnecessary now that users consume only official releases.
Design:
Retain the proven three-tag Stable orchestrator, retire public Preview relays, and add one exact-SHA helper that validates reviewed notes and push CI before atomically creating the CLI, npm, and Desktop tags. Keep historical Preview inputs readable only for compatibility.
Experience:
Prepare release accepts one version. After the notes PR merges, a maintainer runs one command and approves the release environment once. The website and public documentation expose only the official line and reasonix upgrade.
Safety:
Preserve immutable tags, protected control-plane checks, SignPath validation, idempotent recovery, npm provenance checks, and bounded alias convergence. No persisted runtime format changes are introduced.
Verification:
Release workflow contracts, atomic tag failure cases, SignPath contract tests, website public-surface tests, actionlint, cache guard, and diff checks pass locally.
Problem: PR #7199 conflicted with the official single-channel updater changes on main-v2, which prevented GitHub from producing a merge ref and running the full CI matrix.
Root cause: Both branches changed the Desktop updater settings, Wails contracts, configuration compatibility fields, and updater tests from the same older baseline.
Fix: Merge main-v2 while retaining the single ApplyUpdateRequest flow, force every new UI operation onto the official stable-compatible channel, remove the retired channel switch helper and Safe Mode payload, and preserve request/version fences for stale async completions.
Verification: GOWORK=off go test ./... and go vet ./... in both Go modules; focused go test -race for config, install layout, window state, settings, and updater paths; pnpm test:all and pnpm build; Windows amd64 Desktop and update-helper cross-builds; cache guard, cache-impact, and release workflow contract tests.
Problem: The shared marketing header did not expose the source repository, duplicated the install destination with a Get started link, and allowed navigation text to overflow into right-side controls at intermediate widths.
Root cause: The responsive rules kept a non-shrinking navigation row visible until 900px even when its flex container was already narrower than its contents.
Fix: Add an accessible GitHub link, collapse the primary navigation before it can overlap, preserve the compact mobile control budget, remove the duplicate Get started item, and cover the contracts with focused tests.
Verification: npm test in site (48 tests passed); npx astro build; browser checks at 1101px, 1100px, 952px, and 900px in English and Chinese.
Problem:
The bridge release advanced npm latest to 1.19.2 while the compatibility canary and next aliases still selected public prereleases.
Root cause:
The legacy Stable publisher owns latest only, and the single-channel finalizer is not active yet.
Fix:
Add a zero-input, release-approved one-time workflow that verifies all seven package records against the exact 1.19.2 candidate SHA before atomically aligning latest, canary, and next. Fail closed on provenance drift and verify every final alias.
Verification:
node --test scripts/finalize-npm-official-release.test.mjs
bash scripts/release-workflows.test.sh
go run github.com/rhysd/actionlint/cmd/[email protected] .github/workflows/migrate-npm-release-aliases.yml .github/workflows/ci.yml
git diff --check
Problem:
- current.json could degrade to an in-place copy, legacy helpers could reject valid pending updates without relaunching, overlapping window saves could reorder, and manual NSIS installs wrote live layout state directly.
Root cause:
- Commit-pointer writes shared a compatibility fallback, the detached helper resolved pending state from its cache location, frontend saves were concurrent, and the installer bypassed the layout activation transaction.
Fix:
- Add strict atomic pointer writes, claim legacy transactions against the explicit install root with recovery relaunch, serialize window capture/persistence, and route normal installers through signed staging activation.
- Add failure-injection, ordering, handoff, and packaging contract tests.
Verification:
- GOWORK=off go test ./... && GOWORK=off go vet ./... (root and desktop modules)
- GOWORK=off go test -race on affected root and desktop packages
- pnpm test:all && pnpm build
- Windows amd64 cross-compile for migrator, update helper, and desktop
- ./scripts/cache-guard.sh and cache impact check
Problem:
- Product Safe Mode had been disabled semantically, but boot, theme, telemetry, settings, and startup tracking still carried always-false branches and compatibility fields.
Root cause:
- The initial Guard removal preserved obsolete behavior APIs instead of reducing them to the normal startup and appearance paths.
Fix:
- Flatten boot onto the normal integration path, remove Safe Mode fields and telemetry gates, and keep legacy startup state read-only for crash diagnostics.
- Remove obsolete frontend/theme contracts and update recovery documentation.
Verification:
- GOWORK=off go test ./... && GOWORK=off go vet ./... (root and desktop modules)
- GOWORK=off go test -race ./internal/boot ./internal/telemetry ./internal/repair ./internal/fileutil ./internal/installlayout
- pnpm test:all && pnpm build
- ./scripts/cache-guard.sh and cache impact check
Summary:
List all affected user-facing subsystems and correct two English descriptions in the generated stable release notes.
Verification:
Validate the release catalog, run its tests, render the exact public notes, scan them for private data, and check patch formatting.
Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
Summary:
Add the instruction package to the bounded Windows pull-request smoke suite so platform-specific home expansion is checked before merging.
Verification:
Run the instruction package, lint the CI workflow with actionlint v1.7.7, and check patch formatting.
Summary:
Set both Unix and Windows home environment variables in instruction resolver tests so tilde expansion remains inside each temporary test home.
Verification:
Run the instruction package once, repeat the three home-import cases 20 times, and check patch formatting.
Problem:
Detached runtimes without child jobs disappeared from the status bar, Remote Workbench jobs could be replaced by local groups, and displayed runtime decision shortcuts did not work.
Root cause:
The status chip treated jobs.length as the only activity signal and selected one data source. RuntimeDecisionCard only listened for Escape.
Fix:
Keep running and prompt-blocked runtimes visible, merge active remote jobs with local runtime groups, expose actionable runtime state copy, and route enabled number keys to their matching actions.
Verification:
Ran the full frontend suite and production build, both Go module suites, focused and Desktop race suites, cache guards, targeted DOM interaction tests, and a local browser smoke check.
Summary:
List every user-facing subsystem covered by the bridge release notes instead of classifying the release as Desktop-only.
Verification:
Validate the release catalog, render the exact public notes, scan the rendered text for private data, and check the patch formatting.
Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
Problem: v1.20 no longer enters product Safe Mode, but the desktop still carried unreachable startup recovery, theme gating, renderer fallback, and platform helper paths that kept lint red.
Root cause: the initial Guard removal disconnected the entry points without deleting their dormant implementations or moving Linux-only helpers behind the Linux build tag.
Fix: delete the obsolete recovery and renderer policy, simplify theme operations to the normal path, retain only ignored legacy launch tokens and always-false wire fields for upgrade compatibility, and clear the remaining Desktop lint findings.
Verification: go test ./...; go vet ./...; golangci-lint run ./...; pnpm typecheck; pnpm exec tsx src/__tests__/theme-pack.test.ts; Windows amd64 cross-compile.
Problem:
Guard removal left two unused Safe Mode internals and a no-op legacy argument check, causing golangci-lint to fail.
Root cause:
The compatibility APIs were made constant-false but their obsolete storage/helper remained, while the migrator prefix branch only continued at the natural end of the loop.
Fix:
- Remove the unused Config.safeMode field and loadSafeModeForRoot alias.
- Remove the side-effect-free legacy prefix check; unknown compatibility arguments remain ignored.
Verification:
- go test ./internal/config ./cmd/reasonix-legacy-migrator
- go vet ./internal/config ./cmd/reasonix-legacy-migrator
- golangci-lint run ./internal/config ./cmd/reasonix-legacy-migrator
- CACHE_IMPACT_PR_BODY_FILE=/tmp/reasonix-v120-guard-removal-pr.md ./scripts/check-cache-impact.sh
- ./scripts/cache-guard.sh
- git diff --check
Problem:
The initial Guard removal still left old 1.18-1.19.1 clients without a reliable migration handoff, allowed corrupt current.json files to fall back to stale flat binaries, and kept legacy pending transactions in the versioned update path.
Root cause:
Launcher and migrator ownership was split, version activation did not publish stable root entries transactionally or serialize competing processes, and Windows/Linux versioned updates still inherited parts of the flat-layout recovery protocol.
Fix:
- Share permanent launcher behavior and run the one-shot compatibility migrator before resolving the active desktop.
- Finalize exact legacy pending transactions during flat-to-versioned migration and archive only strictly older install-local transactions after a healthy recovery install.
- Publish version directories and root launcher/CLI entries under a cross-process activation lock, with current.json as the final commit point and rollback on failure.
- Remove legacy pending identities from Windows versioned handoff and activate Linux versioned updates without persisting Guard.
- Make config file merging transactional, hold one update request owner across all phases, and align Linux deb/frontend contracts with the Guard-free product behavior.
- Add migration, rollback, packaging, concurrency, compatibility, and legacy environment regression coverage.
Verification:
- go test ./...
- cd desktop && go test ./...
- targeted go test -race for config, installlayout, repair, CLI, launcher, migrator, and updater paths
- go vet ./... in both Go modules
- pnpm test:updater and pnpm build in desktop/frontend
- cache guard and cache-impact checks
- Windows amd64 desktop/update-helper and Linux amd64 launcher/migrator/update-helper cross-compilation
Problem:
v1.20 still lacked local config recovery, kept StartupTracker/old split update
bindings in the product surface, and packaged flat Windows/Linux layouts with
Guard assumptions.
Root cause:
LoadForRoot failed closed on corrupt TOML; desktop still bound download/install
as separate Wails methods; portable and Linux packaging still assumed a flat
Guard release unit; helper still allowed a flat publish fallback.
Fix:
- Config load: corrupt user config uses last-known-good in memory (else
built-in defaults) without rewriting the original file; project config and
MCP JSON failures are isolated with LoadWarnings.
- Desktop surfaces configWarnings with Open config / Reload / doctor repair
banner actions; records LKG after UI ready and on clean shutdown.
- Remove StartupTracker from launch control (observe previous run for crash
diagnostics only).
- Wails update surface is ApplyUpdateRequest only; download/install are private.
- Helper versioned-v1 activation is mandatory for staged Windows payloads.
- Windows portable packaging/verification and Linux tarball use versioned/thin
launcher contracts; macOS bundle rejects Guard.
- Fault-injection and legacy flat migration fixture tests for installlayout.
Verification:
- go test ./internal/config ./internal/installlayout ./cmd/reasonix-launcher
./cmd/reasonix-legacy-migrator ./internal/repair
- cd desktop && go test ./cmd/update-helper/ and focused package tests
- pnpm --dir desktop/frontend test:updater (31 passed)
Out of scope for local automation: Windows DPI 100x soak, Preview 72h gate,
production migration success rate, and live signed-installer UI coverage.
Problem:
The Windows update helper still published a flat six-member release unit with
Guard health recovery. The Guard recovery product remained as a first-class
binary and packaging still installed Guard as the permanent entry point.
Root cause:
Post-stage publish was hard-wired to flat InstallRoot members and pending
receipt recording. Packaging and docs still treated reasonix-guard as the
launcher/recovery surface.
Fix:
- Prefer versioned-v1 activation from staged NSIS payloads: write
versions/<ver>/{desktop,cli,helper}, swap current.json, place thin launcher
and portable alias at InstallRoot, remove flat desktop/guard leftovers.
- On versioned success, clear the pending transaction without health probation
and relaunch via reasonix-launcher.
- Resolve InstallRoot and update-helper source for versioned trees; claim
pending targets from the prepared transaction rather than hard-coded flat
paths.
- NSIS dual mode: /REASONIXSTAGE=1 still extracts the six-member flat payload
for 1.18-1.19.1 helpers; normal install writes versioned layout and does not
persist Guard.
- Delete cmd/reasonix-guard. Compatibility payloads still build
reasonix-guard from reasonix-legacy-migrator only. Point recovery docs at
reasonix doctor.
- Linux .desktop Exec uses reasonix-launcher.
Verification:
- go test ./internal/installlayout ./cmd/reasonix-launcher
./cmd/reasonix-legacy-migrator ./internal/repair
- cd desktop && go test ./cmd/update-helper/
- focused desktop packaging/window/updater tests
- GOOS=windows go test -c ./cmd/update-helper and desktop package
Problem:
Desktop shutdown can panic on Windows when Wails queries window geometry after DPI reports 0. Product Safe Mode and Guard health policy force global capability loss and leave users stuck on pending-update loops. The flat install layout cannot support atomic version activation.
Root cause:
beforeClose/shutdown called WindowGetSize/Position/IsMaximised on a tearing-down WebView2 surface. StartupTracker and REASONIX_SAFE_MODE drove a process-wide degraded mode. Updates relied on a single pending transaction with health probation and Guard rollback.
Fix:
- Persist window state only from frontend reports; Go rewrites last-known state without native queries during hide/shutdown. Accept Windows border origin x=-8,y=-8 and add regression coverage.
- Introduce installlayout (current.json schema 1, staging activation, version GC) plus a permanent thin reasonix-launcher and a one-shot legacy migrator (compat binary name reasonix-guard).
- Add ApplyUpdateRequest single-action update path, install_layout field validation, and frontend update-and-restart UX.
- Make SafeModeRequested always false; strip desktop startup recovery that forced Safe Mode; keep doctor/crash diagnostics non-gating.
- Point desktop-build Windows launcher/migrator builds at the new cmds.
Verification:
- go test ./internal/installlayout ./cmd/reasonix-launcher ./cmd/reasonix-legacy-migrator ./internal/config ./internal/boot
- focused desktop window-state/updater/safe-mode tests
- pnpm --dir desktop/frontend test:updater (31 passed)
Remaining for full v1.20.0 gate: NSIS dual-mode packaging, versioned payload activation in update-helper, delete cmd/reasonix-guard source, full config last-known-good resilience, macOS/Linux packaging and release docs.
Problem:
Delayed missing-reasoning transactions could revive an incident after a newer healthy turn. Future last-missing timestamps and transient healthy-state write failures could then suppress a later regression despite the warning contract.
Root cause:
The persisted record kept only missing timestamps and deleted healthy incidents, so it had no resolution watermark to reject stale claims. State loading validated only the warning timestamp, and resolve writes discarded persistence errors.
Fix:
Keep a bounded nanosecond resolution watermark in the additive v2 state, reject inconsistent or future event timestamps, and keep non-missing read failures visible. Preserve failed healthy resolutions in the agent and retry them before the next missing claim, falling back to a visible warning if persistence is still unavailable. Add ordering, recovery, migration, and partial-read regression coverage.
Verification:
- go test -count=1 ./...
- go test -race -count=1 ./internal/agent
- go test -count=1 ./... (desktop module)
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
- ./scripts/check-cache-impact.sh origin/main-v2
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
TypeScript commands such as tsc --noEmit --init could write tsconfig.json while being treated as non-mutating verification. Metadata, watch, build, and --noCheck modes could also satisfy or stall readiness without completing a bounded type check.
Root cause:
The TypeScript verifier only required --noEmit and rejected a small set of independent output destinations. It did not distinguish ordinary compiler checks from control and early-exit modes.
Fix:
Fail closed on init, help/version/config display, list-only, no-check, watch, build/clean, trace/profile, and explicit build-info destinations for both direct tsc and npx tsc. Preserve ordinary one-shot no-emit checks and add positive and negative classifier regressions.
Verification:
- go test ./internal/evidence -run "VerificationCommandSummary|GoBuild|TSCVerification|PythonCompileall|NpxVerification|RunnerWriteOutput" -count=1
- go test ./internal/evidence ./internal/agent -count=1
- go test -race ./internal/evidence ./internal/agent -count=1
- go vet ./...
- go test ./...
- bash scripts/cache-guard.sh
- git diff --check
Problem:
Valid midtone custom themes could make semantic or syntax colors fail WCAG AA on either the plain code surface or a tinted diff row.
Root cause:
A fixed diff tint could cross the black/white contrast boundary, leaving no shared foreground that reached 4.5:1 across every rendered surface.
Fix:
Reduce semantic row tint only when required, validate every code role against plain, added, and deleted backgrounds, and add adversarial plus generated-palette regressions.
Verification:
- cd desktop/frontend && pnpm test
- cd desktop/frontend && pnpm build
- go test ./...
- go test -race ./internal/cli -count=1
- cd desktop && go test ./...
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
Problem:
After same-incident coalescing fixed duplicate Windows warnings, concurrent claims for different configurations could still lose persisted incidents.
Root cause:
Each fingerprint had its own leader, so distinct leaders still competed in the bounded in-process portion of the file lock. Slow Windows atomic writes let queued leaders time out before their read-modify-write transaction.
Fix:
Serialize all transactions for one warning-state file with a process-local mutex before entering the bounded cross-process file lock. Same-fingerprint flights still coalesce duplicate notices, while distinct configurations now complete lossless read-modify-write transactions.
Verification:
- go test ./internal/agent -run TestMissingReasoningWarnStateConcurrent -count=100
- go test -race ./internal/agent
- go test ./...
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
- CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go test -c ./internal/agent
Co-authored-by: SivanCola <[email protected]>
Require npx verifier identities to use a bare known package name with only an ordinary version or dist-tag suffix. Reject filesystem paths and package protocols so aliases such as eslint@npm:other-package cannot inherit the trusted runner classification.
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
Problem:
Delivery recovery still accepted bytecode compilation and TypeScript diagnostic output as non-mutating verification. The npx policy also recognized only two test runners, leaving safe test and lint commands from #6901 behind the Auto Guard boundary.
Root cause:
The verifier allowlist treated compileall as a check, only gated tsc on noEmit, and did not distinguish safe npx test/check modes from output-producing runners and flags.
Fix:
Classify compileall and explicit TypeScript build-info, trace, and CPU-profile destinations as mutations. Adapt the known-runner direction from #6901 for Mocha, AVA, ESLint, Prettier check mode, and no-emit npx tsc while keeping Playwright, Cypress, source executors, control flags, and output modes fail closed.
Verification:
- go test ./internal/evidence -run 'VerificationCommandSummary|GoBuild|TSCVerification|PythonCompileall|NpxVerification|RunnerWriteOutput' -count=1
- go test ./internal/evidence ./internal/agent -count=1
- go test -race ./internal/evidence ./internal/agent -count=1
- go vet ./...
- go test ./...
- bash scripts/cache-guard.sh
- git diff --check
Co-authored-by: ipanta <[email protected]>
Refresh the Windows concurrency repair after main-v2 advanced with v1.19.1 release notes. The base-only documentation update does not overlap executable code.
Problem:
Windows CI could emit several first warnings for one concurrent incident even though persistent state used a file lock.
Root cause:
The bounded file-lock deadline also covered the in-process queue. On slower Windows atomic writes, followers timed out locally and each took the fail-visible path.
Fix:
Coalesce in-process claims by state path and configuration fingerprint. Followers suppress duplicate warnings while forwarding their latest observation timestamp to the leader, which preserves stale-health ordering and retains the bounded cross-process lock behavior.
Verification:
- go test ./internal/agent -run TestMissingReasoningWarnStateConcurrent -count=50
- go test -race ./internal/agent
- go test ./...
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
- CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go test -c ./internal/agent
Co-authored-by: SivanCola <[email protected]>
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
Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
Refresh the repaired warning-state branch after the base advanced during verification. The intervening billing, remote protocol, and release-note changes do not overlap the incident-state implementation.
Problem:
Persisting a provider-name-only acknowledgment hid endpoint/model changes and future regressions, while OpenAI-format V4 Flash was inconsistent with the current DeepSeek thinking contract.
Root cause:
Warning state had no configuration identity, expiry, or healthy recovery transition, and the OpenAI warning matcher excluded the whole Flash family.
Fix:
Key incidents by an opaque adapter configuration fingerprint, expire them after 24 hours, clear them after healthy tool-call reasoning, and preserve ordering with millisecond event timestamps under the existing cross-process lock. Align known DeepSeek thinking models across OpenAI, Anthropic, and Responses adapters and use protocol-neutral diagnostics.
Verification:
- go test -race ./internal/agent
- go test ./...
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
Co-authored-by: SivanCola <[email protected]>
Synchronize the latest release, desktop packaging, billing, and remote protocol changes before publishing the theme readability integration. The upstream paths do not overlap the theme or syntax-highlighting changes.
Problem:
Theme packs, Creation overrides, and diff tint layers could leave syntax or semantic diff text below WCAG AA, while the extended pane transparency rules could make protected code surfaces translucent again.
Root cause:
Base styles did not install complete code palettes, Creation replaced only the code background, diff contrast was measured against the untinted surface, and the pane-tier integration treated block code as an ordinary child panel.
Fix:
Generate complete opaque palettes for every base style and Creation, derive foregrounds against final diff row backgrounds, share the derivation with previews, exempt block code and diffs from pane transparency, and clear every extended pane variable.
Verification:
- pnpm test
- pnpm build
- go test ./...
- go test -race ./...
- in-app browser light, dark, and background-theme contrast checks
Co-authored-by: HaoyueQin <[email protected]>
Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
Bring the warning-state fix onto the current provider and agent baseline before refining its persistence semantics. This keeps the contributor branch mergeable without rewriting its public history.
Problem:
Go build package patterns can still write a workspace binary when they expand to one main package. The recovery summary also rendered only family labels, hiding safe examples such as tsc --noEmit while the classifier accepted output-capable bare tsc commands.
Root cause:
The static classifier assumed ./... always implied discarded build outputs, and the summary discarded the same concrete examples used by its consistency tests. TypeScript verification was not gated on no-emit mode.
Fix:
Classify every go build form as a mutation, render each tested safe command example in the recovery summary, and require explicit TypeScript no-emit mode. Update the serial sign-off scenario and add regressions for Go build, TypeScript output flags, and model-visible examples.
Verification:
- go test ./internal/evidence ./internal/agent -count=1
- go test -race ./internal/evidence ./internal/agent -count=1
- go vet ./...
- go test ./...
- bash scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
- Five-tier pane transparency system (Shell / Card / Session-hover /
Child / Interact) with CSS variables driven by paneOpacity slider
- Sidebar session items (project-tree__topic) and folders now share
the same session-hover tier, responding to panel opacity uniformly
- Welcome page example buttons (welcome__ex) placed on child tier,
one level above the welcome container background
- Topicbar buttons + external-opener unified on card tier
- Update banner (banner--update) raised to child tier
- Active dock-tab underline moved inside button (bottom:-1px→1px)
so it is no longer clipped by content below
- Windows frameless dock-tab compression: scoped to workbench layout
only, compresses spacing (padding / gap) via clamp() while never
shrinking tabs or truncating labels
(cherry picked from commit fad7908b0f)
Problem:
The status bar could show a USD session cost beside a CNY wallet balance after the user selected USD pricing.
Root cause:
Balance rendering always preferred CNY and neither the local nor Remote Workbench balance query carried the effective Desktop pricing currency.
Fix:
Select the provider-reported balance that matches the effective Settings currency. When the provider only reports another currency, retain that real amount with an explicit ISO prefix instead of applying an implicit exchange rate. Carry the preference through an optional Remote Workbench request field while preserving the legacy CNY-first fallback for older callers.
Verification:
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- pnpm typecheck && pnpm test:typecheck && pnpm test:remote
- pnpm exec tsx src/__tests__/use-controller-live-context.test.tsx
- pnpm build
- go run ./cmd/remote-protocol-gen -check -root .
- ./scripts/cache-guard.sh
Problem:
Code and diff surfaces inherited translucent theme backgrounds while syntax colors followed only the global light/dark mode. Custom backgrounds could therefore bleed through and make highlighted text unreadable.
Root cause:
Theme-pack overlays forwarded bgSoft alpha directly and had no derived code surface or per-token contrast guard. The theme preview also omitted realistic code and diff content.
Fix:
Derive an opaque code background and border from each theme mode, choose the syntax direction from the final surface luminance, and minimally adjust every code and diff text role to WCAG AA contrast. Route code, full diff, and inline diff rendering through the protected surface, and add live code/diff samples to theme previews.
Verification:
- cd desktop/frontend && pnpm test
- cd desktop/frontend && pnpm build
- cd desktop && go test ./...
- go test ./...
- go test -race ./internal/cli -count=1
- in-app browser: light/dark, home/task, official background, and #RRGGBBAA custom bgSoft
Problem:
CLI diffs kept the GitHub dark syntax palette on light terminal themes, leaving pale tokens difficult to read on light diff backgrounds.
Root cause:
The Chroma style was resolved once as github-dark instead of following the active CLI light or dark mode during rendering.
Fix:
Resolve the registered GitHub light/dark counterpart on every render so runtime theme switches and theme-sweep preview frames use matching syntax colors. Add regression coverage for style selection, live output changes, and source-text preservation.
Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli -count=1
- go test ./...
Problem:
The verification classifier searched raw go build arguments for ./..., allowing flag values such as -pkgdir ./... to masquerade as package operands.
Root cause:
Build flags and their values were not separated from package operands.
Fix:
Parse documented Go build flags, accept only safe forms, require ./... among actual package operands, and fail closed for output-affecting or unknown flags. Add regressions for safe -tags forms and flag-value bypasses.
Verification:
- go test ./...
- go vet ./...
- go test -race ./internal/evidence ./internal/agent
- bash scripts/cache-guard.sh
- runtime go build flag checks with unchanged workspace status
Co-authored-by: SivanCola <[email protected]>
Contract: A popover that renders before its anchor must leave the visible fallback and compute an anchored position without requiring a scroll or resize event.
Coverage: Consume the first scheduled measurement while the anchor is absent, then mount the anchor and assert that bounded frame retries recover the correct position and ready state.
Verification: Anchored popover regression test (9/9), frontend typecheck, and git diff --check.
Co-authored-by: SivanCola <[email protected]>
Problem: The defensive popover fallback could remain at the viewport edge when the first animation-frame measurement ran before its anchor mounted.
Root cause: A missing anchor ended the only guaranteed follow-up measurement, while assigning a ref later does not rerun the positioning effect.
Fix: Retry the initial measurement for at most eight animation frames, then stop. Preserve the last valid position after a successful measurement and clear the tracking ref when the popover closes.
Verification: Anchored popover regression test (9/9), frontend typecheck, and git diff --check.
Co-authored-by: SivanCola <[email protected]>
Integration: Bring the popover fallback fix onto the latest main-v2 without rewriting contributor history.
Verification: Focused popover tests, frontend typecheck, full frontend tests, and the production build follow in subsequent validation.
Split the repeated in-memory fallback claim assertion so staticcheck does not
treat two stateful calls as an identical boolean expression.
Verification:
- golangci-lint run --timeout=5m
- go test -race ./internal/agent
- git diff --check
Problem:
The readiness recovery summary advertised direct go build commands that can create workspace binaries, appeared on unrelated readiness failures, and duplicated classifier examples in a test-only list.
Root cause:
The summary treated every accepted verifier as equally safe to recommend and was appended without consulting the structured readiness failure. Its drift test did not share the data used to render the summary.
Fix:
Recommend a structured safe subset, classify only all-package go build patterns as non-mutating verification, and inject the summary only when verification evidence is actually missing. Add regression coverage for workspace-output builds, recommendation consistency, and conditional injection.
Verification:
go test ./...
go vet ./...
go test -race ./internal/evidence ./internal/agent
bash scripts/cache-guard.sh
git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
Separate agent instances cached the warning state independently and used a fixed temporary file, so concurrent claims could duplicate a warning or overwrite another provider.
Root cause:
The read-check-write sequence was not one cross-process transaction.
Fix:
Serialize each claim with a bounded cross-process file lock, reread the state under that lock, and publish the merged provider set with the existing atomic writer. Persistence failures preserve the historical once-per-session warning behavior.
Verification:
- go vet ./...
- go test -race ./internal/agent
- go test ./...
- ./scripts/cache-guard.sh
Co-authored-by: SivanCola <[email protected]>
Contract: Anchored popovers stay visible before their first real measurement, become ready only after anchored geometry is available, and retain the last valid position during temporary anchor loss.
Coverage: Extend the existing real-component scroll test with deterministic unavailable-to-ready and ready-to-unavailable interleavings.
Verification: The focused anchored-popover regression passed, TypeScript typechecking passed, and git diff --check passed.
Co-authored-by: SivanCola <[email protected]>
Integration:
- Update PR #7123 to main-v2 at 84d3d1ae00.
- Preserve the macOS-only icon safe area and pre-signing replacement unchanged.
Verification:
- go test ./...
- (cd desktop && go test ./...)
- (cd desktop && go test -race .)
- bash -n scripts/desktop-build.sh
- git diff --check
Problem: Popovers without an initial measured position were hidden off-screen, while the first PR revision fabricated zero-sized rectangles and could replace a previously valid position with a top-left fallback.
Root cause: Measurement readiness and fallback visibility were represented by the same position state.
Fix: Keep the position null until real elements can be measured, preserve the last valid position during temporary anchor loss, and render an unmeasured popover at the visible edge without marking it ready.
Verification: The focused anchored-popover regression passed, TypeScript typechecking passed, and git diff --check passed.
Co-authored-by: SivanCola <[email protected]>
Integration: Bring the contributor branch onto the latest main-v2 without rewriting its published history.
Verification: The merge completed without conflicts; full frontend verification follows.
Problem:
The scoped footer overflow fix restores composer menus in the normal layout, but a terminal drawer or tall composer can still push a fixed-height menu above the viewport.
Root cause:
The menu capped itself with 50vh instead of the actual space above its composer anchor, and sibling-driven layout changes can move that anchor without resizing it.
Fix:
Preserve decision-only footer scrolling, measure the real anchor space while a menu is open, observe surrounding layout and viewport changes, and cap the menu at the smaller of 360px and that available height. Add DOM geometry coverage plus the scoped-footer guard from the overlapping test PR.
Verification:
- pnpm test
- pnpm build
- pnpm typecheck && pnpm test:typecheck
- go test ./... (desktop module)
- wails build -s -tags webkit2_41
- Browser geometry at 1280x720 with the terminal drawer and a tall composer
Fixes#7127
Co-authored-by: SivanCola <[email protected]>
Problem: The first stable CLI publication failed before GoReleaser could build because the protected release-control checkout appeared as an untracked directory in the immutable product checkout.
Root cause: Preview recovery reused an existing CLI release and never exercised GoReleaser dirty-state validation. A new stable release does, and the nested control-plane checkout made the otherwise clean product tree dirty.
Fix: Add only the exact release-control directory to the product checkout local exclude file, verify the nested checkout is ignored, and fail closed if any other product-tree dirt remains. Resolve the common Git directory absolutely so normal clones and linked worktrees behave consistently.
Verification:
- bash scripts/release-workflows.test.sh
- actionlint v1.7.7 release workflows
- bash scripts/cache-guard.sh
- bash -n scripts/release-workflows.test.sh
- git diff --check
Problem: Reconciliation treated a nil exact-cancel result as proof that the pending marker was removed, but the existing cancel API intentionally returns nil when a different target version or creation time is observed.
Root cause: The new reconciler relied on the transition return value without re-checking its public postcondition after releasing the transaction locks.
Fix: Re-read pending-update.json after cancellation, report success only when the marker is absent, and fail closed when the same or a newer transaction remains. The deterministic rewrite test now swaps in a different target version to exercise the inert-success path.
Verification: focused internal/repair and desktop updater/startup/handoff tests passed with and without the race detector; git diff --check passed.
Problem: Desktop updates could leave pending-update.json behind when the macOS handoff helper failed after the transaction was prepared. Later installs then failed permanently with a pending update already exists error.
Root cause: Startup and install entry points did not reconcile abandoned immutable update transactions, and the macOS parent treated a successful process spawn as a completed handoff without confirming that the child had validated the transaction.
Fix: Reconcile prepared or partially published transactions through exact cancel/rollback paths before every startup and install mode, preserve genuinely probationary releases with content-bound evidence, add a ready/proceed macOS handoff handshake, and surface recovery progress in both updater views.
Verification: go test ./...; cd desktop && go test ./...; focused repair and desktop go test -race suites; frontend updater tests, typecheck, CSS checks, production build, and bundle budgets.
Delivery mode blocks final answers until a host-recognized verification
command is cited in complete_step, but the whitelist classifier
(bashSegmentIsVerification) is invisible to the model. The two most
intuitive guesses both fail deterministically: inline interpreters
(node -e / python -c) are blocked as opaque mutations, and read-only
inspection pipelines (grep|wc, find) never count as verification.
The model loops until maxFinalReadinessBlocksWithProgress=6 or the turn
is force-interrupted - observed 7 readiness injections in one CLI
session and reproduced locally.
Add evidence.VerificationCommandSummary() and embed it in both the
final-readiness retry message and the opaque-interpreter block message,
so the model can pick a valid verifier (node --check, project test
runners, read-only pipelines into a verifier) on the first retry.
Tests: summary/classifier consistency (anti-drift), deadlock-trap
warnings, read-only pipeline escape hatch, retry-message content.
Verified locally with go1.26.5: gofmt clean, go test ok.
Problem:\nPreview recovery rejected the existing public pointer even though its assets use the official immutable version directory.\n\nRoot cause:\nThe fallback validator replaced every legacy Preview asset base with the historical rolling desktop-preview directory. This made version-bound legacy manifests with a null release notes URL impossible to validate.\n\nFix:\nTry the immutable legacy layout first, then retain the rolling directory as a second Preview-only compatibility fallback. Add workflow contract coverage and a null release-notes manifest regression.\n\nVerification:\n- bash scripts/release-workflows.test.sh\n- actionlint v1.7.7 release workflows\n- bash scripts/cache-guard.sh\n- git diff --check
Problem:
Desktop Preview recovery compared a rebuilt manifest with the immutable published manifest before adopting the validated published record.
Root cause:
The directory comparison ran before the existing manifest was validated against its payload sizes and digests and copied into the recovery candidate.
Fix:
Validate and adopt the immutable manifest before comparing directories, and lock the ordering with static and behavioral regression coverage.
Verification:
- bash scripts/release-workflows.test.sh
- actionlint for release-desktop.yml
- bash scripts/cache-guard.sh
- bash syntax and git diff --check
AnchoredPopover's position state starts as null. In some WebKit (Wails)
render timings getBoundingClientRect() returns nothing yet, and the old
'if (!anchor || !menu) return' bailout left position unset forever:
the popover stayed visibility:hidden and parked off-screen at -9999
until a scroll/resize event happened to recompute it - so it only
appeared after the mouse moved over it.
Fall back to a zero-size rect so position is always computed (clamped
to a visible in-viewport spot), and use EDGE_GAP instead of -9999 as
the pre-layout fallback so an unmeasured popover is never hidden.
Problem: Rebuilding an immutable Desktop candidate produces byte-distinct minisign files, so recovery rejects an otherwise identical published directory.
Root cause: The directory verifier treated nondeterministic detached signatures as reproducible payload bytes.
Fix: Verify candidate and published signatures with the embedded public key, allow only verified signature-byte differences, and copy existing signatures into the recovery set so immutable objects are preserved.
Verification: Release workflow contracts and Actionlint pass; public Preview Linux and Windows installer assets verify against the embedded key.
Problem: Historical Preview recovery checks out the immutable product candidate, so recovery scripts added after that tag are absent.
Root cause: The CLI publisher mixed candidate source and publication control policy in one checkout, unlike the Desktop publisher.
Fix: Add a protected workflow-SHA control checkout and run the CLI publication decision from it while keeping product builds pinned to the approved candidate.
Verification: release-workflows.test.sh and Actionlint pass; regression assertions reject candidate-controlled publication policy.
Problem:
A failed Preview publisher cannot consume a workflow fix by rerunning the original Actions attempt, while the immutable Preview tag must not be moved to the repaired main-v2 commit.
Root cause:
The documented all-surface Preview recovery path still required the Preview tag to equal current main-v2, making recovery of an ancestor candidate impossible.
Fix:
Add an opt-in Preview recovery input, keep the control workflow pinned to protected current main-v2, allow only a historical Preview tag that remains on main-v2 history, and propagate the recovery authorization to the CLI publisher.
Verification:
- bash scripts/release-workflows.test.sh
- bash -n scripts/resolve-preview-release.sh scripts/release-workflows.test.sh
- git diff --check
Problem:
Orchestrated Desktop releases passed an empty direct tag input to the R2 mirror revalidation step, blocking Preview publication and the next Stable mirror.
Root cause:
The mirror job used inputs.tag even though Preview and Stable orchestrators authorize releases through inputs.approved_cli_tag.
Fix:
Revalidate the orchestrator-approved CLI tag in the mirror job and lock the mapping with a workflow contract regression assertion.
Verification:
- bash scripts/release-workflows.test.sh
- git diff --check
Problem:
- The first 1.19.0 Preview tag exposed a Desktop orchestrator authorization failure before any public surface could complete.
- Recovery must use a new immutable Preview ordinal instead of moving the published tag.
Fix:
- Add the reviewed bilingual 1.19.0-preview.2 record for the release control-plane repair in PR #7118.
- Bind CLI, Desktop, and npm identities to Preview ordinal 2.
Verification:
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- git diff --check
- Release-record privacy scan
Problem:
- The protected Preview orchestrator passed orchestrated=true to the Desktop release workflow.
- The immutable candidate resolver treated every orchestrated call as Stable and rejected every Desktop Preview before signing preflight.
Root cause:
- release-desktop.yml did not forward the trusted orchestrator kind to the resolver.
- The resolver had a hard-coded Stable-only check, and contract tests covered only standalone Preview resolution.
Fix:
- Forward and validate the stable/preview orchestrator identity in every candidate resolution step.
- Permit the Preview orchestrator to authorize its approved SHA, including recovery after main-v2 advances, while retaining ancestry and tag checks.
- Add success, mismatch, and recovery regression coverage.
Verification:
- bash scripts/release-workflows.test.sh
- git diff --check
- Privacy scan of the patch
Problem:
- The v1.19.0 stable release needs a reviewed catalog record before stable tags can be pushed.
- The hosted generator returned empty content, and the live fallback produced a schema-invalid change body twice.
Fix:
- Derive the stable record deterministically from the reviewed preview record because v1.18.0 and v1.18.0-preview.2 share the same source SHA.
- Replace only the stable channel wording, prior version, release links, and CLI/Desktop/npm build identities.
Verification:
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- git diff --check
- Privacy scan of the release record diff
Problem:
- The v1.19.0-preview.1 release needs an exact reviewed catalog record before tagging.
- The hosted notes generator returned empty content twice.
Fix:
- Generate a bilingual prerelease record from the first-parent range v1.18.0-preview.2..main-v2 using the repository script and a validated DeepSeek model.
- Bind CLI, Desktop, and npm build identities to preview ordinal 1.
Verification:
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- git diff --check
- Privacy scan of the release record diff
Problem:
The curated GLM Coding Plan Anthropic presets selected glm-5.2[1m] by default, which the live endpoint rejected with model-not-found for a valid Coding Plan credential.
Root cause:
The presets promoted a Claude Code-specific model alias to the generic Anthropic-compatible default instead of using the documented glm-5.2 model identifier.
Fix:
Default both China and global Anthropic-compatible Coding Plan presets to glm-5.2 while retaining the optional alias and the one-million-token context configuration. Keep browser mocks and localized descriptions aligned.
Verification:
- go test ./internal/config
- go test ./...
- pnpm --dir desktop/frontend test:all
- ./scripts/cache-guard.sh
- live GLM Coding Plan Anthropic request through the default provider selection
Problem:
PR #7103 conflicted with the merged Responses preset, retained raw non-tool reasoning across PostLLMCall hooks, and omitted the Anthropic preset from the Desktop browser/localization contract.
Root cause:
Both protocol presets added adjacent config tests independently; the Anthropic client advertised full reasoning round trips despite replaying only tool-call reasoning; frontend mock and i18n tables were not extended with the backend preset.
Fix:
Merge current main-v2 while retaining both protocol tests, scope raw reasoning retention to tool-call turns, and add the localized Desktop mock, adjacent preset ordering, and regression coverage.
Verification:
- go test -p 1 ./... -count=1
- cd desktop && go test -p 1 ./... -count=1
- go test -race ./internal/provider/anthropic ./internal/agent ./internal/config ./internal/boot -count=1
- go vet ./... (root and desktop)
- pnpm --dir desktop/frontend test:all
- pnpm --dir desktop/frontend build
- ./scripts/cache-guard.sh
DeepSeek (and some gateways) never attach reasoning_content to tool_calls
turns, so the once-per-session notice re-appeared on every new session and
--resume even though its text promised a single showing (#7059).
Persist the warned-provider set to <reasonix home>/state via a new
missingReasoningWarnState store (atomic temp-file+rename, best-effort:
any IO failure degrades to the historical once-per-session behavior) and
check it in warnMissingToolCallReasoning before emitting. Boot supplies
the shared state dir to both executor and planner options; direct Agent
construction without the option keeps the old scope, so nothing changes
for embedders that do not opt in.
Update the notice text to "shown once" and add unit tests (persistence,
sorting, corrupt-file self-heal, empty-dir no-op) plus an e2e test
covering SetSession and fresh-agent runs over a shared state dir.
Integration:
Bring PR #7094 onto current main-v2 after the Responses API merge. Preserve both the new Responses provider-kind surface and the provider model-cache/race fixes.
Verification:
Passed root and Desktop tests, vet, and build; frontend full tests, typecheck, and production build; focused Go race and frontend cache/privacy regressions; cache-impact guard; and git diff --check.
Problem:
Markdown identifiers were treated as commands solely because they were backticked, while acknowledgement phrases could suppress a real follow-up task.
Root cause:
Inline shell parsing accepted every non-empty word, and the task heuristic returned immediately on acknowledgement wording without inspecting surrounding clauses.
Fix:
Require command-shaped tokens independent of Markdown quoting, and preserve tasks before acknowledgements or after explicit transition boundaries. Add focused and end-to-end regressions for both cases.
Verification:
- go test ./internal/agent -count=1
- go test ./... -count=1
- go test -race ./internal/agent -count=1
- go vet ./...
- cd desktop && go test ./... -count=1
- ./scripts/cache-guard.sh
- scripts/check-cache-impact.sh with the live PR body
- git diff --check
Integration:
Bring PR #7094 onto the current main-v2 baseline after the Remote Workbench CLI bootstrap merge. The base changes do not overlap provider settings, credential locking, or model-cache files.
Verification:
The combined tree will be rechecked with Root and Desktop tests, focused frontend regressions, cache guard, and git diff validation before push.
Problem:
Credential rotation could interleave with provider catalog compare-and-apply, frontend caches ignored the opaque credential revision, and auto-refresh cooldown keys persisted provider header values.
Root cause:
Credential writes and catalog saves used different lock domains, the cache identity omitted modelCatalogFingerprint, and SettingsPanel serialized the full provider request identity into sessionStorage.
Fix:
Serialize credential-store writes with a shared process and advisory file lock, revalidate a content-derived credential revision before catalog commit, key caches and cooldowns by the opaque fingerprint, and add deterministic interleaving and privacy regressions.
Verification:
Focused Go race tests, root and Desktop test suites, Go vet/build, frontend test/typecheck/build, cache guard, and git diff --check passed.
Co-authored-by: SivanCola <[email protected]>
Problem:
- The DeepSeek Responses preset fell back to an English description in Chinese settings and appeared far below the official DeepSeek entry.
- Browser preview data omitted the preset, so local UI checks did not match production.
Root cause:
- The preset had no frontend localization mapping or locale entries.
- Production and mock preset ranking did not give the DeepSeek Responses entry an explicit priority.
Fix:
- Localize the preset description in English, Simplified Chinese, and Traditional Chinese.
- Rank it first among curated presets, making it second overall after DeepSeek Official.
- Mirror the production preset and order in browser mock data with regression coverage.
Verification:
- go test ./...
- cd desktop && go test ./...
- pnpm test
- pnpm test:typecheck
- pnpm build
- ./scripts/cache-guard.sh
- Browser preview verified card order and Chinese copy
Co-authored-by: SivanCola <[email protected]>
Problem:
DeepSeek V4 Flash lost its supported low effort on the Anthropic endpoint, legacy effort aliases used the Pro mapping for every model, and caller temperature was silently dropped.
Root cause:
The compatibility path normalized effort without considering DeepSeek's model routing and reused the native Anthropic request shape that intentionally omits sampling controls.
Fix:
Apply Flash/Pro-aware effort normalization including Claude aliases and fallback routing, expose Flash low effort in the preset, preserve temperature only for official DeepSeek Anthropic requests, and update documentation and regression coverage.
Verification:
- go test ./internal/provider/anthropic ./internal/config ./internal/boot -count=1
- go test -race ./internal/provider/anthropic ./internal/config ./internal/boot -count=1
- ./scripts/cache-guard.sh
- go vet ./...
- go test -p 1 ./... -count=1
- cd desktop && go test -p 1 ./... -count=1
Problem:
Delivery advisory classification still missed affirmative mutations after negated clauses, treated remote diagnostic verbs as observable work, and allowed ordinary commands to escape the evidence gate.
Root cause:
Negation used a fixed prefix window, advisory verbs overrode question semantics globally, and command detection relied on a narrow exact-string list that drifted from the task heuristic.
Fix:
Scope negation across directive boundaries, classify advisory language per clause, recognize concrete commands and file anchors without confusing prose or email addresses, and share mutation intent with the task heuristic. Add a bilingual classification matrix and end-to-end readiness regressions.
Verification:
- go test ./internal/agent -count=1
- go test ./... -count=1
- go test -race ./internal/agent -count=1
- go vet ./...
- cd desktop && go test ./... -count=1
- ./scripts/cache-guard.sh
- scripts/check-cache-impact.sh with the live PR body
- git diff --check
Problem:
Desktop lint rejected obsolete SSH factory wrappers. Workbench CLI reuse also required SFTP before checking an exact PATH binary and could return a relative executable path.
Root cause:
The bootstrap initialized managed-storage dependencies before the zero-install reuse path, while probe validation checked only the Build ID. Compatibility wrappers remained after callers migrated to explicit-binary factories.
Fix:
Probe an exact absolute PATH binary before SFTP, defer platform detection until installation, reject non-absolute probe results, remove unused wrappers, and cover SFTP-disabled and relative-PATH regressions.
Verification:
go test -p 1 ./... -count=1
cd desktop && go test ./... -count=1
go test -race ./internal/remote/bootstrap -count=1
cd desktop && go test -race . -run Test\(EnsureWorkbenchCLI\|GoSSHStream\|RemoteSSHTransport\) -count=1
go vet ./... (root and desktop)
Windows desktop test package cross-compile
./scripts/cache-guard.sh
Problem: Remote Workbench on fresh or cross-platform SSH hosts could exit before initialize and surface only a generic connection-closed error.
Root cause: Desktop launched reasonix from the remote PATH without provisioning an exact protocol-compatible build, while the Go SSH path discarded stderr and session exit failures.
Fix: Probe the complete Workbench Build ID, securely provision a verified platform release into a build-specific remote path, launch that executable explicitly, and preserve classified bootstrap failures. Extend legacy serve bootstrap to support verified cross-platform release uploads.
Verification: go test ./...; cd desktop && go test ./...; frontend remote tests, typecheck, and production build; focused race tests and go vet; cache and protocol guards; Windows and Linux/arm64 cross-compilation.
Context: Synchronize PR #7103 with the latest main-v2 after applying the DeepSeek Anthropic reasoning replay review fixes.
Compatibility: Preserve both the existing DeepSeek Anthropic changes and the latest provider, agent, Desktop, and protocol updates from main-v2 without rewriting branch history.
Verification: The merge completed without conflicts and git diff --cached --check passed; full root, Desktop, race, and cache validation follows on the combined tree.
Problem: Historical DeepSeek Anthropic tool-call reasoning could be omitted when a later request no longer declared tools, while the reasoning guide could misstate the model-specific effort scales.
Root cause: Request serialization keyed reasoning replay to the current tool list and thinking mode instead of the historical assistant tool-call turn. The documentation also combined Flash and Pro capabilities.
Fix: Replay unsigned DeepSeek thinking blocks only for historical assistant tool-call turns regardless of the current tool declaration or thinking mode, add focused regression coverage, and document the separate Chat Completions and Anthropic effort scales.
Verification: go test ./internal/provider/anthropic -run TestBuildRequestDeepSeek -count=1 -v; go test ./internal/config -run TestDeepSeek\|TestNormalizeEffortDeepSeek -count=1; git diff --check.
Problem:
Stateful continuations dropped system instructions, and the DeepSeek Responses preset exposed a provider-wide catalog containing unsupported models.
Root cause:
The continuation path returned before instructions were serialized, while the preset reused a models endpoint broader than Responses API support.
Fix:
Serialize instructions before choosing full-history or previous-response continuation, keep DeepSeek Responses on its curated Flash-only list, and add regressions for both contracts.
Verification:
- go test ./internal/provider/responses ./internal/config -count=1
- go test -race ./internal/provider/responses ./internal/config -count=1
- ./scripts/cache-guard.sh
- go test ./...
- go vet ./...
- cd desktop && go test ./...
- cd desktop && go vet ./...
Co-authored-by: SivanCola <[email protected]>
Problem:
- CodeQL treated the unkeyed SHA-256 catalog fingerprint as weak hashing of sensitive credential-related data.
Root cause:
- The digest crossed the Wails boundary without a process-private key, even though it contained only provider identity and credential-store metadata.
Fix:
- Generate a process-private random key and compute the opaque catalog token with HMAC-SHA256.
Verification:
- focused provider catalog tests under go test -race
- go test ./... and go build ./... in desktop
- git diff --check
Problem:
- A delayed background model refresh could submit a full ProviderView after a newer manual save and silently restore old provider settings.
- Switching settings tabs or rotating a credential did not invalidate the pending write.
Root cause:
- The background effect had no request generation guard.
- SaveProviders serialized writes but had no compare-and-apply contract, so an old snapshot could still win last.
Fix:
- Replace full-provider batch writes with narrow model-catalog updates guarded by an opaque provider and credential fingerprint.
- Compare and apply updates while holding the config edit lock, skipping stale snapshots and rebuilding only after real changes.
- Invalidate outdated frontend completions with an effect generation guard.
- Add deterministic stale-completion, credential-rotation, atomicity, and tab-switch regression coverage.
Verification:
- go test -race focused desktop catalog tests
- go test ./... and go build ./... in desktop
- pnpm test and pnpm build in desktop/frontend
- go test ./...
- ./scripts/cache-guard.sh
- git diff --check
Context: bring PR #7094 onto the current integration baseline before applying the stale-write remediation.
Verification: merge completed without conflicts.
Resolve the overlapping OpenAI provider changes by preserving both the DeepSeek Beta prefix continuation path and the merged Gemini thought-signature/model-normalization behavior.\n\nVerification:\n- go test ./internal/provider/openai ./internal/agent -count=1\n- go test -race ./internal/provider/openai -count=1\n- GOWORK=off go vet ./internal/provider/openai\n- ./scripts/cache-guard.sh\n- go test ./... -count=1\n- cd desktop && go test ./... -count=1\n- git diff --cached --check
Problem:
The ACP sandbox degradation test failed on Windows even though the runtime
override path behaved correctly.
Root cause:
Windows intentionally normalizes a config-file `bash = "enforce"` setting to
`off`, so the test never entered the unavailable enforced-sandbox branch it
intended to exercise.
Fix:
Set the effective `enforce` override explicitly in both the degradable and
fail-closed test cases, matching the supervisor command-line path on every OS.
Verification:
- go test ./internal/cli -run 'TestACPSupervisorRuntimeState' -count=1
- go test -race ./internal/cli -run 'TestACPSupervisorRuntimeState' -count=1
- GOOS=windows CGO_ENABLED=0 go test -c ./internal/cli
- go test ./...
- cd desktop && go test ./...
Co-authored-by: SivanCola <[email protected]>
Problem: CI rejected the advisory classifier with staticcheck S1008 after the functional fix was pushed.
Root cause: The final boolean branch returned true or false explicitly instead of returning the computed negated-mutation value.
Fix: Return negatedMutation directly without changing classifier behavior.
Verification: go test ./internal/agent -count=1; go vet ./...; git diff --check.
Co-authored-by: SivanCola <[email protected]>
Problem:
DeepSeek thinking-mode responses can exhaust max_tokens entirely in reasoning_content before emitting visible text, so the prefix-continuation wrapper stopped without attempting the documented Beta recovery.
Root cause:
The continuation gate required accumulated visible text even when a valid thinking-mode reasoning prefix was available.
Fix:
Allow one bounded Beta continuation when thinking mode has accumulated reasoning_content, including an empty assistant content prefix. Add an SSE regression that verifies the wire payload, endpoint sequence, merged usage, and final response.
Verification:
- go test ./internal/provider/openai ./internal/agent -count=1
- go test -race ./internal/provider/openai -count=1
- GOWORK=off go vet ./internal/provider/openai
- ./scripts/cache-guard.sh
- git diff --check
Problem: Common negated install requests and remote application run questions still armed delivery mutation or host-evidence gates, preventing advisory responses from completing.
Root cause: Mutation negation depended on a small list of complete phrases, while generic run/test keywords were treated as host-observable without repository, file, or command context.
Fix: Classify English and Chinese negation around each mutation verb, preserve affirmative mixed-intent clauses, and require concrete anchors before run/test wording overrides advisory questions. Add regressions for the reported install and WPS cases plus local technical-work controls.
Verification: go test ./...; cd desktop && go test ./...; go vet ./...; ./scripts/cache-guard.sh; git diff --check.
Co-authored-by: SivanCola <[email protected]>
Problem:
Persisted ACP status could restore `running` after a process restart even
though the new runtime owned no active turn, leaving supervisors waiting on
ghost work.
Root cause:
`restoreStatusTelemetry` preserved the process-local running state instead of
converting an interrupted snapshot into a new terminal recovery observation.
Fix:
Always restore sessions idle. When the saved snapshot was running, advance the
status sequence and publish `recovery_paused` with an interrupted paused
outcome while preserving partial usage.
Verification:
- go test ./internal/acp ./internal/cli ./internal/control ./internal/boot
- go test -race ./internal/acp ./internal/cli ./internal/control ./internal/boot
- ./scripts/cache-guard.sh
- go test ./...
- cd desktop && go test ./...
Co-authored-by: SivanCola <[email protected]>
Problem: Reasonix treated the official DeepSeek Messages endpoint as a generic Anthropic-compatible service, which misses its unsigned thinking replay, text-only input, and automatic cache contracts.
Fix: keep Chat Completions as the default, add an optional curated Anthropic preset, and scope endpoint-specific reasoning, effort, cache, and vision behavior to official DeepSeek hosts.
Verification: focused provider/config/boot tests, Desktop tests, serial root tests, go vet, cache guard, and diff checks pass. The opt-in live probe reached the endpoint but the inherited local credential returned HTTP 401.
Problem:
CI staticcheck reported a redundant break at the end of a switch case in the Responses SSE loop.
Root cause:
The terminal-event case already exits the switch naturally before the outer terminal guard runs.
Fix:
Remove the redundant break without changing stream behavior.
Verification:
go test ./internal/provider/responses
go vet ./internal/provider/responses
git diff --check
Problem:
Reasonix could not use DeepSeek V4 Flash through the official stateless Responses API, while the contributor branch mixed protocol work with unrelated artifacts and missed tool/reasoning round-trip correctness.
Root cause:
The initial implementation treated Responses events and previous_response_id too loosely, dropped assistant reasoning/content on tool turns, duplicated terminal text, and did not persist context-mode configuration.
Fix:
Add a focused Responses provider with deterministic full-history serialization, DeepSeek reasoning/tool replay, safe stateful-prefix validation and stale-ID fallback, vendor-scoped DashScope headers, terminal-state handling, config round-tripping, and a DeepSeek Flash preset.
Verification:
go test ./internal/provider/responses ./internal/config ./internal/boot
go test -race ./internal/provider/responses ./internal/config ./internal/boot
go test ./...
(cd desktop && go test ./...)
go vet ./...
(cd desktop && go vet ./...)
./scripts/cache-guard.sh
Co-authored-by: clearnature <[email protected]>
Integration:
- Include the terminal drawer layout work that landed during remediation validation.
- Preserve the model polling fixes on the current review base.
Verification:
- Merge completed without conflicts; targeted frontend and Desktop build checks follow.
Integration:
- Include the main-v2 release-channel and DeepSeek effort updates that landed while PR checks were running.
- Keep the remediated model polling changes tested against the current base.
Verification:
- Merge completed without conflicts; post-merge Desktop and frontend validation follows.
Problem:
The supervisor status extension could block default ACP startup when an OS sandbox backend was unavailable, report stale planner state after session rebuilds, expose non-standard ACP fields, and publish unsanitized status text.
Root cause:
Runtime policy was captured only at session creation, sandbox availability was treated as a startup requirement even for automatic policy, permission reasons were added at the ACP v1 root, and presentation text was reused as public status data.
Fix:
Recompute runtime state from the selected model and profile after rebuilds, distinguish explicit sandbox enforcement from automatic degradation, publish truthful sandbox availability, keep extension data under _meta, and normalize/redact stable status fields.
Verification:
- go test ./internal/acp ./internal/cli
- go test -race ./internal/acp ./internal/cli
- ./scripts/cache-guard.sh
- go test ./...
- cd desktop && go test ./...
Co-authored-by: SivanCola <[email protected]>
Problem:
DeepSeek V4 Flash xhigh selections followed the generic DeepSeek mapping to max instead of the documented high effort.
Root cause:
The model capability registry described selectable levels but had no model-specific compatibility aliases.
Fix:
Add a Flash-specific xhigh-to-high alias and regression coverage for inferred and explicit DeepSeek protocols while preserving Pro xhigh-to-max behavior.
Verification:
- go vet ./...
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- cd desktop/frontend && pnpm build
- ./scripts/check-cache-impact.sh
- ./scripts/cache-guard.sh
Problem:
- Settings writes returned before controller rebuilds and broke deferred lease retry.
- Failed model fetches never retried, cache identities collided, and credential changes kept stale entries.
- Background catalog updates rebuilt once per provider.
Root cause:
- The shared config apply path was made fire-and-forget.
- Backoff tracked only a delay without a deadline, and the key used modelsURL instead of modelsUrl.
- Auto-refresh persisted providers individually.
Fix:
- Restore synchronous failure-atomic rebuilds and deferred lease handling.
- Add typed time-based cache identity, backoff, invalidation, and stale-inflight guards.
- Batch provider catalog persistence into one transaction and runtime rebuild.
- Key refresh cooldowns by provider set and omit failed batch entries without JSON nulls.
Verification:
- go test ./... (desktop)
- pnpm test
- pnpm build
- pnpm typecheck
- ./scripts/cache-guard.sh
- git diff --check
Problem:
Gemini-authored tool calls keep opaque thought signatures in session history. After a same-session switch to another OpenAI-compatible provider, the generic request builder forwarded Gemini-only extra_content.google fields to that provider.
Root cause:
Request serialization checked only whether a stored signature existed; it did not check whether the current endpoint or model spoke Gemini thought-signature semantics.
Fix:
Detect the official Gemini endpoint and Gemini model IDs used through compatible gateways. Emit thought signatures only for those current clients, preserve them in provider-neutral history, and cover official, gateway, and switched-provider request shapes.
Verification:
- go test ./internal/provider/...
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Co-authored-by: dailingtao <[email protected]>
Integration:
- Bring the contributor branch onto the current main-v2 baseline.
- Preserve the model polling performance changes for review remediation.
Verification:
- Git completed the semantic merge without conflicts.
Problem: Advisory diagnostics need to finish without synthetic work, but the initial fix also let read-only technical tasks bypass host-observable evidence and let negated clauses mask later mutation requests.
Root cause: The evidence gate was reduced to the mutation classifier, and mutation negation returned for the whole input instead of the clause it qualified.
Fix: Keep advisory, evidence, and mutation decisions separate; prefer concrete review, test, repository, and file signals; and classify mutation negation per clause. Add classifier and end-to-end readiness regressions for advisory, read-only, and mixed-intent prompts.
Verification: go test ./internal/agent -count=1; go vet ./...; go test ./internal/tool/builtin/ ./internal/boot/ -count=1; git diff --check.
Co-authored-by: SivanCola <[email protected]>
Problem:
Narrow workbench windows placed the terminal drawer over the status bar, and persisted terminal heights could collapse chat after the window shrank. The hidden resize separator also remained in the keyboard focus order.
Root cause:
Responsive CSS kept the drawer and separator on their wide-layout grid coordinates, while the rendered height reused the stored preference without clamping it to the current viewport.
Fix:
Keep the responsive drawer and separator in the content column, clamp the rendered height whenever the viewport changes, add keyboard resizing, and remove the closed separator from accessibility navigation. Add focused regression contracts for each boundary.
Verification:
- pnpm test:terminal
- pnpm typecheck
- pnpm check:css
- app chrome, status bar, and layout default contract tests
- browser checks at 800x700 and 1280x480
Problem:
DeepSeek V4 Flash supports low, high, and max reasoning effort, but Reasonix exposed only high and max and normalized low to high.
Root cause:
DeepSeek effort capabilities and provider validation treated depth support as protocol-wide instead of preserving model-specific levels.
Fix:
Add low to the V4 Flash capability, preserve existing Pro and generic DeepSeek behavior, forward low only for V4 Flash or explicitly configured providers, and update desktop guidance.
Verification:
- go test ./internal/config ./internal/provider/openai -count=1
- cd desktop && go test ./... -count=1
- pnpm typecheck
- pnpm build
- ./scripts/check-cache-impact.sh
- ./scripts/cache-guard.sh
Construct the disabled-thinking test client directly so go vet does not flag a copy of the embedded atomic authentication state.
Verification:
- go vet ./...
- go test ./internal/provider/openai -count=1
Problem:
DeepSeek text responses that reached the output limit stopped at finish_reason=length even though the official API supports assistant-prefix continuation.
Root cause:
The OpenAI-compatible provider treated every completed stream as terminal and had no bounded path to the DeepSeek Beta chat endpoint.
Fix:
Continue official DeepSeek text replies once with a wire-only assistant prefix, preserve reasoning_content in thinking mode, merge usage across requests, and fall back to the original truncated answer when Beta continuation is unavailable. Keep ordinary request bytes, custom gateways, tool calls, and persisted history unchanged.
Verification:
- go test ./internal/provider/openai -count=1
- go test ./internal/agent ./internal/provider/openai -count=1
- ./scripts/check-cache-impact.sh
- ./scripts/cache-guard.sh
- go test ./... -count=1
- cd desktop && go test ./... -count=1
Problem:
An interrupted Stable or Preview event could publish some or all npm packages before another surface failed. Rerunning the documented orchestrator then failed on immutable versions that already existed, so postflight could not create the release marker.
Root cause:
npm/build.mjs always published every package directly under the public dist-tag and required that tag to equal the recovering version.
Fix:
Record the approved candidate SHA in every package, reuse only matching immutable versions, fill missing packages under a temporary tag, and advance public dist-tags monotonically after the full package set exists. Conflicting provenance fails closed and stale recovery cannot roll a newer channel backward.
Verification:
- node --test npm/publish.test.mjs
- bash scripts/release-workflows.test.sh
- actionlint release workflows
- npm six-platform dry build
- ./scripts/cache-guard.sh
Problem:
A TurnDone event could reach the frontend before an in-flight TrySteer rejection settled, leaving preserved guidance stranded on the shelf while the session was idle.
Root cause:
The idle queue effect skipped the item while its steer request was marked in flight, but clearing that marker after rejection did not change any effect dependency.
Fix:
Re-arm the idle queue only after a rejected steer settles, so an already-finished turn submits the preserved guidance as the next explicit user turn. Keep ordinary follow-up failures user-controlled to avoid automatic retry loops.
Verification:
The focused 228-case composer suite, frontend production and test typechecks, production build, and bundle budgets passed.
Problem:
Late guidance could be reported as accepted after its target turn stopped consuming input, Desktop profile previews could allow explicit Ask rules without an approver, and concurrent transcript durability lacked a real multi-session reload regression.
Root cause:
Steer intake closed after a separate queue check while host adapters used a void compatibility API, the preview used the legacy nil-approver headless gate, and existing concurrency coverage exercised only the display sidecar.
Fix:
Make final steer admission atomic, route observable hosts through TrySteer, retain rejected Desktop guidance as the next explicit turn, warn on abnormal unapplied guidance, preserve dynamic Bash policy while using an explicit fail-closed preview gate, and verify twelve concurrent Desktop sessions through disk reload.
Verification:
Passed focused race tests, root and Desktop Go suites, Go vet, frontend typechecks and focused tests, frontend production build and bundle budgets, and all eight cache-guard scenarios.
Problem:
- The settings-page subagent try path still used the permissive legacy headless gate.
- Permission provenance could name an Ask rule overridden by a session grant.
- npx snapshot-update equals forms were misclassified as verification, and headless migration docs were stale.
Root cause:
- One entrypoint bypassed the explicit headless posture builder.
- Rule provenance rescanned matching rules without checking the candidate decision.
- npx flag normalization and bilingual headless-mode documentation were incomplete.
Fix:
- Pin subagent try runs to fail-closed headless Ask and add an entrypoint regression.
- Validate rule provenance against the actual per-subject decision.
- Reject vitest/jest snapshot update equals forms and document --auto/-y migration without adding settings.
Verification:
- go test -p 1 ./... -count=1
- cd desktop && go test ./... -count=1
- affected package and focused race suites
- Node 22 frontend tests and typecheck
- scripts/cache-guard.sh and scripts/check-cache-impact.sh
- git diff --check
Problem:
A TurnDone event could reach the frontend before an in-flight TrySteer rejection settled, leaving preserved guidance stranded on the shelf while the session was idle.
Root cause:
The idle queue effect skipped the item while its steer request was marked in flight, but clearing that marker after rejection did not change any effect dependency.
Fix:
Re-arm the idle queue only after a rejected steer settles, so an already-finished turn submits the preserved guidance as the next explicit user turn. Keep ordinary follow-up failures user-controlled to avoid automatic retry loops.
Verification:
- pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx
- pnpm typecheck
- pnpm test:typecheck
- pnpm build
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- focused root and Desktop go test -race coverage
- ./scripts/cache-guard.sh
Problem:
AST math classification rejected delimiter padding, Young macros copied their internal KaTeX expansion, and common SI-unit contexts left numeric formulas literal.
Root cause:
The policy classified the parser value without trimming, normalization kept only the rendered Young expansion, and numeric context used a narrow unit alternation.
Fix:
Separate authored and rendered math sources through the AST pipeline, classify a trimmed projection while preserving exact copy source, and recognize structured SI/scientific units.
Verification:
- pnpm exec tsx src/__tests__/math-golden.test.ts (263 passed)
- pnpm exec tsx src/__tests__/message-selection-copy.test.ts (13 passed)
- pnpm typecheck
- pnpm test:typecheck
- pnpm test
- pnpm build
- pnpm check:css
- go test ./...
- cd desktop && go test ./...
Problem:
- Preview events for different versions could overlap and let an older npm canary publish last.
- CLI, Desktop, and npm child workflows still exposed standalone public Preview paths that bypassed the all-surface release marker.
Root cause:
- The Preview concurrency key included the version tag, while npm had no channel-level serialization.
- Child workflow dispatch inputs and resolvers treated Preview publication as independently recoverable.
Fix:
- Serialize every Preview event and npm dist-tag publisher, and require public CLI, Desktop, and npm Preview releases to come from release-preview.yml.
- Keep Stable/RC recovery and non-publishing Desktop signing checks available, document unified Preview recovery, and add regression coverage.
Verification:
- bash scripts/release-workflows.test.sh
- actionlint v1.7.7 on all release workflows
- git diff --check
Apply performance fixes for model-list polling jank in Settings:
- F1: single-flight dedup + 60s TTL cache (providerModelCache.ts) around
FetchProviderModels; invalidate on key change.
- F2: exponential backoff 1s->60s for failed fetches; skip background
auto-refresh on saveData/slow-2g/2g connections.
- F3: startTransition + useDeferredValue around large model candidate
lists; content-visibility keeps 400+ model rows cheap.
- F4: 30s sessionStorage cooldown for auto-refresh keyed on provider set,
breaking the refreshKey <-> SaveProvider loop.
- F5: rebuild runs in a goroutine after config persist (async, non-blocking
binding response); emit settings:rebuild-{complete,failed} events so the
UI leaves the saving state on completion.
- F6: FetchAllProviderModels batches all providers in one round-trip
(errgroup, 4 concurrent, 15s per-provider timeout); SettingsPanel falls
back to per-provider cached calls on batch failure.
Adds golang.org/x/sync for errgroup (go.mod/go.sum).
When a user asks a diagnostic/troubleshooting question (e.g. "why
isn't my Zotero plugin working in WPS"), the delivery gate currently
demands host-observable work before the agent may finish — even though
the agent cannot run fixup commands on the user's machine. Because
deliveryTaskNeedsEvidence delegates to heuristicInputIsTask (whose
keyword set catches nearly every real-world prompt), the gate deems
almost every turn a task and blocks the final answer. The agent
retries up to maxFinalReadinessBlocksWithProgress (6) times, each
retry producing an identical diagnostic reply, until the turn is
failed — a dead-loop that wastes tokens and frustrates the user.
Root cause: deliveryTaskNeedsEvidence is not gated on whether the task
actually expects a mutation. Diagnostic/advisory conversations
naturally end when advice is given and should not require observable
work.
Fix: return false from deliveryTaskNeedsEvidence when
deliveryTaskNeedsMutation returns false. This keeps evidence
enforcement in place for mutation-worded tasks ("fix the crash in
a.go") while letting pure diagnostic conversations complete after the
agent has provided its advice.
Additional: add negation phrases to deliveryTaskNeedsMutation
("不敢重新安装", etc.) so that inputs containing mutation keywords in
an explicitly negative context are still classified as non-mutation.
Tests added:
- TestDeliveryTaskNeedsEvidenceSkipsDiagnosticConversations: verifies
that six diagnostic inputs (Chinese and English) are classified as
NOT needing evidence, and that mutation-worded inputs still do.
- TestDeliveryDiagnosticConversationCompletes: end-to-end test showing
a text-only advice turn (no tool calls) completes without a
FinalReadinessError when the task is diagnostic.
All 67 existing Delivery/FinalReadiness tests pass unchanged.
Problem:
The integration branch carried early permission and migrated-session variants that broadened PowerShell rule parsing, omitted prompt restoration for legacy Desktop sessions, and left public permission docs inconsistent with headless behavior and the dynamic Bash opt-in.
Root cause:
Compatibility handling lived in the generic rule parser, prompt-less migrated sessions returned before receiving the deterministic system prompt, and the integrated documentation did not include the final safety contracts.
Fix:
Scope legacy PowerShell expansion to additive deny rules, restore and persist fresh prompts for migrated Desktop sessions, exercise the real local-only steer format, align session expectations, and document fail-closed headless execution plus allow_dynamic_bash.
Verification:
- go test -p 1 ./...
- desktop go test -p 1 ./...
- focused race tests for permission, control, agent, capability, config, instruction, and Desktop session paths
- go vet for root and Desktop
- frontend tests and production build
- site tests and production build
- scripts/cache-guard.sh
Co-authored-by: ipanta <[email protected]>
Problem:
Late guidance could be reported as accepted after its target turn stopped
consuming input, Desktop profile previews could allow explicit Ask rules
without an approver, and concurrent transcript durability lacked a real
multi-session reload regression.
Root cause:
Steer intake closed after a separate queue check while host adapters used a
void compatibility API, the preview used the legacy nil-approver headless
gate, and existing concurrency coverage exercised only the display sidecar.
Fix:
Make final steer admission atomic, route observable hosts through TrySteer,
retain rejected Desktop guidance as the next explicit turn, warn on abnormal
unapplied guidance, use an explicit fail-closed preview gate, and verify
twelve concurrent Desktop sessions through disk reload.
Verification:
Passed focused race tests, root and Desktop Go suites, Go vet, frontend
typechecks and focused tests, frontend production build and bundle budgets,
and all eight cache-guard scenarios.
Problem:
The copy implementation from merged PR #7007 had only synthetic KaTeX fixtures and did not exercise Reasonix's real Markdown normalization pipeline.
Root cause:
A clean Git merge could still leave the renderer annotation and selection-copy contracts semantically incompatible.
Fix:
Render normalized Markdown through the production remark and rehype plugins, select the resulting DOM, and assert that copy returns the original pipe and percentage formulas.
Verification:
- pnpm exec tsx src/__tests__/message-selection-copy.test.ts (11 passed)
- pnpm exec tsx src/__tests__/math-golden.test.ts (252 passed)
- pnpm typecheck
- pnpm test:typecheck
Problem:
The PR base advanced with session recovery changes and the merged LaTeX selection-copy implementation.
Root cause:
The feature branch still targeted an older main-v2 snapshot and could not validate the final renderer-to-copy contract.
Fix:
Merge the latest origin/main-v2 without rewriting feature history.
Verification:
The incoming paths do not overlap the renderer changes; focused and full validation will run on the merged tree.
Problem:
Currency amounts wrapped in punctuation could be rendered as math, and KaTeX annotations exposed normalized TeX instead of the user's source.
Root cause:
Currency matching read raw wrapper punctuation, while parser-safety and KaTeX normalization rewrites were destructive before selection-copy could observe the original formula.
Fix:
Normalize only currency boundaries, carry parser-safe inline source through the AST, and restore original TeX into KaTeX annotations and data attributes in both live and export renderers.
Verification:
- pnpm exec tsx src/__tests__/math-golden.test.ts (252 passed)
- pnpm test
- pnpm typecheck
- pnpm test:typecheck
- pnpm build
- pnpm check:css
Context:
The official main-v2 branch advanced after the PR branch was published. Merge the live base without rewriting the already-pushed fork history.
Compatibility:
The upstream LaTeX copy changes touch no files modified by the guarded execution fix, so both histories remain intact.
Verification:
- merge completed without conflicts
- full Go, Desktop, frontend, typecheck, and cache guard suites are rerun before PR publication
Problem:
The PR's lint job failed because the StepFun regional endpoint fix left an unused preset-matching helper behind.
Root cause:
The migration was intentionally converted to a no-op so user-selected regional endpoints remain stable, but its former predicate was no longer referenced.
Fix:
Remove the obsolete helper without changing provider loading or persistence behavior.
Verification:
- go test ./internal/config
- git diff --check
Problem:
- Existing CLI and Desktop manifests predate exact release-note URLs, RC notes had no supported catalog path, and unpublished exact routes could 404.
- The Preview SignPath call-graph test also retained the old allowlist.
Root cause:
- New manifest validation and byte comparisons did not distinguish legacy persisted objects from newly generated candidates.
- Standalone RCs were treated as public changelog identities, and version routes were generated only after publication markers.
Fix:
- Add strict and legacy validation plus semantic comparators that normalize only the missing release-notes field and preserve immutable Desktop manifests.
- Reuse Stable notes for standalone RCs, generate safe unpublished redirects, and update the SignPath contract test.
- Add focused workflow, manifest, route, and RC regression coverage.
Verification:
- bash scripts/release-workflows.test.sh
- go test ./... and go vet ./...
- desktop go test ./... and go vet ./...
- site npm test and npm run build
- node --test scripts/release-notes.test.mjs
- actionlint with the repository CI allowlist
- live v1.18.0 CLI and Desktop manifest compatibility checks
Problem:
Permission modes, Auto Guard recovery, late steer handling, and desktop submission paths could disagree about whether work should run, prompt, or stop. Concurrent session display writes and unbounded parallel task batches also exposed reliability gaps.
Root cause:
The affected paths used separate fallback assumptions, lost explicit rule provenance, accepted empty provider input behind visible display text, replayed stale guidance, and lacked serialization or admission bounds at key state boundaries.
Fix:
Unify permission precedence and matched-rule explanations, fail closed for headless Ask, require exact approval for dynamic Bash, preserve legacy PowerShell rules, cancel dismissed Ask turns, reject empty provider input while preserving skill-only turns, classify safe verification commands, recover stale edits automatically, persist late steer as local-only, serialize display sidecar updates, and cap parallel task batches.
Compatibility:
Existing session and wire formats remain readable; legacy Set-Content, Add-Content, and Out-File rules migrate transparently; unrelated Verb-Noun tool rules retain their meaning; provider prompt prefixes and tool schemas are unchanged.
Verification:
- go test -p 1 ./...
- cd desktop && go test ./...
- cd desktop/frontend && pnpm test
- cd desktop/frontend && pnpm typecheck
- git diff --check
Problem:
Reasonix 1.18 regressed session recovery, mid-turn steering, permission matching, provider configuration, capability routing, and several Desktop workflows.
Root cause:
State ownership and replay paths were inconsistent across rebuilds, dynamic Bash safety had no explicit advanced override, trusted instruction imports were over-confined, and multiple UI and routing surfaces lacked deduplication or stable interaction boundaries.
Fix:
Preserve tab-local session state and raw replay content, reconcile steer and approval ownership, bound parallel work, harden permission and provider behavior, deduplicate capability routes, and repair the affected Desktop interactions with focused compatibility coverage.
Verification:
- go test -p 1 ./...
- cd desktop && go test -p 1 ./...
- pnpm build
- focused and remaining frontend suites
- ./scripts/cache-guard.sh
Problem:
Migrated Desktop transcripts without a system message still lost new turns on tab switches. PowerShell compatibility also reinterpreted generic hyphenated tool names, and public docs described the old permissive headless behavior.
Root cause:
The Desktop resume wrapper returned early for prompt-less sessions, while cmdlet inference lived in the global permission parser. Existing coverage exercised direct storage instead of the Desktop resume and switch lifecycle.
Fix:
Restore the deterministic system prompt while preserving the loaded persistence baseline, add a migrate/resume/turn/switch/reload regression, scope legacy cmdlet expansion to additive deny rules, and align CLI and site documentation.
Verification:
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- go vet ./...
- cd desktop && go vet ./...
- focused race tests for migrated sessions, prompt stability, and permission rules
- ./scripts/cache-guard.sh
- pnpm --dir site test
- pnpm --dir site build
Problem:
Explicit paste recovery truncated bodies containing a matching end marker, and a controller that adopted newer history could reuse paste IDs already present in that transcript.
Root cause:
The frame parser accepted the first matching end marker without validating the label line count, while the TUI paste allocator only learned historical IDs during initial construction.
Fix:
Select frame boundaries whose recovered body matches the encoded line count, and merge authoritative controller history into the wrap-safe ID allocator before every allocation.
Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli -count=1
- env -u DEEPSEEK_API_KEY go test ./...
- (cd desktop && go test ./...)
- go vet ./...
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Context: Stable and Preview need independent reviewed identities while CLI, Desktop, npm, and changelog publication remain one protected release event.
Changes:
- Add a protected Preview orchestrator with a shared ordinal and SignPath preflight.
- Bind reviewed release notes to immutable all-surface publication markers.
- Route downloads and changelog archives by channel with accessible theme-aware navigation.
- Harden release authorization, manifests, tests, and operator documentation.
Compatibility:
- Legacy catalog entries without managed status remain readable and public.
- Existing Stable pointers remain unchanged, and Preview continues mirroring the legacy canary pointer for older Desktop clients.
- No persisted user state or Wails JSON contract changes.
Verification:
- bash scripts/release-workflows.test.sh
- node --test scripts/release-notes.test.mjs
- cd desktop && go test ./cmd/sign ./internal/update
- cd site && npm test
- cd site && npm run build
- actionlint for all release workflows
Integration: Sync main-v2 at e498c4a7 without rewriting the published PR branch.
Scope: The incoming changes repair pasted-text session history and do not overlap the Markdown math policy.
Verification: desktop/go test ./... passed and the staged merge passes git diff --check.
Problem: The classifier documentation still described the pre-review behavior where paired numbers defaulted to math.
Fix: Document the implemented literal-by-default policy, positive mathematical context requirement, and contextual currency repair.
Verification: The comment-only diff passes git diff --check.
Problem:
Selecting rendered math from a resumed session or replayed branch copied only the Unicode display glyph instead of the original LaTeX source.
Root cause:
The copy transcript path marker-rendered live Markdown blocks but reused the already-rendered text for replay bundles, so historical assistant formulas had no source spans.
Fix:
Share replay traversal between visible and clipboard renderers, inject the marker-aware assistant renderer for copy operations, and assign unique prefixes across replayed assistant messages. Add coverage for ordinary and local recovery history.
Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli -run 'TestSelectedText|TestReplaySections' -count=1
- go test -p 2 ./...
- go vet ./...
- cd desktop && go test ./...
- pnpm test:typecheck
- pnpm exec tsx src/__tests__/message-selection-copy.test.ts
- pnpm build
Problem:
Session updates could be lost after migration or concurrent tab writes. Late steer text could execute on a later turn, headless runs could silently approve writes, PowerShell deny rules missed cmdlets, and parallel_tasks accepted unbounded batches.
Root cause:
Append durability and display sidecar updates lacked fsync and serialized read-modify-write. Unconsumed steers were persisted as future user turns. Explicit headless Ask inherited an allow-on-nil bootstrap gate. Bare cmdlets were parsed as tool names, and task batch length was not bounded.
Fix:
Fsync append events and lock display sidecar updates. Persist unapplied steers as provider-excluded local history. Fail closed for explicit headless Ask and add -y/--auto. Normalize PowerShell cmdlet rules, cap parallel_tasks at 64, and add migration, compatibility, and concurrency regressions.
Verification:
- ./scripts/cache-guard.sh
- go test ./...
- cd desktop && go test .
- go vet ./...
- cd desktop && go vet .
- focused go test -race coverage for sessions, steering, permissions, and parallel tasks
Co-authored-by: ipanta <[email protected]>
Integration:
Bring the contributor branch onto the current main-v2 baseline without rewriting its public history.
Resolution:
Accept the latest goal lifecycle, DeepSeek vision, rich-link, session telemetry, and pasted-text fixes. None overlaps the TUI replay-copy implementation being repaired next.
Verification:
The merge completed without conflicts and the integrated tree will be validated together with the focused replay-copy fix.
Integration: Sync main-v2 at 8ccf5bb4 after the review fixes without rewriting the published PR branch.
Scope: The incoming change is limited to the Goal control path and does not overlap the Markdown math policy.
Verification: go test ./internal/control passed and the staged merge passes git diff --check.
Problem: Moving all inline math normalization after Markdown parsing let GFM split pipe-bearing formulas into table cells, while numeric-only spans without recognized local cues could turn ordinary prices into KaTeX.
Root cause: GFM table tokenization runs before remark AST transforms, and the numeric policy defaulted ambiguous pairs to math while reading only direct text siblings.
Fix: Protect pipe-bearing inline math at the syntax boundary, default ambiguous numeric pairs to literal text, require positive range, operator, or scientific-unit context for numeric math, and derive multilingual currency context from source positions across inline formatting.
Verification: pnpm test; pnpm typecheck; pnpm test:typecheck; pnpm build; go test ./... in root and desktop; 240 math golden cases; 28 rich-link cases; bundle budgets; git diff --check.
Problem: A label-like history entry at the platform integer limit made the resumed paste counter overflow to a negative value.
Root cause: History initialization incremented parsed IDs directly, and paste/image allocation used unchecked counter increments.
Fix: Track historical paste IDs and allocate through a wrap-safe counter that skips identifiers already in use.
Verification: go test ./internal/cli -count=1; go test -race ./internal/cli -count=1; env -u DEEPSEEK_API_KEY go test ./...; go vet ./...; desktop go test ./...; ./scripts/cache-guard.sh.
Co-authored-by: SivanCola <[email protected]>
Integration: Sync main-v2 at 0c0422c8 into the PR branch without rewriting published history.
Conflict resolution: Preserve RichMarkdownLink from #6976 while retaining the shared reasonixRemarkPlugins chain used by live rendering and session export.
Verification: Conflict markers are absent and the staged merge passes git diff --check; full branch verification follows with the review fixes.
Problem:
Sessions written before RawContent existed still reopen expanded pasted-text blocks after an upgrade.
Root cause:
History display returned the resolver fallback immediately when RawContent was empty, bypassing legacy pasted-block collapse.
Fix:
Use sanitized Content as the replay source for content-only transcripts, preserve sidecar precedence, and cover the real session save/load path.
Verification:
- focused Desktop history recovery tests
- go test ./...
- cd desktop && go test ./...
- ./scripts/cache-guard.sh
Co-authored-by: SivanCola <[email protected]>
Problem:
An old Goal turn could be admitted correctly, then have its assistant output applied to a replacement Goal when SetGoal ran during the model turn. The stale marker could complete or otherwise advance the new Goal.
Root cause:
The lifecycle epoch guarded continuation input admission, but the subsequent advance() call did not verify that the assistant output still belonged to the Goal that started the turn.
Fix:
Capture the continuation epoch before every Goal-owned real, edited, and subagent turn, carry the admitted epoch across synthetic continuations, and validate it under the goalMachine lock before applying any assistant marker. Add deterministic regressions for both ordinary Goal turns and synthetic continuations replacing the Goal during runner execution.
Verification:
- focused Goal lifecycle regression tests
- ./scripts/cache-guard.sh
- go test -count=1 ./internal/control
- go test -race -count=1 ./internal/control
- go test ./...
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem: Inline dollar classification ran before Markdown parsing, conflating paired numeric math with currency and relying on HTML entities to bypass remark-math.
Root cause: A global regex owned both syntax recognition and Reasonix product policy without AST code boundaries or surrounding-node context.
Fix: Add a shared remark AST policy using unist-util-visit, classify parsed inline math as math, currency, or literal, remove the entity escape path, and share the same plugin chain between live rendering and session export. Adapt the paired-number and one-sided-equality cases from #7054.
Verification: pnpm test; pnpm typecheck; pnpm test:typecheck; pnpm build; git diff --check.
Co-authored-by: Xingbo Zhao <[email protected]>
Integration:
Bring the contributor branch up to main-v2 at d541c17e after adding the paste recovery and Qwen preset compatibility fixes. The merge completed without conflicts and preserves the PR-specific behavior.
Verification:
env -u DEEPSEEK_API_KEY go test ./...
(cd desktop && go test ./...)
go vet ./...
go test -race ./internal/agent -run 'Test.*(Snapshot|SaveRewrite|Foreign|Interrupted|Concurrent)' -count=1
./scripts/cache-guard.sh
git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
Orphaned paste recovery removed significant whitespace and silently chose the newest conflicting legacy label. Existing Qwen preset installs also kept zero context limits after the curated defaults changed.
Root cause:
The paste parser trimmed framed bodies and stopped at the first history match, while the Qwen preset update had no load-time compatibility migration.
Fix:
Recover only a unique user-authored paste body, preserve its bytes inside the framing newlines, and migrate untouched official Qwen installs while retaining customized provider and model values.
Verification:
go test ./internal/cli ./internal/config -count=1
git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
Compact display text replaced complete raw input for pasted and reference turns, degrading capability routing and previous-client replay.
Root cause:
runRefTurnWithResolverSync reused display as the orchestrator raw value even though raw is also the semantic and persistence contract.
Fix:
Restore complete input as raw, keep sidecar and legacy display recovery, and add routing and compatibility assertions.
Verification:
- go test ./...
- cd desktop && go test ./...
- ./scripts/cache-guard.sh
- frontend edit replay tests (17/17)
- frontend pasted block tests (9/9)
Co-authored-by: SivanCola <[email protected]>
Integration: Bring PR #6976 forward to main-v2 at d541c17e without rewriting the contributor branch history.
Compatibility: Preserve the session rebind and overlapping-interface fixes introduced after the prior synchronization; the rich-link implementation remains isolated to Markdown presentation.
Verification: Rerun focused frontend tests, type checking, CSS validation, and production build after this merge.
Integration: Synchronize PR #6976 with main-v2 at 0ac122a3 while preserving the contributor history.
Conflict resolution: Keep main-v2 terminal, goal, bundle-budget, Markdown bare-rendering, and performance changes; add the rich-link rendering test to the current frontend test list.
Verification: rich-link, selection-copy, Mermaid rendering, and render-optimization tests pass; TypeScript, CSS checks, production build, bundle budgets, and narrow-width browser behavior pass.
Integration:
Bring the contributor branch onto the current main-v2 baseline without rewriting its public commit.
Resolution:
Accept the latest Desktop session reattach and status-bar layout fixes from main-v2; they do not overlap the LaTeX clipboard implementation.
Verification:
The merge completed cleanly. Focused clipboard tests and repository checks are rerun on the integrated tree.
Co-authored-by: SivanCola <[email protected]>
Problem:
CI reported SA4006 because the no-marker branch assigned the loop position immediately before breaking.
Fix:
Remove the dead assignment while preserving the branch's existing output and control flow.
Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli -run 'TestSelectedText|TestLanguageCommandRefreshesCurrentController|TestCurrencyCommandPersistsAndRefreshesCurrentController' -count=1
- go vet ./internal/cli
- golangci-lint v2.12.2: 0 issues
Co-authored-by: SivanCola <[email protected]>
Problem: Compact GitHub labels replaced selectable URLs, long external links could not wrap, and parser-only tests did not cover rendered accessibility or copy behavior.
Root cause: The renderer swapped the DOM text for a short label and applied white-space: nowrap to every enriched link.
Fix: Keep the original URL as DOM text, expose the compact label through presentation-only CSS, add contextual accessible names, allow long labels to shrink and wrap, and exercise the rendered link contract.
Verification: pnpm exec tsx src/__tests__/github-link-rendering.test.tsx; pnpm typecheck; pnpm check:css; git diff --check.
Integration:
Bring the contributor branch onto the current main-v2 baseline without rewriting its public commit.
Resolution:
Accept the remote workbench runtime fix from main-v2; it does not overlap the LaTeX clipboard implementation.
Verification:
The merge completed cleanly. Focused clipboard tests and repository checks are rerun on the integrated tree.
Co-authored-by: SivanCola <[email protected]>
Problem:
The official-endpoint guard treated every current and future DeepSeek model as permanently text-only, so an explicitly configured future multimodal model could never receive images.
Root cause:
Vision capability provenance was discarded during provider assembly, leaving the provider unable to distinguish stale provider-wide vision=true from a deliberate per-model opt-in.
Fix:
Keep official DeepSeek models text-only by default, carry positive model-scoped vision provenance from vision_models or model_overrides into provider assembly, and permit image parts only for that explicit opt-in. Add config, provider-serialization, and boot-assembly regressions.
Verification:
- focused config/provider/boot tests passed
- cache guard passed
- root Go suite passed with the live DeepSeek key unset
- focused Desktop tests passed
- frontend composer image tests passed (15/15)
Co-authored-by: SivanCola <[email protected]>
Problem:
A synchronous Notice callback could replace the active Goal after the initial continuation check but before the synthetic turn was composed, pairing an old intercept with the replacement Goal.
Root cause:
Continuation validation and the Goal snapshot used by turn composition were separate operations on opposite sides of externally callable event emission.
Fix:
Revalidate and capture the Goal, AutoResearch task, and delivery scope atomically at synthetic-turn admission. Abort stale continuations and keep admitted turns bound to the captured lifecycle snapshot. Update the merged blocked-signal test to the result-owned intercept model and add a deterministic Notice callback regression test.
Verification:
- go test ./internal/control
- go test -race ./internal/control
- go test ./...
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
Desktop copy could discard prose around a single formula, while the TUI reused display coordinates against a separately wrapped raw transcript. The original commit also dropped the /currency dispatcher and discarded the /language rebuild command.
Root cause:
The desktop path performed global string substitution instead of respecting DOM range boundaries. The TUI assumed rendered Unicode and LaTeX source had identical line and column geometry.
Fix:
Reconstruct desktop selections from ordered KaTeX DOM ranges and MathML annotations. Re-render semantic CLI Markdown only when copy is requested, attach zero-width source markers to the identical visible rendition, and map selected display cells back to formula spans. Restore both runtime-setting dispatchers and add focused regressions for partial, mixed, duplicate, and wrapped selections.
Verification:
- go test ./internal/cli -run 'TestSelectedText|TestLanguageCommandRefreshesCurrentController|TestCurrencyCommandPersistsAndRefreshesCurrentController|TestRenderInlineMath|TestCopyToClipboard' -count=1
- go vet ./internal/cli
- pnpm exec tsx src/__tests__/message-selection-copy.test.ts
- pnpm typecheck
- pnpm test:typecheck
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Integration:
Bring the contributor branch onto the current main-v2 baseline without rewriting its public commit.
Resolution:
Preserve the stable-section Markdown renderer introduced on main-v2. The clipboard implementation reads KaTeX source from the selected DOM instead of adding a render-time annotation pass.
Verification:
The conflict was resolved semantically; the final merged tree is validated after the focused fix is applied.
Co-authored-by: SivanCola <[email protected]>
Problem:
Legacy sessions can still reopen expanded pasted-text payloads, and edit replay can resubmit provider-only capability, goal, hook, and resolved-reference context.
Root cause:
History always preferred non-empty RawContent without distinguishing old expanded values from the new compact display format, while SubmitText copied the full provider-facing Content.
Fix:
Prefer persisted display sidecars, collapse legacy expanded paste blocks when no sidecar exists, and sanitize replay text down to user-authored content. Keep display and pagination decisions on the same recovery path and cover session restart plus ref-turn raw persistence.
Verification:
- go test ./...
- (cd desktop && go test ./...)
- ./scripts/cache-guard.sh
- frontend edit-replay and message-pasted-blocks tests
Co-authored-by: SivanCola <[email protected]>
Problem:
Reattaching a detached target over an idle visible source closed the source controller but left its shared plugin-host reference counted. Repeated session switches could accumulate references and keep MCP host processes alive.
Root cause:
applyRuntimeTab replaced the visible tab SharedHostKey before the idle source teardown captured it, so the caller could no longer release the source-owned reference.
Fix:
Transfer the idle source host key with its controller, sink, and lease, then release it after controller shutdown outside App.mu. Keep active-source ownership unchanged and add a regression that verifies the shared-host refcount drops from two to one.
Verification:
- cd desktop && go test . -run Test\(RebindTabToDetachedSessionReleasesIdleSourceSharedHost\|RebindTabToDetachedSessionPreservesRunningSourceRuntime\|CanReclaimRejectsDetachedRuntimeOwner\|RebindTargetLeaseFailureKeepsSourceRuntimeAtomic\|RebindPostLeaseValidationFailureRollsBackCandidate\|BuildTabControllerReattachesDetachedSessionRuntime\) -count=1
- cd desktop && go test -race . -run Test\(RebindTabToDetachedSessionReleasesIdleSourceSharedHost\|RebindTabToDetachedSessionPreservesRunningSourceRuntime\) -count=1
- cd desktop && go test ./...
Co-authored-by: SivanCola <[email protected]>
Problem:
The docked status bar change left an empty row in IM detail views, placed narrow classic terminals over chat, and retained duplicate workbench padding.
Root cause:
Grid rows referenced the always-nonzero dock token instead of the visibility-aware status bar height, while responsive row overrides conflated chrome-visible and chrome-hidden layouts.
Fix:
Use --statusbar-height for dock rows, collapse it through layout--statusbar-hidden, restore separate responsive terminal rows, remove residual workbench padding, add regression contracts, and normalize the CSS formatting introduced by the original edit.
Verification:
- pnpm test
- pnpm check:css
- pnpm typecheck
- Browser computed-style and geometry checks at 800px and 1200px
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem: Session hydration could issue duplicate context requests, while persisted last-usage fields could be overwritten by planner or subagent telemetry.
Root cause: The fallback effect raced the authoritative hydration request, and recordUsage updated executor fallback fields for every usage source.
Fix: Keep persisted Last* context scoped to executor usage, remove the competing active-tab fetch, and add compatibility, rebind fallback, and hydration coalescing regression coverage.
Verification: go test ./...; pnpm test:typecheck; tsc --noEmit; focused frontend hydration and context tests.
Co-authored-by: SivanCola <[email protected]>
Problem:
- PR 6971 had fallen behind main-v2 and conflicted with the new Kimi K3 preset helper.
Root cause:
- Both branches added provider override helpers at the same insertion point.
Fix:
- Merge current main-v2 and retain both the Kimi K3 and Qwen per-model overrides without rewriting contributor history.
Verification:
- go test ./internal/agent ./internal/cli ./internal/config
- ./scripts/cache-guard.sh
Integration:
- Update the contributor branch to the latest main-v2 without changing the catalog fix.
- Preserve the empty effort-level normalization and its end-to-end regression coverage.
Verification:
- go test ./internal/remote/workbench/runtime -run ^TestWorkspaceCatalogEmptyEffortLevelsEncodeAsArray$ -count=1
- go test -race ./internal/remote/workbench/runtime -run ^TestWorkspaceCatalogEmptyEffortLevelsEncodeAsArray$ -count=1
- go test ./internal/remote/protocol -count=1
- REASONIX_RELEASE_CACHE_GUARD=1 go test ./...
- cd desktop && go test ./...
- go vet ./...
- go build ./...
- git diff --check
Problem:
- Same-revision snapshot recovery could overwrite an interrupted foreign write.
- Paste recovery mutated loaded history, reused labels after resume, and trusted assistant text.
- Mixed Qwen catalogs inherited a provider-wide 1M context budget.
Root cause:
- Revision equality was treated as ownership without checking the persisted digest.
- Recovery lived in the persistence layer while paste IDs were process-local.
- Qwen context metadata was configured only at provider scope.
Fix:
- Require persisted-state ownership for non-prefix snapshots and preserve foreign bytes.
- Keep loaded history byte-stable, recover labels only on explicit resubmission, and continue IDs from history.
- Add per-model context overrides for smaller Qwen catalog models.
Verification:
- go test ./internal/agent ./internal/cli ./internal/config
- go test -race ./internal/agent -run snapshot recovery cases
- ./scripts/cache-guard.sh
- full repository suite passed except an opt-in live probe using an expired DEEPSEEK_API_KEY; the package passed with that variable unset
Co-authored-by: SivanCola <[email protected]>
Contract:
Fresh desktop sessions must persist their scope, workspace root, and topic before controller reconciliation, including when the session directory is not yet discoverable.
Coverage:
- Verify project and global binding metadata at creation.
- Verify fallback reconciliation preserves project scope outside known directories.
- Verify corrupt sidecars fail visibly and rejected bindings leave no session artifacts.
- Verify read-modify-write preserves existing BranchMeta persistence and listing fields.
Verification:
- go test -count=1 . -run 'TestPinNewEmptySessionBranchMeta|TestPinnedSessionMetaKeepsProjectBindingOutsideKnownDirectories|TestPinSessionBranchMeta'
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
New desktop sessions could continue into controller startup after their workspace binding metadata failed to persist, leaving the original project-to-global reclassification window open.
Root cause:
The pinning helper ignored BranchMeta read and write errors and performed an unlocked read-modify-write at each call site.
Fix:
Serialize the BranchMeta update with the session metadata lock, normalize scope/root values, propagate failures from every new-session call site, and remove any newly created unbound session artifacts before returning.
Verification:
- go test -count=1 . -run 'TestEnsureBlankTab|TestTrashTopicFallbackCreatesUnindexedBlank|TestTransientFallbackIndexesOnFirstUserTurn|TestOpenProjectTab'
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem: PR #6961 returned intercepts from advance(), then wrote them back through a second lock acquisition. A concurrent goal replacement, stop/resume, or newer FSM step could therefore consume stale output from an older lifecycle.
Root cause: goalAdvanceResult carried no identity for the machine state that produced it, and the orchestrator round-tripped the output through interceptMsg.
Fix: remove the pending intercept field, consume the returned intercept directly, and validate a monotonic continuation epoch before starting the synthetic turn. Lifecycle transitions and newer FSM steps invalidate older results.
Verification: go test ./internal/control; go test -race ./internal/control; go test ./...; git diff --check.
Co-authored-by: SivanCola <[email protected]>
Context: Bring PR #6961 onto the current review baseline before changing the goal FSM output contract.
Integration: Preserve current goal snapshot, transactional persistence, raw-input propagation, and large-session fixes while keeping the contributor refactor isolated for follow-up changes.
Verification: Resolve the merge without conflicts and validate the resulting focused PR diff before push.
Problem:
Reattaching a detached target session unconditionally closed the visible source controller, cancelling its active turn and background jobs.
Root cause:
The fast reattach path bypassed the normal rebind active-runtime detachment semantics and retired the source after attaching the target.
Fix:
Atomically move an active source runtime into detachedSessions while attaching the detached target. Only idle source runtimes are closed and released. Add regression coverage for dual-running session switches and detached-owner lease reclaim.
Verification:
- cd desktop && go test . -run Test\(RebindTabToDetachedSessionPreservesRunningSourceRuntime\|CanReclaimRejectsDetachedRuntimeOwner\|RebindTargetLeaseFailureKeepsSourceRuntimeAtomic\|RebindPostLeaseValidationFailureRollsBackCandidate\|BuildTabControllerReattachesDetachedSessionRuntime\) -count=1
- cd desktop && go test -race . -run Test\(RebindTabToDetachedSessionPreservesRunningSourceRuntime\|CanReclaimRejectsDetachedRuntimeOwner\) -count=1
- cd desktop && go test ./...
Co-authored-by: SivanCola <[email protected]>
Problem:
The blocked lifecycle coverage only asserted the final status, so it could not detect an idle reminder replacing the normal continuation prompt.
Root cause:
No test exercised a blocked marker while the idle counter was one turn below its threshold.
Fix:
Add a focused goal-machine regression that seeds the near-threshold state and verifies an explicit blocked signal produces no idle intercept.
Verification:
- go test ./internal/control -run '^TestGoalBlockedSignalDoesNotTriggerIdleIntercept$' -count=1
- go test ./internal/control -run 'Goal' -count=1
- go test -race ./internal/control -run '^TestGoalBlockedSignalDoesNotTriggerIdleIntercept$' -count=1
- go test ./...
Problem:
DeepSeek's official chat API rejects image_url content parts, while the original fallback treated image data URLs as file paths and could copy large base64 payloads into message text.
Root cause:
Endpoint image capability was conflated with the DeepSeek reasoning protocol, and the text fallback assumed image values were filesystem paths instead of resolved data URLs.
Fix:
Disable direct image input for official DeepSeek endpoints at both capability resolution and provider assembly, keep explicit vision support for custom DeepSeek-compatible gateways, remove the text fallback, and add request-shape regressions for user and tool images.
Verification:
- focused config, provider, and controller tests passed
- cache guard passed
- root Go suite passed with the unavailable live credential unset
- desktop image capability and frontend composer image tests passed
Co-authored-by: SivanCola <[email protected]>
Problem:
Compatible gateways that return Anthropic-style usage counters were parsed, but input_tokens was treated as the complete prompt and total_tokens stayed zero. Cache writes also omitted ordinary uncached input from miss accounting.
Root cause:
Anthropic-style usage splits the prompt across input_tokens, cache_creation_input_tokens, and cache_read_input_tokens, while Reasonix expects complete prompt, hit, miss, and total counters.
Fix:
Sum all three input components, derive missing totals, preserve DeepSeek and OpenAI field precedence, replace struct-only assertions with decoded JSON fixtures, and remove the unrelated desktop version change from the PR diff.
Verification:
- go test ./internal/provider/openai -run 'TestNormaliseUsage(AnthropicStyleFallback|DeepSeekShape|MiMoShape)$' -count=1
- go test ./internal/provider/openai ./internal/provider
- go test -p 4 ./...
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Update the contributor branch to the latest review baseline so already-merged Windows filename work and the build-version placeholder no longer appear in the PR diff.
Move the StatusBar out of the footer and into its own dedicated CSS
Grid row so the sidebar and workspace panel no longer extend behind
the semi-transparent bottom status bar.
- Extract <StatusBar> from the footer and place it as a direct child
of the .layout grid, wrapped in the existing sidebarImDetailConnection guard.
- Change the layout grid from 2 rows (chrome + content) to 3 rows
(chrome + content + statusbar): the statusbar row uses
var(--statusbar-dock-height) so it scales with the interface text size.
- Remove position:fixed from :root[data-theme-style] .statusbar — the
grid row now keeps it anchored to the viewport bottom.
- Properly place the statusbar in every grid variant:
normal (row 3), workbench-chrome-hidden (row 2), terminal-open (row 3
or 4), and in the narrow-viewport media query.
- Hide the statusbar row in creation mode (.app--creation .statusbar)
since that layout has no bottom bar.
- Drop the --statusbar-height compensation from the sidebar and
workbench-dock bottom padding — the grid row boundary handles the
separation, so the extra padding only created dead space.
- Fix :root[data-theme-style] .footer bottom padding (38px → 10px):
the 38px was reserved for the old fixed statusbar and caused a
visible gap below the composer.
- Let the workspace-panel-resizer fill the full grid-cell height
(100% instead of calc(100% - var(--statusbar-height))) since the
resizer no longer needs to avoid a fixed overlay.
- Update the app-chrome-tabs and workspace-layout tests to match the
new grid-template-rows declarations.
Fixes#7030
The previous fix made both branches of the RawContent check set
hm.SubmitText to m.Content, making the else-if redundant.
Collapse them into a single else branch for clarity.
#1 Root cause: runRefTurnWithResolverSync passed submitText as raw, causing RawContent to store the expanded text instead of the display text. This made history loading leak BEGIN/END blocks into the bubble. #2 Fix SubmitText assignment: set hm.SubmitText to m.Content so the frontend can parse paste blocks. #3 Fix fallback: wrap StripComposePrefixes with StripReferencedContextPrefix to strip file XML blocks when .display.json is missing.
Validating the sweep against live data before enabling it turned up two ways it
would have aged current reports.
#6563 filled the form's version field with 1.0.0 and named the real build,
v1.17.13, further down in the reproduction steps. 1.0.0 shipped, so the
published-tag check could not catch it, and the report would have been swept as
seventeen minors old. Age now comes from the newest released version mentioned
anywhere in the body: a report cannot be older than the newest build it names.
Filtering to published versions keeps Node versions and window sizes out.
Reading the whole body also let feature requests in, and "does it still
reproduce" is a question only a defect report can answer, so the sweep now
requires the bug label.
The target count is unchanged at 160, but the membership is not: six typo'd
reports and seven feature requests left, and old reports whose only version
evidence was in prose came in.
65% of the open tracker has never had a reply, and 160 open bug reports name a
release nine or more minors back — old enough that nobody can say whether the
symptom survived. Leaving them open costs the reports that can be acted on;
closing them on age alone would be a guess dressed up as triage.
So: ask, wait 21 days, close only what nobody answered. A close is "not planned"
with reopening invited, and any comment after the ask — from anyone — takes the
issue back out of the sweep. Severity labels (data-loss, security, crash) and
anything a maintainer already replied to never enter it at all, because a bot
must not time out a judgement a human already made.
The version check is against the published tag list, not the version format. A
dry run first targeted six issues reporting "1.7.18", "1.7.13" and "1.7.10":
1.7.x only ever reached 1.7.0, so those are dropped digits from 1.17.x — current
releases that parse cleanly as eleven-minor-old ones. Six current reports would
have been swept as ancient.
Posting stays behind the STALE_SWEEP_ENABLED repository variable, so merging
this cannot start commenting on its own.
A PR writing "Fixes #N" auto-closes its issue on merge. One writing "Refs",
"Related" or "Addresses" does not, so the report stays open waiting for a
follow-up after the release that nobody sends. Auditing the last 400 merged
PRs found 33 open issues referenced by shipped work; only three could be
closed on the evidence, and most of the rest were blocked on nothing but an
unanswered "please re-test".
On every published release this now diffs against the previous tag in the
same series, collects the issues those PRs referenced, and asks each still-open
reporter to verify. It never closes anything — deciding a report is resolved
stays human.
Two traps the audit hit are pinned by tests rather than left to be rediscovered:
- "owner/repo#123" is an upstream reference. Matching it once pulled a
wailsapp/wails issue number in as if it were ours.
- GitHub's issues API answers for pull requests too, so a consolidation PR
listing "PR #5576 by @x" reads as a perfectly good open issue. A dry run
against desktop-v1.18.0 showed 26 of 40 references were pull requests; without
the check the bot would have asked their authors to re-test.
The PR template now says where to put the keyword, since a "- Fixes#123" list
item is exactly the silent no-op that produced this backlog.
A /theme switch repainted every colour in one frame, which reads as a
glitch rather than a deliberate change. The new palette now travels
across the frame from left to right over roughly 290ms.
Both frames are rendered once when the sweep starts, so the cost is two
renders rather than two per animation step; the transcript is frozen for
the duration, and a switch during a running turn skips the animation
rather than freeze streaming output. No-colour terminals, terminals
under 24 columns, and switches that resolve to the current palette also
fall through to the instant repaint.
Rows are composed to an exact cell count. Slicing on an odd column
cannot split a double-width rune, so a CJK transcript otherwise runs a
row one cell long and the boundary zigzags down the screen.
Windows Terminal advertises 24-bit colour through WT_SESSION and sets
neither COLORTERM nor TERM_PROGRAM, so the hand-rolled check never
matched it and every theme colour rendered as its 256-colour fallback.
The same gap hid kitty, alacritty, ghostty, foot, rio, st and contour,
and never downgraded tmux or screen.
Colour capability was also split across two globals that could disagree:
colorEnabled decided on/off from its own NO_COLOR/isatty rules while
supportsTrueColor separately decided fidelity. Both collapse into one
detected profile, and colorOn/trueColorTerminal derive from it, so
CLICOLOR_FORCE and the cmd.exe Windows API path come along for free.
The mode pill bypassed the theme entirely with raw lipgloss colours,
which meant NO_COLOR never suppressed it; it now goes through the same
accessor as everything else.
Terminal background probing was opt-out: thirteen call sites had to
remember a NoProbe wrapper or they would read stdin while the TUI owned
it. Inverted to opt-in, which removes the wrapper and the footgun.
The hand-chosen 256-colour column stays. Delegating it to the library
was tried and reverted: the quantiser maps #222631 to navy 17 at 34x the
distance of the correct grey, and even a proper Lab nearest-neighbour
flattens the dark diff backgrounds to grey, losing the red/green tint
that carries their meaning.
Problem: the full Windows release sweep timed out two ACP end-to-end prompt scenarios after two seconds, leaving their session lease files locked during cleanup.
Root cause: drainPrompt used a two-second idle limit while exercising the complete ACP server, controller, agent, tool-loop, and transcript-persistence pipeline. Loaded Windows runners can exceed that scheduling window without violating the protocol contract.
Fix: keep the end-to-end helper bounded while extending its idle responsiveness window to five seconds and documenting the covered pipeline.
Verification: go test ./internal/acp -run '^TestE2E(ToolTurnAndPersistence|SessionLoad)$' -count=20; go test -race ./internal/acp -run '^TestE2E(ToolTurnAndPersistence|SessionLoad)$' -count=5; go test ./internal/acp
Problem: Windows release runners can take more than one second to schedule asynchronous controller completion during the full desktop suite, causing Fork or Rewind tests to fail before their semantic assertions and leave lease files locked during cleanup.
Root cause: the shared waitNotRunning test helper used a one-second outer bound that was tighter than loaded Windows runner scheduling latency.
Fix: retain the polling invariant while extending the bounded responsiveness window to five seconds and document why the platform-neutral test needs that allowance.
Verification: go test . -run '^Test(Fork|Rewind)KeepsProjectWorkspacePrompt$' -count=20; go test -race . -run '^Test(Fork|Rewind)KeepsProjectWorkspacePrompt$' -count=5; go test ./...
Problem: The existing v1.18.0 catalog covered only the earliest merged work and the automated bilingual generator returned truncated JSON twice.
Fix: Replace the draft with a source-grounded bilingual summary of the full v1.17.21..main-v2 release range, including the Guard automation upgrade note and all major product, reliability, security, and release-channel changes.
Verification: release-notes validate, unit tests, zh/en render, grounded PR reference audit, privacy scan, and git diff --check.
Problem: Windows CI failed because the config-lock registry test required Unix mode 0700 from FileMode.Perm().
Root cause: Windows protects the per-user registry through inherited ACLs and does not represent that ACL through Unix permission bits.
Fix: Skip the Unix-only permission-bit assertion on Windows while retaining the cross-process lock and user-profile registry coverage.
Verification: go test ./internal/config -run 'TestAcquireConfigEditLock|TestLockUserConfigEdits' -count=1; go test ./internal/config; go vet ./internal/config; GOOS=windows GOARCH=amd64 go test -c ./internal/config
* fix(desktop): prevent stale transcript pixels on Windows / 修复 Windows 会话残影
* Harden Windows transcript compositor backing
Problem:
- Windows WebView2 can retain stale TurnActions pixels after React removes old chat nodes in Classic and Workbench layouts.
- A broad transcript rule would also alter History and Creation surfaces, while background packs must keep their existing viewport geometry.
Root cause:
- Theme-pack CSS leaves the main scrolling transcript transparent, so WebView2 has no opaque repaint surface when child layers disappear.
- The earlier #6365 repair covered Creation only.
Fix:
- Scope an opaque Canvas-backed compositor surface to the primary Classic and Workbench transcript.
- Recreate background-pack image, pane opacity, task opacity, and safe-area washes on clipped viewport-fixed pseudo-layers without touching History or Creation.
- Expand the source contract to cover selector scope, stacking, viewport geometry, and all theme scene variants.
Verification:
- pnpm exec tsx src/__tests__/turn-actions-rendering.test.ts (9 passed)
- pnpm check:css
- pnpm typecheck
- git diff --check
- Chromium computed-style, scroll-stability, and Windows/non-Windows visual parity checks
* fix(desktop): reveal Windows theme backgrounds
Problem:
Windows theme packs loaded correctly, but their scene images stayed hidden after making Classic and Workbench transcripts opaque for WebView2 repaint correctness.
Root cause:
The image and overlay pseudo-elements retained negative z-index values, so they painted behind the transcript opaque compositor backing.
Fix:
Use nonnegative image and overlay layers, raise only direct transcript children above both layers, and enforce the ordering in the source-contract regression.
Verification:
Focused rendering regression 10/10; CSS syntax and z-index checks; TypeScript; full frontend test and production build; native Win11 Wails build and WebView2 task scroll plus home new-session validation.
All Qwen provider presets (qwen-cn, qwen-global, qwen-coding-plan-*)
were missing the ContextWindow field, causing the resolver to fall
back to 128k. Qwen 3.x models (qwen3.5-plus, qwen3.6-plus,
qwen3.7-plus, qwen3.7-max, qwen3-coder-plus) all have 1M context
windows per Alibaba's API docs and qwen-code's tokenLimits.ts.
Ref: qwen-code packages/core/src/core/tokenLimits.ts
* fix(guard): bind repair apply to confirmed preview
* Harden Guard preview confirmation binding
Problem: non-interactive apply-plan could accept a bare --yes, while preview IDs omitted derived-state contents and full pending-update identity.
Root cause: confirmation hashes covered only display previews and were checked once before the plan loop, so state-dependent actions could drift after confirmation.
Fix: require a prior preview ID for non-interactive apply-plan, bind opaque file and transaction state, re-check each action immediately before execution, and preserve unrelated multi-action behavior. Update English and Chinese recovery docs.
Verification: go test ./internal/repair ./cmd/reasonix-guard; go test -race ./internal/repair; go vet ./...; go test ./...; git diff --check.
Co-authored-by: SivanCola <[email protected]>
* Serialize confirmed Guard repair mutations
Problem: preview IDs were rechecked before mutation, but another process could still replace a target during the final rename window or race on last-repair.json, losing or mixing undo records.
Root cause: repair actions lacked a shared transaction lock and per-target cross-process locks, while confirmed snapshot/config bytes were read again after approval and replacement writes could overwrite a concurrent recreation.
Fix: serialize repair bookkeeping before canonical target locks, revalidate confirmed state inside the lock and around renames, publish restored content with create-only atomic writes, bind complete pending-update identity, preserve concurrent writers, and keep undo under the same lock order. Add focused coverage for wait/recheck, rename windows, snapshot bytes, unreadable LKG nodes, transaction serialization, and rollback identity.
Verification: go test ./internal/fileutil ./internal/config ./internal/repair ./cmd/reasonix-guard; go test -race ./internal/fileutil ./internal/repair; go vet ./...; env -u DEEPSEEK_API_KEY go test ./...; Windows test-binary cross-compilation for internal/repair and cmd/reasonix-guard; git diff --check.
Co-authored-by: SivanCola <[email protected]>
* Normalize Windows repair lock test paths
Problem: the Windows CI test waited for a raw backslash path while the lock hook receives the canonical lower-case slash form, so the test timed out before exercising the recheck.
Root cause: the regression test compared platform-specific input text instead of the production lock key representation.
Fix: normalize test hook paths with the same Windows case and separator rules used by repair mutation locks.
Verification: focused repair lock tests pass on macOS; Windows amd64 test-binary compilation passes.
Co-authored-by: SivanCola <[email protected]>
* test(jobs): remove StartForSession race
Keep the worker blocked while assertions inspect its state and protect job fields with the existing mutex. This prevents the race job from observing concurrent completion writes when validating accepted input.
Co-authored-by: PaulDing98 <[email protected]>
* fix(repair): bind target identity into Guard preview confirmation
Problem:
- previewId hashed only exported preview fields and content-based state IDs,
so identical reasonix.toml content in two project roots produced the same
confirmation and apply-plan --yes could mutate the unconfirmed project.
- Mutation locks used Abs/Clean only, so symlink path aliases (and macOS
case variants) took different locks for the same real node.
- rollback_update bound only the pending transaction JSON and skipped target
locks, so a stable pending update could still roll back an unconfirmed
live release unit.
- When a target was recreated after quarantine rename, the move was not
recorded in last-repair, so UndoLastRepair could restore an unrelated prior
repair.
Root cause:
- Confirmation identity omitted canonical target identity; lock keys and
rollback binding did not converge on real paths or live release-unit state;
post-rename failure paths returned before persisting the already-moved node.
Fix:
- Embed an irreversible canonical target identity in per-file state IDs used
by previewId, while verifying quarantined nodes against the original path
identity.
- Resolve existing symlink parents and case-fold on Windows/macOS when
building mutation-lock keys and target identities.
- Bind current release-unit file states into rollback preview state, take
shared target locks during prepare/rollback, and re-check under those locks.
- Persist the quarantine/move transaction when a concurrent recreate is
detected so undo restores the confirmed node and retains the rewrite as redo.
Verification:
- go test ./internal/repair/ -count=1
- go test -race ./internal/repair/ ./internal/fileutil/ -count=1
- go test ./cmd/reasonix-guard/ -count=1
- go vet ./internal/repair/
* fix(repair): close symlink, app-bundle, and macOS handoff confirmation gaps
Problem:
- canonicalRepairPath EvalSymlinks'd the full path, so two leaf symlinks
sharing a referent produced the same previewId, and renaming a confirmed
symlink leaf changed its identity and made apply fail.
- App-bundle rollback bound only directory kind/mode for the live .app and
omitted the backup tree, so interior executable drift still accepted an
old previewId and rolled back.
- macOS self-update held mutation locks only while writing the pending
transaction; the actual mv/ditto ran in a shell helper that ignored the
shared flock, racing Guard rollback into mixed bundles.
Root cause:
- Leaf nodes are what Lstat/Rename mutate, but identity resolved through them;
directory release units need tree digests; handoff and repair used different
processes without a shared lock holder over the swap window.
Fix:
- Resolve only parent-directory symlinks and reattach the original leaf name
for mutation locks and preview target identity.
- Bind full tree digests for directory release-unit nodes and include backup
paths in pending-update preview state.
- Replace the macOS shell handoff with a detached self-subprocess that waits
for the old PID, then holds LockRepairMutations around the directory swap.
- Export LockRepairMutations/Timeout for desktop handoff and add regressions.
Verification:
- go test ./internal/repair/ -count=1
- go test -race ./internal/repair/ -count=1
- go test ./cmd/reasonix-guard/ -count=1
- cd desktop && go test -count=1 -run TestMacUpdate .
- go vet ./internal/repair/ and desktop
* fix(repair): secure updater claims and path case locks
Problem:
The detached macOS updater trusted filesystem paths from command-line arguments, and repair identities folded path case by operating system rather than by the target filesystem.
Root cause:
The handoff only checked that a pending-update file existed before swapping bundles. Path canonicalization assumed every macOS and Windows filesystem was case-insensitive.
Fix:
Persist optional handoff paths and owner identity in the pending transaction, claim them under strict pending and target locks, revalidate the staged bundle before swapping, and remove filesystem paths from child arguments. Detect case sensitivity from the actual macOS directory or Windows per-directory flag before folding lock and preview identities.
Verification:
- go test ./internal/repair/ -count=1
- go test -race ./internal/repair/ ./internal/fileutil/ -count=1
- go test ./cmd/reasonix-guard/ -count=1
- cd desktop && go test -count=1 -run TestMacUpdate .
- cd desktop && go test -race -count=1 -run TestMacUpdate .
- go vet ./internal/repair/
- cd desktop && go vet .
- GOOS=windows GOARCH=amd64 go test -c ./internal/repair
* fix(repair): scope case probe helper by platform
Problem:
The Linux golangci-lint job reported existingRepairPathParent as unused.
Root cause:
The helper lived in a common file while its only callers were excluded from Linux builds by Darwin and Windows build tags.
Fix:
Move the helper into a Darwin-or-Windows platform file so it is compiled exactly where the filesystem case probes use it.
Verification:
- golangci-lint run --timeout=5m
- go test ./internal/repair/ -count=1
- cd desktop && go test -count=1 -run TestMacUpdate .
- GOOS=windows GOARCH=amd64 go test -c ./internal/repair
* fix(repair): preserve verified rollback recovery
Problem:
- File rollback could install backup bytes that changed after preflight validation.
- A failed macOS backup restore still cleared pending recovery state and cleanup material.
- NFC and NFD aliases on APFS produced different repair lock and preview identities.
Root cause:
The staged-copy hash was ignored, macOS rollback cleanup was unconditional, and canonical repair paths handled case aliases without filesystem-aware Unicode normalization.
Fix:
Verify the staged rollback bytes against the transaction hash before mutating live targets. Make macOS rollback compensating and retain pending, backup, and staging state when restoration fails. Normalize repair identities to NFD only on APFS and HFS while preserving distinct names on other filesystems.
Verification:
- go test ./internal/repair/ -count=1
- go test -race ./internal/repair/ ./internal/fileutil/ -count=1
- go test ./cmd/reasonix-guard/ -count=1
- cd desktop && go test ./... -count=1
- cd desktop && go test -race -count=1 -run TestMacUpdate .
- go vet ./internal/repair/
- cd desktop && go vet .
- golangci-lint run --timeout=5m
- GOOS=windows GOARCH=amd64 go test -c ./internal/repair
* Harden transactional updater handoffs
Problem:
Repair and self-update paths could continue without a pending-state lock, apply a prepared update after its transaction or release unit changed, or restore an unverified macOS bundle tree. Linux also persisted a CLI sibling that transaction validation rejected.
Root cause:
Pending transitions used a best-effort unbounded lock, file updaters locked paths without claiming the exact prepared transaction, and app-bundle handoff/rollback validation did not bind both the staged tree and original backup tree across the rename window.
Fix:
Use bounded fail-closed pending locks and a consistent pending-to-target lock order. Claim Linux and Windows replacements by version, creation identity, platform, and canonical release-unit paths. Bind macOS staging, original, installed, and backup trees; recover rejected handoffs only when the original bundle is still verified; protect backup paths and reject symlink escapes. Add focused concurrency, drift, rollback, compatibility, and cross-platform tests.
Verification:
- go test ./internal/repair/ ./internal/fileutil/ ./cmd/reasonix-guard/ -count=1
- go test -race ./internal/repair/ ./internal/fileutil/ ./cmd/reasonix-guard/ -count=1
- cd desktop && go test ./... -count=1
- cd desktop && go test -race ./... -count=1
- go vet ./... and cd desktop && go vet ./...
- golangci-lint run --timeout=5m ./...
- Windows amd64/arm64 test-binary compilation for repair, desktop, and update-helper
Co-authored-by: SivanCola <[email protected]>
* Complete fail-closed repair and updater transactions
Problem:
Repair, undo, and self-update entry points could act on state that changed while waiting for locks. File-update completion also needed a crash-safe installed-unit binding, and platform handoffs could execute mutable or incompletely claimed payloads.
Root cause:
Invocation identity was sampled too late or incompletely, mutable pending state doubled as installed state, cleanup paths did not consistently compare ownership, and Windows/macOS handoffs did not bind every source, backup, staging, and release-unit transition.
Fix:
Bind every mutating invocation before lock waits and recheck it under a pending-to-target lock order. Persist create-only installed-state sidecars, preserve recovery material on ambiguity, use atomic no-replace publication, validate complete app and file trees, isolate and hold the verified Windows installer through execution, and make rollback/undo crash-retryable.
Verification:
go test ./internal/repair ./internal/fileutil ./cmd/reasonix-guard -count=1
cd desktop && go test ./... -count=1
Windows amd64 and arm64 update-helper test-binary compilation
gofmt -l internal/repair desktop
git diff --check
Co-authored-by: SivanCola <[email protected]>
* Document fail-closed legacy update recovery
Problem:
Recovery documentation described additive update state as generally backward compatible and a Linux comment still described the obsolete mutable-pending design.
Root cause:
The text was not updated when installed release-unit state moved to a transaction-unique sidecar and legacy destructive actions began requiring explicit ownership proof.
Fix:
Document immutable pending state, crash-safe sidecar recovery, and the distinction between readable legacy transactions and authorization for handoff, healthy commit, or app-bundle rollback.
Verification:
git diff --cached --check
Focused repair and updater tests, full race suites, vet, lint, and cross-platform compilation passed against the merged main-v2 baseline.
Co-authored-by: SivanCola <[email protected]>
* Close updater execution and compensation races
Problem:
- Generic failure-marker writes could let a stale invocation overwrite a newer transaction marker.
- Legacy app rollback could move a symlink backup before rejecting it, and file publish could leave a tampered staged node live.
- Windows self-update could execute a helper that changed after transaction prepare, overwrite a concurrently allocated cache path, or add a portable alias that was absent before.
- macOS updater commands were resolved through PATH.
Root cause:
Mutation ownership was not rebound under the pending lock at every public entry point. Helper validation proved only PE architecture, staged-file verification followed symlinks, and helper cache publication used replace semantics.
Fix:
Rebind generic failure markers after lock acquisition, reject non-directory app backups before mutation, require regular-file staged payloads, compensate failed publication by retaining the rejected node and restoring the verified prior file, and preserve pending recovery state. Bind the Windows helper to the exact prepared release-unit SHA-256, create a unique cache node without replacement, freeze it through process creation, preserve the original portable-alias shape, and use absolute macOS system command paths.
Verification:
- go test ./... -count=1
- go test -race ./internal/repair ./internal/fileutil ./cmd/reasonix-guard -count=1
- cd desktop && go test ./... -count=1
- cd desktop && go test -race ./... -count=1
- go vet ./... and cd desktop && go vet ./...
- golangci-lint 2.12.2 for root and desktop
- cache guard: 8/8 pass
- Windows amd64/arm64 repair, desktop, and update-helper test-binary compilation
- Linux amd64 repair test-binary compilation
Co-authored-by: SivanCola <[email protected]>
* Tidy desktop updater dependencies
Problem:
The desktop CI go-mod-tidy gate failed because the updater no longer imports github.com/minio/selfupdate, but the module files still retained that dependency and its stale transitive checksums.
Root cause:
The transactional updater implementation replaced the selfupdate package without running the desktop module tidy gate afterward.
Fix:
Remove the unused selfupdate requirement and the transitive checksum entries that are no longer reachable from the desktop module graph.
Verification:
- cd desktop && go mod tidy (idempotent)
- cd desktop && go vet ./...
- focused workspace-rebuild timing tests: 10/10 pass
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* Harden prepared repair journals and Windows release-unit signing
Problem:
- Repair mutations could leave ambiguous undo state after a crash between
rename and last-repair.json publication.
- Concurrent writers could replace a pending journal or cleanup node after
ownership checks, turning cleanup into deletion of unowned material.
- Windows portable/installer updates did not bind signed payload identity
through packaging, and Linux release-unit publication did not return
exact installed-state receipts.
Root cause:
Undo bookkeeping treated last-repair.json as the only durable intent, cleanup
used path-based remove after verification, and Windows packaging trusted
unsigned payload layout without a create-only signed manifest contract.
Fix:
- Introduce create-only pending-repair.json write-ahead journals, promote or
discard them under the repair lock, and fail closed on ambiguous recovery.
- Displace cleanup targets to unique siblings, re-verify identity, and never
unlink a pending journal that changed before cleanup.
- Bind Windows signed payload manifests through packaging/signing and require
exact installed-state receipts for Linux release-unit publication.
Verification:
- go test ./internal/repair -count=1 (pass)
- focused security coverage for prepared intent, pending cleanup ownership,
legacy healthy/rollback fail-closed, and macOS staging cleanup
Co-authored-by: SivanCola <[email protected]>
* fix(ci): use resolved version for payload manifest
Problem: The Windows payload manifest step referenced a nonexistent ver step output, so actionlint rejected the release workflow.
Root cause: The build job receives the release version from needs.resolve.outputs.version, but the step used steps.ver.outputs.version.
Fix: Bind the signed payload manifest to the resolved release version output.
Verification: actionlint v1.7.7 and scripts/release-workflows.test.sh pass.
* fix(repair): bind prepared create ownership to published Windows modes
Problem:
- Windows CI rejected undo after a crash following snapshot create: the
restored config file remained because CreatedStateID ownership failed.
- Desktop packaging contract tests still expected steps.ver.outputs.version
after the release workflow switched to needs.resolve.outputs.version.
Root cause:
- Prepared create intents predicted FileMode 0o600, but Windows Lstat reports
regular files as 0444/0666. Undo then treated the published file as an
unowned concurrent write and left it in place.
- The packaging test was not updated with the resolved-version workflow fix.
Fix:
- Predict create-state IDs with platform-published file modes and rebind the
pending journal to the exact on-disk node after AtomicCreateFile.
- Align the Windows signing packaging contract test with the resolve job
version output.
Verification:
- go test ./internal/repair -count=1
- go test ./internal/repair -run TestPreparedCreateStateIDMatchesAtomicCreate,TestApplyRepairPlanPersistsMissingSnapshotTargetBeforeCreate
- cd desktop && go test -run TestWindowsReleaseSignsPayloadBeforeRepackaging .
Co-authored-by: SivanCola <[email protected]>
* fix(repair): reject prepared create ownership drift
Problem: A file replaced between AtomicCreateFile and ownership rebinding could be claimed by the repair transaction and removed by undo.
Root cause: The pending journal was rewritten to bind whatever state happened to be present after publication instead of proving it matched the precomputed owned state.
Fix: Keep the write-ahead ownership binding immutable and fail closed when the published path differs; add a regression test that preserves a concurrent replacement through undo.
Verification: focused crash/create tests, go test ./internal/repair, repair/fileutil race tests, Guard tests, go vet, and Windows repair test cross-compile pass.
---------
Co-authored-by: SivanCola <[email protected]>
Co-authored-by: PaulDing98 <[email protected]>
* complete stable and preview channel loop
* fix(site): harden release-channel fallbacks and version scopes
Problem:
Website CLI Stable fallbacks pointed at GitHub releases/latest asset
URLs, incomplete GitHub releases were turned into 404 download links,
and Desktop/CLI Stable races overwrote every .rxv version chip.
Root cause:
CLI releases set make_latest:false so Latest belongs to Desktop Stable;
releaseAssetMap synthesized missing CLI assets; updateStableVersion was
shared across both product surfaces.
Fix:
- CLI fallback always uses the releases page, never /releases/latest.
- Require all 7 CLI assets before accepting a release (match Worker).
- Scope package-manager .rxv updates to CLI Stable only; desktop
version chips use data-release-version="desktop".
Verification:
node --test site/src/scripts/release-channels.test.mjs
site/src/scripts/download-link.test.mjs
* fix(desktop): unify update channel controls
Problem: The desktop settings page separated channel selection, update status, and install actions across detached rows.
Root cause: The channel picker and updater state machine were rendered as independent controls, so switching channels did not read as one actionable workflow.
Fix: Merge current version/status, Stable and Preview selection, manual refresh, auto-check guidance, and state-specific download/install actions into one compact settings flow. Add the localized channel auto-check hint for English, Simplified Chinese, and Traditional Chinese.
Verification: pnpm --dir desktop/frontend run typecheck; pnpm --dir desktop/frontend run check:css; pnpm --dir desktop/frontend run test:updater (18 passed); pnpm --dir desktop/frontend run build; git diff --cached --check.
* feat(cli): persist release channel selection
Problem:
CLI users had to repeat the verbose --channel flag, and a plain upgrade always returned to Stable instead of following the user's selected public channel.
Root cause:
The native updater had no user-global channel preference, while the website and docs exposed only one-off flag syntax.
Fix:
Add a user-global [cli].update_channel setting, support upgrade stable|preview as persistent switches, retain --channel as a one-off compatibility override, reject conflicting arguments, and synchronize English/Chinese docs plus website commands.
Verification:
- env -u DEEPSEEK_API_KEY GOCACHE=/private/tmp/reasonix-go-cache go test ./...
- env GOCACHE=/private/tmp/reasonix-go-cache go vet ./internal/cli ./internal/config
- npm test (site)
- npm run build (site)
- git diff --check
* fix(release): keep CLI channel pointers monotonic
Problem: Older Stable recovery runs and out-of-order Preview publishers could overwrite a newer public CLI channel pointer.
Root cause: The release workflow wrote cli/<channel>/latest.json unconditionally and had no channel-scoped serialization or strict version comparison.
Fix: Serialize CLI releases by channel, publish immutable per-tag metadata first, validate the current pointer, and move it only when a strict Stable or Preview version tuple is newer.
Verification: bash scripts/release-workflows.test.sh; bash -n scripts/compare-cli-release-tags.sh scripts/release-workflows.test.sh; git diff --check.
* fix(config): preserve symlinked config files
Problem: Persisting the selected CLI update channel could replace a symlinked user config with a regular file.
Root cause: AtomicWriteFile renamed a temporary file over the symlink path instead of the symlink's final target.
Fix: Resolve complete config symlink chains before atomic writes and route both incremental saves and Config.WriteFile through the shared helper.
Verification: go test ./internal/config; focused writable-target and multi-level symlink regression tests; go test ./...; go vet ./internal/cli ./internal/config.
* fix(release): harden stable and preview end-to-end
Problem:
The Stable/Preview loop still allowed stale or cross-channel release pointers, unsafe Desktop release origins and retries, updater events detached from their checked target, and concurrent config writers that could lose user changes.
Root cause:
Release publication lacked one immutable candidate and strict manifest/recovery contracts; Desktop operations were not bound to request, channel, and version; config locks followed logical paths and several writers loaded stale or malformed snapshots.
Fix:
- authorize Desktop candidates from protected main-v2, separate the current control plane from recovered source commits, validate immutable assets, and make GitHub/R2 publication monotonic and resumable
- bind Desktop checks, downloads, verification, and installs to one request/channel/version state machine with strict size and manifest validation
- validate CLI, Desktop, website, and Worker release data consistently across Stable and Preview
- canonicalize and pin config targets, serialize all affected writers, preserve safe symlinks, and fail closed on malformed configuration
Verification:
- go test -race ./...; go vet ./...; go build ./...
- cd desktop && go test -race ./... && go vet ./... && go build ./...
- frontend typecheck, updater tests (31), CSS checks, and production build
- site tests (44) and production build
- Worker typecheck and tests (74)
- release workflow contracts, actionlint, cache guard, shell syntax, diff check, and Linux/Windows cross-builds
Co-authored-by: ffff2004 <[email protected]>
* fix(config): remove obsolete edit wrappers
Problem:
The release-channel hardening left two private config edit wrappers unreachable, causing the pinned golangci-lint unused check to fail.
Root cause:
Callers now resolve and pin final config targets through the transactional edit path, but the superseded convenience wrappers remained in place.
Fix:
Remove only the two unreferenced wrappers while preserving the resolved-target and timeout helpers used by production code and concurrency tests.
Verification:
- go test ./internal/config
- go vet ./...
- golangci-lint v2.12.2 run --timeout=5m
- git diff --check
* fix(release): satisfy workflow shell lint
Problem:
The final CI lint job passed golangci-lint but failed actionlint because the CLI metadata publisher assigned expected_prerelease without reading it.
Root cause:
Prerelease validation moved into validate-cli-release-manifest.sh, leaving duplicate channel-specific shell assignments in the workflow.
Fix:
Remove the unused assignments and keep prerelease validation centralized in the manifest validator.
Verification:
- actionlint v1.7.7 on all release workflows
- bash scripts/release-workflows.test.sh
- bash -n on release helper scripts
- git diff --check
* fix(release): close desktop channel blockers
Problem:
Desktop Preview-to-Stable switches failed for older Debian Stable builds, updater Wails methods broke older one-argument callers, and config edit locks could split across TMPDIR or macOS case aliases.
Root cause:
The privileged helper rejected every downgrade, strict request binding reused legacy exported method names, and lock identity depended on process-local temp directories without Darwin path folding.
Fix:
Allow only signed public Preview-to-Stable Debian downgrades, preserve legacy updater methods beside strict request-bound APIs, and use a stable hardened per-user lock registry with Darwin alias normalization.
Verification:
Root and Desktop tests, Desktop race, Go vet/build, updater 31/31, site 44/44, Worker 74/74, release workflow contracts, cache guard, actionlint, golangci-lint, frontend typecheck/CSS/build, and focused cross-process/security regressions.
---------
Co-authored-by: ffff2004 <[email protected]>
Problem: TestLoadNormalizesQuotedNodeEvalHooksPerProject flaked in CI
(test (windows-latest)) — the node -e hook child was killed at the
15.03s timeout on a loaded Windows runner, failing the job.
Root cause: hook_test.go's realSpawnTimeout (15s) feeds the per-hook
Timeout for tests that spawn real child processes. The constant's own
comment says these tests assert behavior, not latency, and node cold
starts can stall for seconds on a loaded machine — 15s just was not
generous enough for a saturated Windows GitHub runner.
Fix: raise realSpawnTimeout to 60s. The ceiling only fires on a genuine
hang, so a larger budget costs nothing when children exit normally.
Tests asserting the timeout path keep their own tight budgets (50-100ms)
and are untouched, so the suite stays fast. All real-spawn tests share
this constant (execution_contract_test.go, windows_batch_test.go,
hook_test.go), so one bump deflakes every call site.
Verification:
- go test ./internal/hook -count=1 passes locally (1.5s, node v22).
- CI on this PR exercises the windows smoke suite, which includes
internal/hook.
Problem: gating the test matrix at job level means a docs-only pull
request produces no test (ubuntu/macos/windows-latest) check runs at all
— a skipped matrix job reports only the parent job check, never the
per-leg checks the Protect main-v2 ruleset requires. Docs-only PRs then
sit at mergeStateStatus=BLOCKED with the required legs stuck in
"Expected". Confirmed live on a docs-only verification PR.
Fix: the test job now always runs (if: always()), and every step carries
the gate via a job-level RUN_STEPS env. When the changes detector
reports an unrelated diff, all steps skip and each leg reports success
in seconds, so the required per-OS checks exist and pass. When the
changes job itself fails, the legs still start and the empty output
fails open (!= 'false') into the full suite. Required check names are
unchanged; non-required jobs keep job-level skipping.
Verification:
- actionlint v1.7.7 passes; YAML parses cleanly.
- On this PR the full matrix runs (workflow files are not in any skip
list). The docs-only verification PR will be re-run after merge to
confirm legs report success and the PR turns mergeable.
* Gate CI jobs on changed paths to cut Actions waste
Problem: every pull request runs the full ci.yml matrix (11 jobs across
3 OSes) and the cache-impact guard fires on every PR event, even for
docs-only or otherwise unrelated changes. That is roughly 33k wall-minutes
per month on GitHub-hosted runners — free while the repo is public, but
pure queue time now and a real bill if the repo ever goes private.
Change:
- ci.yml: add a cheap `changes` detection job and gate the heavy jobs on
it. PRs confined to docs/, site/, release-notes/, workers/ or top-level
Markdown skip the Go test/race/lint/govulncheck/coverage jobs; PRs that
cannot affect the desktop module (which imports the root kernel via
`replace reasonix => ../`) skip the desktop/desktop-macos/desktop-windows
jobs; the site job only runs for site/ changes. Job-level `if` reports
skipped, which still satisfies the required status checks (lint, race,
test) — a workflow-level paths-ignore would leave them pending and block
merges. Detection fails open, and pushes to main-v2 always run the full
matrix.
- cache-impact.yml: paths-ignore for clearly unrelated paths so the guard
stops running on every PR event (~3k runs/month) when the PR can never
trip it.
- release-desktop.yml: retention-days: 7 on the dist-* and preview-dist
artifacts (same-run handoffs to the publish/mirror steps); they
previously kept the 90-day default, accumulating ~17 GB of dead storage.
Verification:
- actionlint v1.7.7 passes on all three modified workflows (same version
and -ignore flags as the lint job).
- Both modified YAML files parse cleanly.
- Simulated the path filter against 10 representative change sets
(docs-only, root markdown, site-only, desktop-only, internal, npm-only,
workers-only, workflow file, nested skill markdown, mixed); every output
matched the intended gating.
* Speed up pull request CI: focused race, Windows smoke, push-only installer
Problem: after the required checks, merging waits on the slowest ones —
`race` averages ~12 min and `test (windows-latest)` ~6.3 min, while the
(non-required) desktop-windows job runs ~14.5 min, most of it building the
NSIS installer. Every PR pays that cost even though main-v2 re-verifies
everything after merge.
Change:
- race: pull requests run `go test -race` only on the concurrency-heavy
packages (agent, plugin, jobs, proc, sandbox, filelock, eventwire,
remote — the same areas the job comment calls out). Pushes to main-v2
keep the full ./... sweep as the safety net. Check name is unchanged,
so the required-checks ruleset is unaffected.
- test (windows-latest): pull requests run a smoke suite of the packages
that actually carry *_windows.go code, plus cmd/. Full ./... sweep stays
on pushes. The Unix legs are untouched.
- desktop-windows: Install NSIS + installer/portable build steps now run
only on pushes; PRs stop after go test. Installer packaging is
packaging validation, and the release pipeline rebuilds it regardless.
- codeql.yml: paths-ignore docs/**, release-notes/**, root *.md on push
and pull_request (CodeQL checks are not required; the weekly cron keeps
the full scan cadence).
Expected: required-check wait drops from ~12 min to ~5 min per PR, and
the all-green wait from ~15 min to ~6 min, with no loss of post-merge
coverage.
Verification:
- actionlint v1.7.7 passes on both workflows; YAML parses cleanly.
- `go list` resolves all packages in the race subset (24 pkgs) and the
Windows smoke suite (37 pkgs).
- This PR's own checks exercise the new PR paths live: the race and
windows-test jobs run their focused variants because the branch only
touches workflow files.
Problem: every pull request runs the full ci.yml matrix (11 jobs across
3 OSes) and the cache-impact guard fires on every PR event, even for
docs-only or otherwise unrelated changes. That is roughly 33k wall-minutes
per month on GitHub-hosted runners — free while the repo is public, but
pure queue time now and a real bill if the repo ever goes private.
Change:
- ci.yml: add a cheap `changes` detection job and gate the heavy jobs on
it. PRs confined to docs/, site/, release-notes/, workers/ or top-level
Markdown skip the Go test/race/lint/govulncheck/coverage jobs; PRs that
cannot affect the desktop module (which imports the root kernel via
`replace reasonix => ../`) skip the desktop/desktop-macos/desktop-windows
jobs; the site job only runs for site/ changes. Job-level `if` reports
skipped, which still satisfies the required status checks (lint, race,
test) — a workflow-level paths-ignore would leave them pending and block
merges. Detection fails open, and pushes to main-v2 always run the full
matrix.
- cache-impact.yml: paths-ignore for clearly unrelated paths so the guard
stops running on every PR event (~3k runs/month) when the PR can never
trip it.
- release-desktop.yml: retention-days: 7 on the dist-* and preview-dist
artifacts (same-run handoffs to the publish/mirror steps); they
previously kept the 90-day default, accumulating ~17 GB of dead storage.
Verification:
- actionlint v1.7.7 passes on all three modified workflows (same version
and -ignore flags as the lint job).
- Both modified YAML files parse cleanly.
- Simulated the path filter against 10 representative change sets
(docs-only, root markdown, site-only, desktop-only, internal, npm-only,
workers-only, workflow file, nested skill markdown, mixed); every output
matched the intended gating.
* fix(desktop): allow skills in initial goal turns
Problem: Goal mode rejected or swallowed Skill invocations in the first user turn.
Root cause: The Composer blocked structured invocations before a Goal existed, while App wrapped the remaining first turn as /goal prose.
Fix: Require task text but preserve selected and pasted Skill invocations as structured submissions after activating the Goal. Extract the activation/submission order into a tested contract and add coverage for inline and subagent-compatible invocation paths.
Verification: pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx; pnpm exec tsx src/__tests__/send-failed.test.ts; pnpm typecheck; pnpm exec tsc --noEmit -p tsconfig.test.json; ./scripts/cache-guard.sh
* fix(desktop): scope initial Goal activation to source tab
Problem: Switching tabs while the first Goal was activating could store the Goal on the newly active tab while submitting the structured Skill to the original tab. Initial Goal setup also rejected attachment- or workspace-reference-only prompts after the Skill guard changed.
Root cause: The activation callback closed over activeTabId, and the Composer guard treated every empty-text first Goal as invalid even when non-text context was present.
Fix: Add explicit tab-scoped Goal/profile activation, route the initial structured submission through the source tab, preserve attachment and workspace-reference-only submissions, and cover the active-Goal Subagent path plus source-tab wiring.
Verification: go test ./internal/control -count=1; go test -race ./internal/control -count=1; go test -race ./internal/jobs -run TestStartForSession_AcceptsValidInput -count=1; pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx; pnpm exec tsx src/__tests__/send-failed.test.ts; pnpm typecheck; ./scripts/cache-guard.sh; git diff --check.
Co-authored-by: SivanCola <[email protected]>
* fix(desktop): fail closed when initial Goal activation fails
Problem: Structured Skill submission on the first Goal turn could run even when SetGoalForTab failed or no-oped, because activation errors were swallowed and the structured path has no /goal prose fallback.
Root cause: setGoalForTab used .catch(() => {}), applyGoalForTab patched the local Goal profile before backend success, and regression coverage only asserted source wiring instead of deferred/bridge-failure behavior.
Fix: Propagate SetGoalForTab failures (still refresh meta in finally), patch the composer Goal profile only after backend activation succeeds, and document that activateGoalAndSubmit must abort send when activation fails. Add behavioral tests for bridge failure, deferred source-tab ordering, and attachment-/workspace-ref-only initial Goals.
Verification: pnpm exec tsx src/__tests__/send-failed.test.ts; pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx; pnpm typecheck.
* fix(desktop): return SetGoalForTab errors across the Wails boundary
Problem: Frontend fail-closed Goal activation still continued structured Skill submits when the real bridge "succeeded", because Go SetGoalForTab returned void and remote/invalid-tab failures only warned.
Root cause: The Wails method swallowed remote RPC errors and missing/replaced tabs with a normal return, so the Promise always resolved. Cross-tab coverage also only exercised helper callback order, not SetGoalForTab/SubmitInvocationsToTab tab ids through the controller bridge.
Fix: Make SetGoal/SetGoalForTab/ClearGoal/ClearGoalForTab return error (remote RPC, missing tab, replaced tab). Route the first Goal turn through activateGoalAndSubmitOnTab so source tab ids are captured once. Add desktop Go error tests and a controller bridge harness that hangs SetGoalForTab(A), switches to B, and asserts Goal plus SubmitInvocationsToTab stay on A.
Verification: cd desktop && go test -count=1 -run 'TestSetGoalForTab|TestClearGoalForTab|TestMetaReportsGoalStatus|TestCollaborationModesPreserveToolApprovalMode|TestToolApprovalModesPreserveCollaborationMode' .; pnpm --dir desktop/frontend exec tsx src/__tests__/goal-activation-tab-routing.test.tsx; pnpm --dir desktop/frontend exec tsx src/__tests__/send-failed.test.ts; pnpm --dir desktop/frontend typecheck.
* fix(desktop): handle Goal bridge rejections
Problem: Interactive and background Goal calls discarded rejected Wails promises, allowing unhandledrejection to reach the global crash overlay.
Root cause: Composer callbacks, mode shortcuts, model commands, and controller resync paths invoked asynchronous Goal actions without a shared error boundary.
Fix: Route non-submit Goal actions through a toast-aware rejection handler, await model commands, and defer local Goal state updates until bridge success while preserving fail-closed structured submissions.
Verification: pnpm frontend test/typechecks, focused Goal routing and rejection tests, git diff --check, and cache-guard.sh.
* fix(desktop): fence initial Goal submission to its target
Problem:
An initial Goal could activate on Remote tab A, then submit its structured Skill to Local tab A after the user switched projections. The routing regression also was not part of the standard frontend test suite.
Root cause:
Goal activation and the first turn crossed Wails as separate calls identified only by tab ID. Wails schedules calls independently, while Local and Remote projections can reuse the same tab ID.
Fix:
Capture the source tab and workbench target token in React, validate both in one backend operation, and hold the workbench transition lock across Goal activation and first-turn submission. Fail closed on stale targets, keep zero-valued request sequences in target events, and cover frontend bridge plus Go backend behavior.
Verification:
- pnpm --dir desktop/frontend test
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend test:typecheck
- cd desktop && go test ./...
- cd desktop && go test -race -count=1 -run 'TestSubmitInitialGoal|TestWorkbenchTargetEventKeepsZeroRequestSequence|TestSetGoalForTab|TestClearGoalForTab|TestSetActiveTabSwitchesRemoteProjectionOnlyForDifferentTab' .
- cd desktop && go vet ./...
- ./scripts/cache-guard.sh
* feat(desktop): add integrated terminal sessions
Add a right-dock xterm terminal with multi-session controls, workspace-relative start directories, and file-tree entry points. Use Unix PTY and Windows ConPTY backends with bounded session counts, output buffering, stale-tab checks, read-only and remote guards, and application shutdown cleanup. Keep shell resolution renderer-safe by accepting only backend-approved shell IDs and reading custom shell paths from user-global config.
Refs #6513
Refs #6891
Verification: desktop Go race tests, Windows amd64 cross-compile, frontend terminal tests, full frontend test suite, typecheck, CSS checks, and production build.
Co-authored-by: ipanta <[email protected]>
* feat(desktop): scope terminal to each chat tab
Align the integrated terminal with the chat-scoped Codex workflow. Bind sessions to the active chat tab and workspace, close only that tab's PTYs on tab close, and render the terminal as a responsive bottom drawer.
Expose a bounded terminal output snapshot through an explicit user action that adds it to the composer. Do not inject terminal output into provider prompts or bypass the existing agent shell approval path.
Verification: desktop terminal race tests, Windows amd64 cross-compile, frontend typecheck, terminal and workspace-layout tests, CSS checks, and production build.
Co-authored-by: ipanta <[email protected]>
* fix(desktop): harden integrated terminal lifecycle
Problem:
- Terminal output was lost after switching sessions or reopening the drawer, and concurrent session creation could overwrite renderer state.
- Closing a chat tab could race an in-flight terminal start, while Windows coverage did not exercise the real ConPTY path.
- The terminal drawer duplicated controls and loaded xterm into the initial application bundle.
Root cause:
- Output was buffered only while no sink was attached, state updates reused stale workspace snapshots, and the backend did not remember closed tab IDs.
- Terminal-specific frontend tests were outside the standard suite, and the Windows executable fixture lacked an .exe suffix.
Fix:
- Keep bounded per-session output history, clear it only when a session is removed, and merge concurrent session updates atomically.
- Tombstone closed tabs before process cleanup, reject late starts, suppress stale output, and distinguish natural exits from removals.
- Add backend-approved shell selection, compact horizontal session tabs, lazy terminal loading, Windows ConPTY coverage, and CI wiring.
Verification:
- Desktop terminal race tests, full desktop tests, and go vet pass.
- Windows amd64 test-binary cross-compilation passes.
- Full frontend tests, TypeScript type checking, and production bundle checks pass.
* fix(desktop): close integrated terminal review gaps
Problem:
- Read-only transitions and workspace rebinding could leave existing shells alive or allow a slow shell start to register against stale tab state.
- First-open, retry, concurrent create, and single-session UI states could lose errors, duplicate requests, or hide the only close control.
- Terminal output added to chat could retain terminal control payloads or break its Markdown fence.
Root cause:
- Terminal lifecycle invalidation was not serialized with tab capability changes, and process exit did not wait for the output reader to drain.
- Renderer readiness used replace-style state updates without a retryable error model.
- Composer insertion treated raw terminal output as ordinary text.
Fix:
- Add per-tab generations, capability gates, process-output draining, process-group cleanup coverage, and macOS PTY CI.
- Make terminal readiness deduplicated and retryable, preserve concurrent sessions, expose operation errors, and keep bottom-drawer controls usable in all layouts.
- Strip ANSI, OSC, C1, and unsafe control sequences, bound inserted output, and choose a safe Markdown fence.
Verification:
- go test ./... and go vet ./... in desktop/
- go test -race -run 'Test(ResolveTerminal|Terminal|EmptyTerminal|UnixTerminalProcess)' . in desktop/
- Windows amd64 and arm64 cross-builds for desktop/
- pnpm test, test:typecheck, test:remote, test:updater, test:performance, typecheck, test:terminal, and build
- go vet ./..., gofmt, YAML parse, and git diff --check
---------
Co-authored-by: ipanta <[email protected]>
* feat(memory): separate standing instructions from scoped facts
* fix(memory): preserve scoped memory compatibility
Problem:
Omitted-scope updates could move legacy global memories into the current project, and global preferences or feedback were unavailable on the first turn. Mixed-version clients could also route independently scoped facts through the wrong directory.
Root cause:
Save normalized an empty scope before consulting the active file, the stable prompt only contained the memory index, and previous releases treated metadata.type as the routing signal.
Fix:
Preserve the effective scope of same-name updates, snapshot global user and feedback bodies into deterministic lower-priority guidance, retain a previous-release routing type alongside authoritative fact_type metadata, and queue complete save/revocation notes for the current session.
Verification:
./scripts/cache-guard.sh
go vet ./...
env -u DEEPSEEK_API_KEY go test ./...
(cd desktop) go test ./...
(cd desktop/frontend) pnpm test
(cd desktop/frontend) pnpm build
gofmt -l .
git diff --check
* refactor(context): separate raw and provider user input
* refactor(instructions): resolve scoped context with provenance
* refactor(memory): add versioned fact storage
Context: Memory facts were addressed by mutable slugs, trusted a separately maintained index, and had no durable update history or conflict protection.
Change: Add stable IDs, monotonic revisions, atomic writes, derived indexes, deterministic legacy migration, scoped identity handling, revision restore, and optimistic update checks. Surface the metadata through tools and Desktop while preserving legacy frontmatter routing and Safe Mode behavior.
Compatibility: Existing files remain readable, gain deterministic runtime metadata before migration, and keep metadata.type/fact_type/scope values that previous releases understand. Added Desktop JSON fields are optional.
Verification: go test ./internal/memory; go test -race ./internal/memory; go test ./internal/boot -run Test.*Memory; go test ./... with provider credentials isolated; desktop go test ./...; frontend pnpm test and typecheck; go vet ./...; ./scripts/cache-guard.sh.
* feat(memory): recall relevant facts per user turn
Problem:
Saved facts were only visible through a stable index and required the model or user to invoke memory search manually.
Design:
Add conservative host-side BM25 recall for real user turns with project-over-global deduplication, freshness labels, explainable scoring, strict character limits, and local-home redaction. Keep the system prompt and tool schemas stable by appending recall only to the provider user-turn tail, and exclude synthetic turns and already-loaded global guidance.
Compatibility:
Preserve Store.List behavior and add ListAll for scope-aware consumers. No persisted memory or session fields change; display fallbacks strip the new transient suffix.
Verification:
go test -count=1 ./internal/memory ./internal/agent ./internal/control
go test -race -count=1 ./internal/memory
go test -race -count=1 ./internal/control with focused recall tests
go test -count=1 ./internal/tool/builtin/ ./internal/boot/
go vet ./...
./scripts/cache-guard.sh
* feat(memory): auto-save low-risk project facts
Problem:
Every remember call required a fresh approval, including bounded project facts that were new, non-sensitive, and low impact. This made durable context expensive enough that agents and users often skipped it.
Design:
Classify interactive remember requests against the scoped store. Automatically allow only explicit project/reference creates that are bounded, non-sensitive, and not semantic duplicates. Keep global, user/feedback, sensitive, and overwrite requests on the existing fresh-human path; keep headless and sub-agent mutation fail closed.
Safety:
Issue one-shot hashed grants and enforce create-only again inside the serialized Store write, preventing an approval-to-write race from becoming an overwrite. Clear abandoned grants at turn completion. Expose revision history and audited restore through the controller.
Compatibility:
The remember description and schema are unchanged. RequireCreate is a runtime-only SaveOptions field; persisted memory and session formats are unchanged.
Verification:
DEEPSEEK_API_KEY= go test -count=1 ./...
go test -count=1 ./... in desktop
go test -race -count=1 ./internal/memory ./internal/control with memory/approval tests
go vet ./...
./scripts/cache-guard.sh
* feat(memory): recover archived facts safely
Problem: Archived facts remained inspectable on disk but users could not recover them through Reasonix, and ad hoc restoration could overwrite active memory or lose revision provenance.
Fix: Add store-validated archive recovery with collision checks, exclusive active-file creation, monotonic revisions, revision snapshots, controller turn-tail refresh, and a Desktop restore action.
Compatibility: Existing active and archived Markdown files remain readable; recovery writes the established v2 frontmatter and hidden revision layout, while older binaries continue to read the restored active fact.
Verification: go test ./internal/memory ./internal/control; go test ./... in desktop; pnpm test; pnpm exec tsc --noEmit; ./scripts/cache-guard.sh.
* feat(context): expose memory recall diagnostics
Expose scoped fact freshness, project-over-global conflict decisions, and the last automatic recall trace through the desktop contract. Add revision history and restore APIs while preserving non-null collection responses and remote-workspace isolation.
This gives Context Center enough provenance to explain what was available, selected, suppressed, or restored without changing the stable prompt prefix.
* feat(context): add provenance center to desktop
Make scoped memories explainable in the desktop settings surface. Show stable identity, freshness, revision history, project-over-global resolution, automatic recall traces, instruction precedence, imports, and diagnostics.
The suggestions workflow remains compatible while the Context Center gains zero-impact read paths and explicit restore actions for user-controlled recovery.
* feat(memory): make suggestions zero-config
Open the suggestions surface with automatic local-history scanning by default, removing the hidden localStorage setup switch. Keep every candidate behind explicit confirmation.
Deduplicate against all scoped active facts and resolved instruction bodies so project and global memories cannot produce repeated guidance.
* feat(memory): add CLI diagnostics and recovery
Context:
Context Engine v2 exposed revision, recall, and instruction provenance in the backend and desktop, but the CLI only listed legacy merged facts and archives.
Changes:
- add shared /memory diagnostics for scoped facts, recall traces, instruction precedence, revisions, and archives
- add explicit revision restore and archive recovery commands with structured completion across CLI, desktop, and remote workspaces
- preserve same-name facts across scopes and keep narrow TUI output bounded
Verification:
- go test ./internal/control ./internal/cli ./internal/remote/workbench/runtime ./internal/memory -count=1
- go test -race ./internal/control ./internal/memory
- cd desktop && go test . -count=1
- ./scripts/cache-guard.sh
Cache-impact: management diagnostics only; provider-visible prompt and tool schemas are unchanged.
* docs(context): document instructions and memory v2
Context:
Context Engine v2 changed instruction precedence, automatic recall, safe memory writes, revision recovery, and desktop suggestions, while the user docs still described the legacy approval-only retrieval model.
Changes:
- rewrite the context and memory contract around standing instructions versus background facts
- document recall freshness, project-over-global resolution, zero-configuration writes and suggestions, revision/archive recovery, cache stability, privacy, and remote fail-closed behavior
- add a complete Chinese companion and update Guide, CLI, migration, spec, approval, and README navigation in both languages
Verification:
- validated relative Markdown links, code fences, and final newlines across 14 changed files
- scanned for stale approval/suggestion claims
- git diff --cached --check
- scanned the staged diff for secrets and machine-local paths
* fix(context): preserve safe headless memory writes
Allow controller-scoped low-risk project/reference creates to pass the non-interactive approval gate while keeping deny rules and all higher-risk memory operations fail-closed. Reject archive recovery through symlinked files or archive directories, with end-to-end regressions for both boundaries.
* fix(memory): reject non-directory migration roots
* fix(context): make instruction and memory references safe
Problem:
Provider-visible instruction paths exposed machine-local roots, memory results exposed store paths, and listed scoped references could not be used directly for read, update, revision, or archive operations.
Root cause:
Instruction rendering used absolute source paths and memory lookup treated display names as unqualified slugs. Symlink reads also crossed boundaries through a path-resolution gap, while scope-aware listing deduplicated same-name facts.
Fix:
Render stable workspace/user provenance, confine instruction and import reads to their owning roots, reject external symlinks, and support project/<name>.md and global/<name>.md references end to end. Preserve unqualified compatibility, stable-ID renames, revision snapshots, exact-scope archive behavior, low-risk create-only approval boundaries, and bilingual documentation.
Verification:
Focused instruction/memory tests and race tests; go test -count=1 ./...; go test -race ./...; go vet ./...; desktop go test/race/vet; pnpm test:all; pnpm build; go run ./cmd/remote-protocol-gen --check; ./scripts/cache-guard.sh (8/8 pass, long tool loop tail average 95%); staged privacy scan clean.
* docs(contributors): attribute adopted context work
Attribution:
Record the authors whose prior pull request mechanisms were adapted by the Context Engine v2 integration.
- cyq1017: feedback injection from #5710 and Markdown input normalization from #5603
- salarkhannn: Markdown input normalization from #5604
Scope:
This metadata-only commit adds repository-level co-author attribution without changing runtime behavior.
Verification:
Confirmed source PR ownership and both public GitHub noreply identities.
Co-authored-by: cyq <[email protected]>
Co-authored-by: Salar <[email protected]>
* test(desktop): allow slow Windows git metadata probe
Problem:
The Windows Desktop CI job failed when MetaForTab's asynchronous first git probe did not finish within the test's one-second deadline.
Root cause:
The production path intentionally launches the cold git branch lookup in the background so tab metadata never blocks. Windows runner process startup can exceed one second under normal CI load, making the test timing assumption platform-fragile.
Fix:
Keep the non-blocking metadata implementation unchanged and extend the cross-platform test observation window to five seconds, with a comment documenting the Windows process-startup variance.
Verification:
- GOCACHE=/tmp/reasonix-desktop-go-cache go test -count=1 -run 'TestMetaForTabIncludesWorkspaceContext|TestWorkspaceGitBranch' ./desktop
- git diff --check
---------
Co-authored-by: cyq <[email protected]>
Co-authored-by: Salar <[email protected]>
* fix(cli): fall through keyless default_model to next configured provider
The CLI built sessions on whatever cfg.DefaultModel said, even when that
provider had no API key in the current environment. Every command
(
easonix -p,
easonix run,
easonix review,
easonix acp,
subagent runs) then hard-failed with 'missing env X_API_KEY' until the
user manually picked a different model in the bottom switcher, even
when another configured provider was sitting right there (#6996).
A companion fix to desktopNewSessionModel in #6999, which only covered
the desktop NewSession path. This change covers the shared boot.Build
entry point used by every CLI subcommand plus the six pre-Build call
sites that resolve the bootstrap / banner / effort / ACP model ref.
Semantics:
- No explicit --model: use cfg.DefaultModel when configured, else
fall through to the first provider that has a configured key. When
nothing is configured the raw default is preserved so the existing
'unknown model' / 'missing env' messages still surface.
- Explicit --model (or session-level p.Model / f.model): strict
resolve, fail loud on keyless or unknown. We must never silently
reroute a model the user asked for by name.
Verification:
- go test -count=1 ./internal/cli/... ./internal/boot/... ./internal/config/...
- go vet ./...
- gofmt -l internal/cli/ internal/boot/
- manual: a config with keyless default_model + a configured second
provider now boots onto the second provider (the prior build
hard-failed on the keyless one).
- manual: --model <keyless_ref> still errors; --model <configured_ref>
still works.
Cache-impact: none - CLI/boot model selection only, no provider-visible
prompt, tool schema, memory prefix, or cache key touched.
Cache-guard: TestBuildKeylessDefaultFallsBackToConfiguredProvider +
TestBuildExplicitKeylessModelStillFails in internal/boot, plus the
8-case TestResolveModelForCLI table in internal/cli/default_model_test.
System-prompt-review: N/A.
* fix(cli): preserve explicit model in TUI state
Keep the controller's resolved model reference as the TUI source of truth so interactive keyless recovery and later controller rebuilds never reinterpret an explicit model as an implicit default. Add a regression covering an explicit keyless model with the interactive RequireKey=false path.\n\nCo-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* fix(agent): stop stale edit retry loops
Problem:
Goal mode could alternate successful file reads with repeated stale edit_file or multi_edit calls, bypassing the existing consecutive-failure guard and wasting automatic continuation turns.
Root cause:
The host treated any successful read as progress and had no goal-scoped, target-aware memory of failed write intents.
Fix:
Track semantic failed-write signatures across one user turn or stable Goal scope, invalidate them only for overlapping mutations, and re-check stale-anchor applicability through the existing side-effect-free preview before blocking a retry.
Verification:
- go test ./internal/agent -run 'TestRepeatGuard' -count=1
- go test -race ./internal/agent -run 'TestRepeatGuard' -count=1
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- ./scripts/cache-guard.sh
* fix(agent): retain stale edit failures across file changes
Problem:
Repeated stale anchor failures could regain retry budget after another successful edit to the same file or after changing the target path spelling.
Root cause:
The Goal-scoped guard cleared every overlapping failure record, keyed signatures with raw paths, and treated dynamic nearest-line diagnostics as the error class.
Fix:
Preserve Preview-recheckable anchor failures across mutations, normalize anchor error classes and target paths, and cover real edit_file state changes plus path aliases.
Verification:
- go test ./internal/agent -run 'TestRepeatGuard' -count=1
- go test -race ./internal/agent -run 'TestRepeatGuard' -count=1
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- go vet ./...
- ./scripts/cache-guard.sh
* fix(agent): reset recoverable failures between goal runs
Problem:
Ordinary write failures were retained across Run calls in the same Goal scope, so a recovered permission or external-state issue could be blocked before retry execution.
Root cause:
The Goal-scoped repeat-failure map preserved every write-like failure even though only stale-anchor failures have a side-effect-free state recheck.
Fix:
Keep stale-anchor failures across Runs and discard ordinary write failures at the next Run boundary. Add a regression test proving the write executes again in each Run.
Verification:
go test ./internal/agent -run 'TestRepeatGuard' -count=1
go test -race ./internal/agent -run 'TestRepeatGuard' -count=1
go test ./internal/control -run 'TestRecoveryPauseKeepsGoalRunningAndDeliveryScope|TestGoalReadinessFailureBlocksAndKeepsDeliveryScope' -count=1
go vet ./...
env -u DEEPSEEK_API_KEY go test ./... -count=1
./scripts/cache-guard.sh
* fix(agent): tailor repeated write failure guidance
Problem:
The repeat-failure guard used stale-anchor recovery instructions for every repeated write failure, including permission and filesystem errors.
Root cause:
The block message did not distinguish Preview-recheckable anchor failures from ordinary write failures even though the failure record already carried that classification.
Fix:
Keep anchor-specific guidance for stale edit failures and return generic write-condition guidance for ordinary failures. Extend the permission-denied regression test to reject stale-anchor wording.
Verification:
go test ./internal/agent -run 'TestRepeatGuard' -count=20
go test -race ./internal/agent -run 'TestRepeatGuard' -count=1
go test ./internal/control -run 'TestRecoveryPauseKeepsGoalRunningAndDeliveryScope|TestGoalReadinessFailureBlocksAndKeepsDeliveryScope' -count=10
go test ./internal/agent -count=1
GOCACHE=/private/tmp/reasonix-go-cache-vet2 go vet ./internal/agent
./scripts/cache-guard.sh
* refactor(agent): add TaskTool Options constructor / 增加 TaskTool Options 构造入口
Problem:
NewTaskTool takes 18 positional parameters, which is hard to extend and
easy to mis-order at call sites.
Root cause:
Construction was only exposed as a long positional helper with no
structured options type.
Fix:
- Add TaskToolOptions and NewTaskToolWithOptions as the internal standard
entry point (empty SysPrompt still maps to DefaultTaskSystemPrompt).
- Keep the 18-arg NewTaskTool as a compatibility wrapper for one full
iteration cycle.
- Migrate boot wiring and the shared test helper to Options.
- Add equivalence tests for config, schema, empty prompt, resolver, and
execute behavior.
Verification:
go test ./internal/agent -run 'TestNewTaskToolWithOptions|TestTaskTool' -count=1
go test ./internal/boot -count=1
* refactor(agent): split Run and executeOne state machines / 拆分 Run 与 executeOne 状态机
Problem:
Agent.Run and executeOne mixed lifecycle, turn policy, and tool policy in
two very large methods, raising cognitive load without changing behavior.
Root cause:
Turn-loop counters and tool-call gating lived inline instead of package-
private staged helpers with a single ownership model for locks/defers.
Fix:
- Keep Run as the lifecycle owner of workspace lease, evidence commit,
delivery checkpoint, steer queue, and active-turn cleanup.
- Extract runLoopState plus beginRunTurn, runToolLoop, handleFinalResponse,
and handleToolRound without changing stream/persist/batch/compact order.
- Split executeOne into parseToolCall, resolveToolPolicy (plan/proxy,
delivery, recovery/permission), prepareToolExecution, and
finishToolExecution behind toolCallPlan, preserving permission-before-
lease and PreToolUse-before-execute semantics.
Verification:
go test ./internal/agent/... -count=1
go test -race ./internal/agent/... -count=1
REASONIX_RELEASE_CACHE_GUARD=1 go test ./internal/agent -run '^TestReleaseCacheHitGuard$' -count=1
* ci: report-only agent complexity monitor / 增加 agent 复杂度报告模式监控
Problem:
The repo already runs gofmt, vet, build, full tests, race, and
golangci-lint, but had no trend signal for remaining internal/agent
hotspots after structured extraction.
Root cause:
Main lint intentionally avoids funlen/cyclop hard fails that would
generate a flood of pre-existing noise.
Fix:
- Add a report-only funlen+cyclop config scoped to internal/agent
production code with generous thresholds (not a hard 50-line rule).
- Run it beside the existing lint job with issues-exit-code=0, write a
CI summary, and upload the report as an artifact.
- Ignore the generated local report file.
Verification:
bash scripts/agent-complexity-report.sh
* test(provider): isolate live DeepSeek cache probe / 隔离 DeepSeek 真实缓存探针
Problem:
The environment-gated DeepSeek cache probe ran during ordinary test suites whenever a stale API key was present, and its diagnostics still claimed tool-call reasoning replay should have zero prompt-token cost.
Root cause:
The probe had no explicit live build tag and retained commentary from the obsolete policy that stripped reasoning_content from DeepSeek tool-call turns.
Fix:
Gate the probe behind the live build tag, document the tagged invocation, describe required replay versus the empty-key recovery fallback, and fail when the required replay request is rejected.
Verification:
- go test ./...
- go test -race ./internal/provider/openai -count=1
- env -u DEEPSEEK_API_KEY go test -tags live ./internal/provider/openai -run TestRealDeepSeekCacheProbe -count=1 -v
- go vet ./internal/provider/openai
- ./scripts/cache-guard.sh
* fix(desktop): prevent freezes in large sessions
Problem:
Large session collections, rapid profile/model changes, and long streaming responses could trigger repeated filesystem scans, redundant controller rebuilds, and expensive transcript-wide rerenders. Remote Profile and Goal persistence could also diverge if the process stopped between durable writes.
Root cause:
Project-tree refreshes invalidated a global cache, profile axes crossed independent asynchronous calls, model changes queued every intermediate selection, and streaming Markdown reparsed the growing document on every delta. Remote session registry and Goal sidecar updates had no recovery journal.
Fix:
Add directory-scoped cache generations with TTL reconciliation, serialized atomic composer profiles, coalesced model switching, event-driven tab metadata refresh, an external live-stream store, batched stable Markdown rendering, lazy frontend chunks, and bundle/performance budgets. Preserve empty-history Goal state and make remote Profile plus Goal updates crash-recoverable with a durable transaction journal.
Verification:
- go test -race ./... in root and desktop modules
- focused cache, Profile, Goal, and model lifecycle race tests repeated 10 times
- pnpm test:all and production frontend build
- go vet ./... in root and desktop modules
- protocol generation drift and cache guard checks
- Windows amd64 and Linux amd64 cross-compilation checks
* fix(desktop): eliminate metadata and task rollback races
Problem:
Concurrent AutoResearch task creation could select the same directory ID across Store instances, and a failed durable Goal rollback could remove another transaction's task. Tab metadata refreshes could also apply a snapshot begun before a tab mutation.
Root cause:
Task IDs were chosen by a check-then-create sequence and deletion had no ownership proof. The bounded tab refresh coordinator treated a saturated in-flight request as current even after a backend mutation.
Fix:
Reserve task directories with atomic Mkdir and attach an opaque creation token required for rollback deletion. Add mutation generations and a latest-only trailing refresh to the tab metadata coordinator, then use it for lifecycle and explicit tab mutations.
Verification:
- go test ./internal/autoresearch
- go test -race ./internal/autoresearch
- go test -race ./internal/control ./internal/remote/workbench/runtime
- go test ./... (desktop module)
- pnpm --dir desktop/frontend test:all
- go vet ./internal/autoresearch ./internal/control ./internal/remote/workbench/runtime
* fix(remote): recover AutoResearch profile side effects
Problem:
Prepared Profile+Goal recovery restored the session registry and Goal sidecar but could leave a newly-created AutoResearch task running after a crash.
Root cause:
The task ownership token was generated inside task creation and was not durable in the parent transaction before the task directory appeared.
Fix:
Persist a transaction-owned create token in the prepared journal, bind transaction task IDs to a hash of that token, and remove the uniquely-owned task during prepared rollback and restart recovery. Preserve committed tasks and cover incomplete directory reservations.
Verification:
- go test ./internal/autoresearch ./internal/control ./internal/remote/workbench/runtime
- go test -race ./internal/autoresearch ./internal/control ./internal/remote/workbench/runtime
- go vet ./internal/autoresearch ./internal/control ./internal/remote/workbench/runtime
* ci: retry final baseline checks
Desktop (Wails):
- MarkdownRenderer annotates .katex/.katex-display/.katex-html with
data-latex-source attribute from rehype-katex MathML annotation
- messageSelectionCopy handler reads data-latex-source and replaces
KaTeX rendered text with the original $...$ / ... LaTeX markup
- Auto-locks to the full formula when any part is selected
- Collects all display blocks in the range for multi-row formulas
CLI (TUI):
- mathNode retains both value (Unicode display) and source (raw LaTeX)
- mdRenderer gains rawMath mode and RenderRaw() method
- renderAssistantMarkdownRaw builds a parallel transcript with
raw LaTeX preserving $...$ delimiters
- selectedText() uses rawWrappedLines when available to restore
original LaTeX on clipboard copy
Fixes: Ctrl+C copy of LaTeX math expressions in both desktop and CLI
* fix(desktop): clear stale session data when reusing blank tab / 修复新建会话时展示旧工具调用记录的问题
When creating a new session tab via handleNewTab → openBlankSession → ensureBlankTab, the backend may reuse an existing blank tab that still carries stale session data (e.g. tool calls from a previous session). The frontend then loads this stale data via HistoryPageForTab, rendering 'N 个工具' tool-fold cards that should not appear in a fresh session.
The fix calls app.NewSessionForTab(meta.id) after EnsureBlankTab returns, mirroring what newSession() already does, to guarantee the backend session is clean before loadSessionDataForTab runs. A silent catch with addBreadcrumb preserves diagnostic signal when the backend refuses.
Cache-impact: none — only desktop/frontend TypeScript, no cache-sensitive internal paths.
* Fix reused blank tab hydration
Problem:
Reusing an existing blank tab could leave stale tool cards visible and allow an earlier history request to repopulate them.
Root cause:
The frontend kept the existing tab state and used a non-reset hydration. The added NewSessionForTab call did not invalidate local requests and could no-op for an already blank backend session.
Fix:
Remove the redundant backend session rotation, invalidate checkpoint work, and force a reset hydration bound to the TabMeta session path.
Verification:
- pnpm exec tsx src/__tests__/new-session-load-race.test.tsx
- pnpm exec tsc --noEmit
- cd desktop && go test ./...
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* Add reused blank tab hydration regression
Contract:
A reused blank tab starts a fresh history load, clears orphaned tool cards immediately, and rejects late history from the previous session.
Coverage:
Add an integration-style useController test that holds the startup history request, reuses the same tab id with a new session path, and releases the stale tool history after the fresh empty hydration completes.
Verification:
- pnpm exec tsx src/__tests__/new-session-load-race.test.tsx
- pnpm exec tsc --noEmit
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* Fix stale desktop navigation completions
Problem:
A tab click made while blank, topic, or session navigation was in flight could be overwritten by the older completion. The frontend could show an abandoned tab or disagree with the backend active tab.
Root cause:
Navigation and tab-switch queues tracked last-click ownership independently, and controller navigation helpers advanced their epoch only when execution began. A queued click therefore did not invalidate an already-running operation, while late metadata and hydration work could still apply.
Fix:
Capture one shared navigation intent at enqueue time, propagate it through composite controller operations, and reject stale UI, hydration, and metadata updates. When a stale backend navigation already activated its result, reassert the currently visible tab so frontend and backend focus converge.
Verification:
- new-session-load-race: 28 passed
- tab-switch-hydration: 75 passed
- activate-topic-stale: 8 passed
- open-topic-coalescing: 15 passed
- app-chrome-tabs: 83 passed
- pnpm exec tsc --noEmit
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* Fix stale navigation reassertion races
Problem:
A stale tab switch or fork repair could finish after a third navigation and leave the backend active tab behind the visible frontend tab.
Root cause:
Both stale-completion paths captured the visible tab once and issued a single SetActiveTab call without rechecking ownership after the RPC returned.
Fix:
Route stale switch and fork completions through the shared reassert loop so each await is followed by a fresh visible-tab check.
Verification:
- pnpm exec tsx src/__tests__/tab-switch-hydration.test.tsx
- focused desktop frontend navigation matrix
- pnpm exec tsc --noEmit
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* Add stale navigation repair regressions
Contract:
A stale tab-switch or fork repair must keep following navigation ownership when a third navigation completes while SetActiveTab is still pending.
Coverage:
Use deferred backend gates to force both three-way interleavings and assert that the latest tab remains visible and backend-active.
Verification:
- pnpm exec tsx src/__tests__/tab-switch-hydration.test.tsx (79 passed)
- focused desktop frontend navigation matrix (409 passed)
- pnpm exec tsc --noEmit
- git diff --check
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: xiaoshuai1024 <[email protected]>
Co-authored-by: Sivan <[email protected]>
Session rebind (project-tree switch) rebuilds the controller with a fresh
executor whose LastUsage() returns nil and ContextSnapshot() reports used=0.
The telemetry file (.telemetry.json) already had cumulative totals but was
missing three categories of data needed after a rebind:
1. Context fill (used): now persisted as LastUsedTokens in sessionUsageStats
and used as a fallback in ContextUsageForTab/ContextPanel when the executor
reports used=0.
2. Per-turn token breakdown (prompt/completion/reasoning/cache hit/miss): now
persisted as Last* fields and restored in ContextPanel when LastUsage() is
nil, keeping the donut chart and type breakdown accurate.
3. Right-panel refresh timing: a new contextPanelSeq counter is bumped after
hydration ancillary data lands, merged into ContextPanel's refreshKey so
RequestCount/ElapsedMs/SessionCost re-fetch after a rebind instead of
showing stale data.
Frontend also drops the stillVisible guard on ancillary context/effort/jobs
dispatch (tab-scoped dispatchTo is safe for non-visible tabs) and adds an
activeTabId-driven fallback effect for StatusBar.
Fixes: #5335, #5766
* fix(jobs): reject traversal in SetActiveSessionPath sessionPath
Follow-up to the issue #6932 thread (the StartForSession half is fixed in
#6936). SetActiveSessionPath trimmed sessionPath but did not reject `..`
components. Combined with store.SessionJobsDir(sessionPath) (which builds
<id>.jobs from the transcript path), a malicious or malformed sessionPath
like "[tmp][..][sibling][passwd.jsonl]" would resolve to a sibling
sidecar directory and then flow into:
* m.artifactDirs[parentSession] = newDir
* migrateArtifactDirForSession (file copies to the caller-picked dir)
* loadSessionArtifacts (reads meta files from the caller-picked dir)
This change:
* Adds validateSessionPath, which rejects empty input, NUL and control
characters, and any path component equal to "..". Unlike
validatePathSegment, separators are allowed because sessionPath is an
absolute or workspace-relative path. Names like "..hidden" and "..."
remain accepted as legitimate filenames; only ".." as a standalone
component is rejected.
* Calls validateSessionPath at SetActiveSessionPath entry, after trim
and before any lock or filesystem side effect. A failure emits a
Warning notice through the manager's sink (so the caller can observe
the rejection in its event stream) and returns immediately without
touching m.active, m.artifactDirs, or the migration/load paths.
* Adds jobs_sessionpath_test.go with a 15-case table for
validateSessionPath, plus three integration tests:
- TestSetActiveSessionPath_RejectsTraversalPayload: a payload
containing ".." does not create a sidecar directory next to a
canary file, leaves m.artifactDirs empty, and emits the expected
warning.
- TestSetActiveSessionPath_RejectsLeadingDotDot: a leading ".."
component is rejected the same way.
- TestSetActiveSessionPath_AcceptsValidInput: a typical
t.TempDir()/abc.jsonl input still flows through to
store.SessionJobsDir, caches correctly, and a follow-up
StartForSession writes the artifact next to the transcript.
This PR is independent of #6936: validateSessionPath does not call
validatePathSegment, so the two can be reviewed and merged in either
order. Both fixes share the underlying issue #6932 threat model.
* fix(jobs): validate session paths across platforms
Problem: SetActiveSessionPath traversal validation missed Windows-style backslash components on Unix and changed the legacy empty-path behavior.
Root cause: filepath.ToSlash only rewrites the current platform separator, and validation ran before the active-only empty-path branch.
Fix: Normalize both separator styles explicitly, keep empty paths as active-only calls, document the trusted-path boundary, and add regression coverage.
Verification: go vet ./internal/jobs/...; go test -count=1 ./internal/jobs/...; go test -race -count=1 ./internal/jobs/...; go test -count=1 ./...
Co-authored-by: SivanCola <[email protected]>
* fix(jobs): preserve trusted session path semantics
Problem: Trusted transcript paths containing valid .. or host-specific separator characters were rejected, and rejection left active-session routing and persistent bindings stale.
Root cause: The validator conflated lexical path syntax with trusted-root containment and returned before updating independent session-routing state.
Fix: Limit trusted-path validation to control characters, preserve the caller path spelling, update active routing on rejection, clear stale bindings, and add regression coverage for relative paths and invalid rebinding.
Verification: go vet ./internal/jobs/...; go test -count=1 ./internal/jobs/...; go test -race -count=1 ./internal/jobs/...
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* feat(desktop): add regional pricing and English localization
Problem: Desktop cost estimates assumed a single CNY price table, while automatic session titles and several desktop options remained Chinese-only for English users.
Root cause: Currency selection was coupled to persisted language settings, browser locale never reached controller rebuilds, cost telemetry lacked consistent regional repricing, and fallback copy was not localized at the API boundary.
Fix: Add official CNY/USD regional pricing with auto locale detection and an explicit desktop override, preserve structured currency telemetry and custom prices, localize session titles and missing desktop options, and make generated titles follow the user's language.
Verification: Passed config/boot/serve and full desktop Go suites, frontend typecheck and production build, focused frontend behavior tests, the cache guard, and git diff checks.
* fix: preserve regional pricing and manual topic titles
Problem:
Auto regional pricing could overwrite persisted official price tables, and manually entered default-like topic titles were localized.
Root cause:
Pricing normalization did not retain TOML provenance, while title localization inferred automation from title text alone.
Fix:
Carry regional pricing provenance through config loading, normalization, backfill, and edit replay; only explicit currency choices override persisted tables. Propagate topic title source through desktop runtimes and localize only auto-generated default titles.
Verification:
go test ./internal/config ./internal/boot ./internal/serve -count=1
cd desktop && go test ./... -count=1
* chore: credit regional localization contributors
Context: PR #6945 consolidates regional pricing preservation and English session-title work from three related contributor PRs.
Attribution: Record the source authors with their public GitHub noreply identities so the consolidated repository history preserves their contributions.
Verification: Attribution-only commit; product files are unchanged.
Co-authored-by: dmitri-0 <[email protected]>
Co-authored-by: erphenheimer <[email protected]>
Co-authored-by: delight-f <[email protected]>
* feat(cli): add regional pricing controls
Context: PR #6945 already provides regional DeepSeek pricing in desktop, but CLI startup, in-session settings, and JSON result output did not expose the same currency contract.
Changes: Resolve Auto pricing from the active CLI locale, add global currency config and slash commands, refresh the current controller after language or currency changes, and emit currency-aware total_cost while retaining total_cost_usd as a compatibility alias. Reject mixed-currency totals and prevent project config from overriding user regional preferences.
Verification: go test ./internal/cli ./internal/config ./internal/boot ./internal/control ./internal/i18n -count=1; go test ./... -count=1 with DEEPSEEK_API_KEY unset.
* docs(cli): document regional pricing controls
Documentation:
- Add English and Chinese examples for global currency selection and locale-based Auto.
- Explain structured cost/currency output and the total_cost_usd compatibility alias.
- List the /currency session command and runtime refresh behavior.
Verification:
- go test ./internal/cli -run "Test(ConfigCurrency|RunOutput)" -count=1
- git diff --check
---------
Co-authored-by: dmitri-0 <[email protected]>
Co-authored-by: erphenheimer <[email protected]>
Co-authored-by: delight-f <[email protected]>
When a session is resumed or its history replayed, orphaned
[Pasted text #N] labels that were never expanded (because
pastedBlocks was lost during a prior reload) are stored raw in
the transcript. The model receives an opaque token it cannot
interpret.
repairOrphanedPasteLabels runs during loadSessionUnlocked:
1. Scans the same transcript for prior expansions (--- Begin/End ---)
2. Inlines recovered content into orphaned labels
3. Replaces unrecoverable labels with [pasted content unavailable]
This cleans historical pollution at load time, complementing the
TUI-layer fix (chat_tui_paste.go) that prevents new orphaned
labels on submit.
Ref: #6948
* feat(cli): add privacy-preserving telemetry and crash reports
Summary:
- add bounded CLI usage metrics with explicit policy controls and silent retry behavior
- capture sanitized panic reports locally and require user confirmation before upload
- separate Desktop and CLI aggregates in the crash-report worker and stats dashboard
- document telemetry fields, privacy guarantees, report commands, and D1 migration requirements
Compatibility:
- default missing client surfaces to Desktop for existing clients and payloads
- migrate existing aggregate rows to the Desktop surface before deploying the new worker
Verification:
- go test ./internal/telemetry ./internal/crashreport ./internal/config ./internal/cli ./cmd/reasonix
- env -u DEEPSEEK_API_KEY go test ./...
- pnpm test (workers/crash-report, 49 tests)
- pnpm run typecheck (workers/crash-report)
- ./scripts/cache-guard.sh
* fix(stats): decouple CLI schema migration from Worker deploy
Problem:
The initial surface migration rebuilt the existing Desktop telemetry tables and changed their primary keys. Running it before the new Worker broke the released Worker's ON CONFLICT clauses, while deploying the Worker first queried columns that did not exist yet.
Root cause:
Desktop and CLI isolation was implemented by changing a shared persisted schema, which made the old and new Worker contracts mutually exclusive during rollout.
Fix:
Keep the released Desktop tables and primary keys unchanged, route CLI telemetry to additive cli_* tables, and let the new Worker initialize those tables with idempotent CREATE IF NOT EXISTS statements. Keep the migration additive and identical to the runtime bootstrap so migration, deployment, and rollback order no longer affect Desktop ingestion.
Verification:
- pnpm test (workers/crash-report, 54 tests)
- pnpm run typecheck (workers/crash-report)
- git diff --check
* fix(test): make crash report mode check portable
Problem:
The Windows CI job failed because os.FileMode reports synthesized POSIX bits on Windows rather than the 0600 mode requested by AtomicWriteFile.
Root cause:
The test treated Unix permission bits as a cross-platform ACL signal even though Windows enforces file access through inherited ACLs.
Fix:
Keep the 0600 assertion on Unix-like systems and skip only that non-portable mode-bit comparison on Windows while retaining all report creation, sanitization, and bounded-retention checks.
Verification:
- go test ./internal/crashreport
- GOOS=windows GOARCH=amd64 go test -c ./internal/crashreport
* fix(cli): preserve actionable crash report groups
Problem:
CLI panic reports used runtime/debug.Stack as their top frame, so unrelated panics collapsed into one fingerprint. Queue pruning also deleted reports written in an unknown future format.
Root cause:
Top-frame extraction selected the first source location without filtering capture/runtime frames, and pruning treated every JSON file as a current report.
Fix:
Select the first application frame with its sanitized function name, retain a safe fallback, and prune only recognized current-schema report files. Reject newer schemas from old-client listing so their fields remain untouched.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- go test -race ./internal/crashreport ./cmd/reasonix
- go vet ./...
- pnpm test (workers/crash-report, 54 tests)
- pnpm run typecheck (workers/crash-report)
- Windows cross-compile of internal/crashreport
* fix(cli): request consent before telemetry upload
Problem:
A fresh release-build interactive CLI treated a missing telemetry setting as auto and could start anonymous uploads without first informing the user.
Root cause:
The runtime default and persisted decision state were conflated, and telemetry startup had no consent owner before the reporter launched its background flush.
Fix:
Preserve a missing cli_metrics field as an undecided state, show a localized one-time [Y/n] disclosure before the first eligible upload, persist auto or off before continuing, clean pending counters on rejection, and fail closed when saving fails. Keep CI, Safe Mode, development, environment opt-outs, and non-interactive sessions silent and disabled until a decision exists.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- go test -race ./internal/telemetry ./internal/cli
- go vet ./...
- pnpm test (workers/crash-report, 54 tests)
- pnpm run typecheck (workers/crash-report)
- ./scripts/cache-guard.sh
- git diff --check
* fix(jobs): reject path traversal in artifact parentSession and kind
Issue: #6932
StartForSession trimmed parentSession but did not reject path separators
or '..' components. Combined with filepath.Join(tempRoot, parentSession, id)
and the subsequent os.MkdirAll + os.OpenFile, a caller controlling the
session id or kind could write artifact files outside the manager's temp
root, subject to the running process's filesystem permissions.
This change:
* Validates parentSession and kind at StartForSession entry. A validation
failure short-circuits before any goroutine or artifact is created; the
job is registered as Failed with the validation error in artifactErr so
list / Wait calls observe it.
* Adds a defense-in-depth containment check in openArtifactLocked: the
resolved dir must remain inside m.tempRoot. This catches any future
path that reaches openArtifactLocked through a different route (e.g.
SetActiveSessionPath with a malicious sessionPath).
* Adds unit tests in jobs_security_test.go covering:
- validatePathSegment table (valid + every rejected character class)
- StartForSession with traversal parentSession (no file written)
- StartForSession with traversal kind (no file written)
- StartForSession with normal input (regression guard)
- openArtifactLocked with an out-of-root dir in artifactDirs
* docs(jobs): clarify artifact containment boundary
Problem: The validator and regression-test comments implied every accepted artifact path remains under the manager temp root.
Root cause: The explanation overlooked trusted persistent sidecar directories bound by SetActiveSessionPath, which intentionally live next to transcripts.
Fix: Clarify that path-segment validation confines only the temp-root fallback while persistent directories come from trusted store paths.
Verification: go test -count=1 ./internal/jobs/...; go test -count=1 ./...
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* fix(permission): harden dynamic Bash authorization
Problem:
Broad Bash allow rules could silently authorize nested command and process substitutions, while treating every dynamic shell form as human-required would make Auto unnecessarily disruptive.
Root cause:
Bash rule matching did not distinguish reusable static commands, exact-only dynamic syntax, and nested or indirect execution before applying scoped allows and controller bypasses.
Fix:
Classify Bash ASTs into reusable, exact-only, and human-required tiers. Restrict all dynamic approvals to exact Bash=<literal> grants, require a human for nested or indirect execution outside YOLO, preserve Auto fallback for expansions, assignments, redirects, heredocs, and globs, and apply the same contract across interactive, headless, persistence, CLI, and ACP paths.
Verification:
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- go vet ./...
- env -u DEEPSEEK_API_KEY go test ./...
- gofmt -l .
- git diff --check
Fixes#6963
* fix(control): remove unused approval wrapper
Problem:
The CI lint job rejected PR #6985 because requestApprovalDecision was left unused after approval paths moved to the option-aware helper.
Root cause:
The dynamic Bash approval refactor replaced the wrapper call sites but did not remove the obsolete wrapper itself.
Fix:
Remove the unused wrapper without changing approval behavior.
Verification:
- go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- gofmt -l .
- git diff --check
* fix(permission): preserve dynamic compound rule precedence
Problem:
Dynamic compound Bash commands could reach fallback before per-segment Deny or Ask rules were evaluated. Raw rule matching also lost the established :* prefix semantics, and brace expansion or extglob arguments could be treated as reusable static commands.
Root cause:
The approval class short-circuited compound decomposition, the raw matcher interpreted Bash prefix markers as ordinary glob text, and the AST feature pass did not normalize brace expansion before testing whether words were static.
Fix:
Run compound segments through the same complete policy recursively, preserve Bash prefix semantics with AST-aware raw matching, prioritize wildcard tokens in glob matching, and classify brace/extglob or other non-static arguments as exact-only expansions using mvdan syntax nodes.
Verification:
- go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- go vet ./...
- env -u DEEPSEEK_API_KEY go test ./...
- gofmt -l .
- git diff --check
* fix(permission): detect indirect builtin execution
Problem:
Bash builtin eval and builtin source could inherit broad Bash authorization even though their direct forms require human approval.
Root cause:
The indirect-execution classifier recursively handled command, exec, env, and other wrappers, but omitted Bash's builtin wrapper.
Fix:
Treat builtin as a recursive command wrapper and add positive and negative regression cases so eval/source require a human while ordinary builtins stay reusable.
Verification:
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- go vet ./...
- gofmt -l .
- git diff --check
* fix(config): prevent .mcp.json plugins from leaking into reasonix.toml / 防止 .mcp.json 插件泄漏到 reasonix.toml
RenderTOMLProjectDelta (used by saveProjectIncremental) now filters out
plugins sourced from .mcp.json (MCPSourceProjectMCPJSON). Previously,
saving the config after Load() merged .mcp.json servers into c.Plugins,
the TOML renderer would write them into reasonix.toml. On next load,
their Source provenance changed from project_mcp_json to project_config,
invalidating launch grants and activation receipts keyed by ConfigSource.
Fixes#6702.
* fix(config): preserve MCP plugin ownership on save
Problem:
Scoped TOML saves could copy merged MCP entries into the wrong config file and could leave a removed project plugin behind while foreign-source entries remained in memory.
Root cause:
The project delta filtered only .mcp.json entries, while full rendering and plugin-section removal still used the unfiltered runtime plugin list.
Fix:
Centralize destination-aware plugin filtering for full and incremental renders, retain unknown provenance for directly constructed configs, and mark explicit upgrade imports as user-owned before saving.
Verification:
- go test ./internal/config
- go vet ./internal/config
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: jiangrenhang <[email protected]>
Co-authored-by: SivanCola <[email protected]>
* fix(machine): resolve project session scope for --dir
* test(machine): pin project session directory mapping
* fix(machine): preserve explicit session directory scope
Problem:
The PR reinterpreted the documented --dir session-store argument as a project root, which made existing machine Session, Task, and Recovery callers return empty or not-found results.
Root cause:
A single flag was used for two distinct path scopes instead of preserving the existing contract and adding an explicit project selector.
Fix:
Keep --dir as the session storage directory, add --project-root for project-scoped lookup, share directory resolution across Session and Task commands, reject conflicting selectors, and document both modes in English and Chinese.
Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli with focused machine tests
- go vet ./internal/cli
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* fix: honor configured Bash for Windows plugin hooks
Problem:
Windows plugin hooks that explicitly request Bash can fail after upgrade even when a usable non-standard Git Bash path is configured. Expanded plugin paths can also remain in a form that Git Bash cannot consume.
Root cause:
Hook execution and diagnostics rediscovered Bash independently instead of receiving the effective configured shell path, while Windows plugin-root expansion did not normalize shell-form commands for Bash.
Fix:
Propagate the configured Bash runtime through hook execution and diagnostics, normalize expanded Windows plugin paths for explicit Bash hooks, and report missing runtime dependencies in CLI and Desktop doctor surfaces. Add Superpowers and custom-path regression coverage plus bilingual documentation updates.
Verification:
go test ./internal/hook ./internal/capdiag ./internal/boot ./internal/cli
go test -race ./internal/hook ./internal/capdiag
env -u DEEPSEEK_API_KEY go test ./...
(cd desktop && go test ./...)
go vet ./internal/hook ./internal/capdiag ./internal/boot ./internal/cli
(cd desktop && go vet ./...)
Windows amd64 cross-compilation for affected root and Desktop packages
git diff --check
Cache-impact: none
* fix: remove obsolete Windows hook wrappers
Problem:
The CI unused-code linter rejects two compatibility wrappers that became unreachable after Hook execution started injecting the effective Bash runtime.
Root cause:
The runtime-aware call sites moved to resolver-injected helpers, but the old zero-argument resolver wrappers remained in the Windows compatibility module.
Fix:
Remove the two obsolete wrappers and keep the shared runtime-aware parsing helpers and cached Bash discovery path.
Verification:
go test ./internal/hook ./internal/capdiag ./internal/boot ./internal/cli
go vet ./internal/hook ./internal/capdiag ./internal/boot ./internal/cli
gofmt -l internal/hook/windows_compat.go
git diff --check
bash scripts/check-cache-impact.sh with the PR metadata
Cache-impact: none
* test: normalize Superpowers Bash hook expectation
Problem:
The Windows CI test expected a host-native backslash plugin root even though explicit Bash hooks intentionally receive a forward-slash path.
Root cause:
The Superpowers 6.2.0 regression assertion joined the temporary plugin root without applying the same platform-independent slash contract that the Hook loader guarantees for Bash.
Fix:
Normalize the expected plugin root with filepath.ToSlash so the test verifies the intended Git Bash command on Windows and remains stable on Unix hosts.
Verification:
go test ./internal/hook
Windows amd64 test-binary compilation for internal/hook
gofmt -l internal/hook/hook_test.go
git diff --check
Cache-impact: none
* docs(release): prepare v1.18.0 notes
Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
* docs(release): refine v1.18.0 notes
Summary:
Clarify Kimi K3 support across the official Kimi APIs and OpenCode Go, retain the distinct reasoning scales, and record every affected product surface. Keep the Desktop and native CLI channel copy aligned with the merged release contract.
Verification:
Validate the catalog, run the release-notes test suite, render both languages, and check the diff for whitespace errors.
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Add native Kimi K3 support for OpenCode Go
Problem:
Reasonix did not expose Kimi K3 in the OpenCode Go subscription preset even though the upstream catalog now serves it.
Root cause:
The curated OpenCode Go model catalog and capability metadata predated Kimi K3, while the generic OpenAI-compatible provider clamped max reasoning effort to high.
Fix:
Add Kimi K3 with vision, 1M context, and max-only reasoning metadata; preserve explicitly supported max effort; safely migrate untouched legacy presets; and synchronize desktop previews and documentation.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- cd desktop && go test ./...
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend exec tsx src/__tests__/provider-model-refresh.test.ts
- ./scripts/cache-guard.sh
* Fix Kimi K3 compatibility paths
Problem:
Kimi K3 max-only effort metadata was omitted by reasonix-guard assist, and legacy normalization replaced existing custom Kimi K3 overrides.
Root cause:
The recovery command duplicated provider configuration assembly, while the migration assigned the Kimi K3 override unconditionally.
Fix:
Forward resolved effort and capability metadata through the recovery path, merge only missing Kimi K3 defaults, preserve case-insensitive custom override keys, and add regression coverage.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- cd desktop && go test ./...
- ./scripts/cache-guard.sh
- git diff --check
* Support Kimi K3 high and max effort
Problem: Kimi K3 was advertised as max-only even though the service accepts both high and max, so /effort high was rejected and capability displays were incomplete.
Root cause: The preset, legacy catalog migration, desktop mock, documentation, and regression fixtures all encoded the old max-only capability.
Fix: Advertise high and max while keeping max as the default, and update every compatibility path and focused regression test. Existing custom Kimi K3 overrides remain unchanged.
Verification: env -u DEEPSEEK_API_KEY go test ./...; (cd desktop && env -u DEEPSEEK_API_KEY go test ./...); pnpm --dir desktop/frontend typecheck; pnpm --dir desktop/frontend exec tsx src/__tests__/provider-model-refresh.test.ts; ./scripts/cache-guard.sh; git diff --check.
* Adapt official Kimi APIs for K3
Problem: The PR exposed Kimi K3 only through OpenCode Go, leaving the official Kimi CN and Global presets without the model or its documented request contract.
Root cause: The direct-API catalogs predated K3, while the generic OpenAI-compatible wire shape drops assistant reasoning, forwards configurable temperature, and uses max_tokens.
Fix: Add K3 capability overrides and conservative legacy catalog migration for both official endpoints; preserve complete assistant messages; omit fixed sampling fields; map output limits to max_completion_tokens; and keep relay behavior isolated.
Verification: env -u DEEPSEEK_API_KEY go test ./...; cd desktop && env -u DEEPSEEK_API_KEY go test ./...; pnpm --dir desktop/frontend typecheck; pnpm --dir desktop/frontend exec tsx src/__tests__/provider-model-refresh.test.ts; ./scripts/cache-guard.sh; git diff --check.
* Preserve K3 reasoning through remote broker
Problem:
Remote Workbench lost the Kimi K3 reasoning round-trip policy when the Desktop provider was represented by the Host broker proxy. A configured PostLLM hook could therefore replace the reasoning persisted for the next official K3 request.
Root cause:
The new ReasoningRoundTripPolicy was implemented by the OpenAI-compatible client and consumed by the Agent, but the broker descriptor and hostProvider only forwarded the older tool-call reasoning capabilities.
Fix:
Carry an optional reasoningRoundTrip capability through provider descriptors and the Remote wire schema, expose it from hostProvider, and add an end-to-end broker regression that proves display-transformed reasoning still replays the original provider text.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- go test -race ./internal/remote/broker
- go vet ./...
- go test ./... (desktop module)
- pnpm typecheck (desktop/frontend)
- go run ./cmd/remote-protocol-gen --check
- ./scripts/cache-guard.sh
* Preserve custom vision settings during K3 migration
Problem: Kimi K3 migration appended vision support to every matching provider, including configurations that explicitly disabled or customized vision models.
Root cause: provider-level vision_models distinguishes an omitted nil list from an explicit empty or user-curated list, but the migration ignored that distinction.
Fix: add Kimi K3 vision support only for omitted lists or exact legacy preset lists, and preserve explicit empty and custom lists for direct Kimi and OpenCode Go migrations. Add regression coverage for both paths.
Verification: go test ./...; ./scripts/cache-guard.sh; go run ./cmd/remote-protocol-gen --check; git diff --check
Refresh the bilingual v1.18.0 catalog against the latest main-v2 release boundary, including #6155 channel updates and #6944 signing-gate hardening.
All required CI, CodeQL, release-notes, cache, and platform checks passed.
When a user opens a folder, createEmptySessionFile writes the new
session's BranchMeta sidecar with an empty Scope field. During the
tab controller build, if resolveSessionBinding reaches
sessionBindingFromMeta, the empty Scope causes DefaultScope() to
return global, and applySessionBindingToTab overwrites the tab's
correct project scope. The wrong value is persisted to
desktop-tabs.json while desktop-projects.json remains correct.
The fix adds pinSessionBranchMeta, called immediately after
createEmptySessionFile, which writes the correct scope, workspace
root, and topic ID into the BranchMeta before the controller build
reads it — closing the empty-Scope window regardless of which
binding path is taken.
Closes#6914.
When a session is reloaded (recovery chain, model switch, resume),
the in-memory pastedBlocks slice is cleared. If the user later
re-submits an input that still references a [Pasted text #N] label
(via history navigation or quoted reply), expandPastedBlocks could
not find the matching block and the raw placeholder was sent to the
model — the model saw an unexpanded token instead of the pasted
content.
Recover orphaned labels by scanning conversation history for prior
submissions that contain the expanded --- Begin/End --- block and
re-expanding from there. Labels with no recoverable content are
stripped so the model never receives a raw placeholder.
Ref: #6948
advance() previously wrote interceptMsg directly to the goalMachine
struct field as a side effect. Multiple FSM mechanisms (todo intercept,
self-check, idle detection) could each set interceptMsg, relying on
sequential ordering within the switch statement to avoid contention.
Extract intercept as an explicit field in goalAdvanceResult. The
caller (advanceGoalAfterTurn) stores it via a new setIntercept()
method, which takeIntercept() reads as before. This makes the data
flow explicit: advance() produces output, the caller routes it.
No behavior change. One theoretical edge case (auto-turn limit firing
simultaneously with an intercept) has a different interceptMsg state
after advance() returns, but the loop exits via cont=false immediately
so the intercept is never consumed and gets cleared on the next goal
lifecycle event.
Related to #6958 (FSM output model refinement).
The concrete bug #6956 was fixed separately in PR #6960.
When the agent signals [goal:blocked], the goal FSM increments the
blocks counter but did not reset g.idleTurns. If blocks < 3 (so
notice remains empty) and the agent makes no tool calls for 2
subsequent turns, the idle detection at maxGoalIdleTurns fires and
sets an interceptMsg that overrides the agent's blocked signal.
Add g.idleTurns = 0 in the GoalStatusBlocked case so that an explicit
blocked signal resets the idle counter. The agent is not idling — it
deliberately signalled a blocking condition.
Fixes#6956
When rapidly switching between saved sessions on the same tab, the old
session's runtime is detached (controller kept alive in detachedSessions)
if it was actively running. Switching back triggers a rebuild path that
tries to acquire an OS-level file lock already held by the detached
controller's file descriptor — LockFileEx with LOCKFILE_EXCLUSIVE_LOCK
on Windows prevents a second handle from the same process.
Fix: before entering the build path, check if the target session has a
detached runtime. If found, reattach it via attachExistingSessionRuntime
instead of building a new controller. This avoids the lease conflict
entirely and is more efficient.
Also fix canReclaimCurrentProcessSessionLease to check detachedSessions,
preventing incorrect reclaim attempts against detached runtimes.
Fixes#6955
When baseState.revision == currentRevision the revision ledger proves
no external writer has touched the transcript. In that case the
byte-level messagesHavePrefix comparison in checkSnapshotWrite is
redundant — the revision CAS already vouches for consistency — and its
false positives (normalisation / JSON round-trip / local-only metadata)
drive spurious diverged conflicts that cascade into recovery-branch
chains and force-saves that break the prefix-cache.
Future-tense (Delta): anchor on the revision CAS match, not the
byte-level disk snapshot (Gamma). Same insight as Agda PR #8611
(makeTau's nTarget = nOld + nctel - 1).
Ref: #6027 Phase 3, #6948
Problem: Stable release v1.17.21 reached SignPath through release-stable.yml while the provider policy allowed only release-desktop.yml, blocking both Windows architectures after CLI and npm publication.
Root cause: The provider-side build-definition allowlist was maintained manually, and the stable orchestrator did not prove the complete signing path before starting public publishers.
Fix: Add a machine-readable signing contract, workflow call-graph validation, protected fingerprints, exact CODEOWNERS coverage, a zero-publication dual-architecture preflight, and fail-closed standalone attestation checks.
Verification: Full PR CI, CodeQL, Windows Desktop packaging, race, coverage, release workflow contract tests, cache guard, root and Desktop Go tests, vet, actionlint, npm dry build, and diff checks passed.
MiniMax uses Anthropic-style field names (input_tokens / output_tokens /
cache_read_input_tokens / cache_creation_input_tokens) on its
OpenAI-compatible endpoint instead of the standard OpenAI/DeepSeek
names. normaliseUsage() only recognised the latter, so MiniMax cache
stats were always zeroed out, causing the frontend to display
'cache not reported'.
Fix: add InputTokens, OutputTokens, CacheCreationInputTokens and
CacheReadInputTokens to wireUsage as fallbacks. normaliseUsage()
now resolves token counts and cache stats through a priority chain:
DeepSeek top-level -> OpenAI nested details -> Anthropic-style fields.
This benefits all OpenAI-compatible providers without provider-specific
branching.
Test: TestNormaliseUsageMiniMax covers cache-hit, cache-creation,
and DeepSeek field priority scenarios.
Context:
Reasonix needed one public Stable/Preview contract across native CLI archives and Desktop updates while keeping npm prerelease tags independent.
Changes:
- Add protected native CLI Preview prereleases without moving Homebrew or GitHub Latest.
- Add persistent Desktop Stable/Preview selection, channel-aware update state, R2 pointers, and legacy Canary compatibility.
- Preserve the complete production Windows payload and installer signing chain and fail closed when signing or Preview R2 prerequisites are unavailable.
- Add release gateway routing, workflow contracts, compatibility coverage, and bilingual settings copy.
Compatibility:
Missing and unknown Desktop channel settings remain Stable. Legacy canary, beta, and next settings normalize to Preview. New Preview clients reject stale test-signed Canary manifests while older clients retain the mirrored canary pointer.
Verification:
Root and Desktop Go suites, frontend test:all and production build, crash-report Worker typecheck and 45 tests, release workflow contracts, cache guard, release notes tests, six native CLI target builds, CodeQL, race, and Windows NSIS/portable CI all pass on head 97623c982.
* docs(release): prepare v1.17.21 notes
Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
* docs(release): complete v1.17.21 notes
Summary:
Expand the release surfaces beyond Desktop and add the missing CLI diagnostics and recovery ownership changes from PR #6912.
Verification:
Validated the release catalog, rendered the bilingual notes, ran the catalog tests, and checked the diff for whitespace errors.
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(desktop): sync typography code-region when global mono font changes (#6907)
When the user switches the global monospace font in Appearance settings,
reset any previously-customised TypographySettings code-region override
(followGlobal=true) so the global choice takes effect in code blocks
instead of being silently blocked by the --typography-code-font inline
style set by applyTypographyPreferences.
Also replace three hardcoded font-family: monospace occurrences
(.remote-file-view__path, .remote-file-view__editor,
.remote-hostkey-dialog__fp) with var(--font-mono, monospace), and
align .code-lines-wrap with the other code-block rules by using
var(--typography-code-font, var(--font-mono)).
* fix(desktop): preserve regional code font overrides
Problem: Selecting a global monospace font silently forced an explicit Code & Tools typography override back to follow-global, while valid quoted preset font stacks were rewritten unnecessarily.
Root cause: The settings handler treated the regional override as stale, and the original patch assumed quoted multi-word family names were invalid after CSS custom-property substitution.
Fix: Keep regional typography independent when the global monospace choice changes and restore quoted multi-word font family names.
Verification: pnpm --dir desktop/frontend exec tsx src/__tests__/typography-preferences.test.ts; pnpm --dir desktop/frontend typecheck; git diff --check.
Co-authored-by: SivanCola <[email protected]>
* test(desktop): protect regional code font behavior
Problem: The code-font fix relied on CSS inheritance and settings ownership contracts that were not covered by focused regression tests.
Risk: A future global monospace handler could silently overwrite an explicit regional choice, or nested code elements could fall back to the browser monospace default again.
Coverage: Render Appearance with a customized code region and verify a global monospace change preserves its stored preference and CSS variable. Assert the nested code and line-number viewer font declarations directly.
Verification: pnpm --dir desktop/frontend exec tsx src/__tests__/settings-refresh-snapshot.test.tsx; pnpm --dir desktop/frontend exec tsx src/__tests__/typography-overflow-contract.test.ts; git diff --check.
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* feat(desktop): add bounded regex search to code previews
Problem:
Workspace code previews only exposed literal search, the find shortcut was scoped to the focused viewer, and the floating search popup could obscure code.
Root cause:
JavaScript regular expressions run synchronously without cancellation, the workspace panel had no search action of its own, and the popup used absolute positioning inside the code viewport.
Fix:
Run opt-in regex searches in an inline worker with hard termination, debounce, stale-request cancellation, and pattern, source, and result limits. Add a visible workspace search action and panel-level shortcut, dock the search toolbar above code, preserve literal search by default, and localize bounded failure states.
Verification:
- pnpm --dir desktop/frontend test
- pnpm --dir desktop/frontend build
- pnpm --dir desktop/frontend exec tsx src/__tests__/line-number-code.test.tsx
- pnpm --dir desktop/frontend exec tsx src/__tests__/workspace-changes-errors.test.tsx
- pnpm --dir desktop/frontend exec tsx src/__tests__/workspace-preview-css.test.ts
- git diff --check
- Browser verification at the narrow dock width
* Fix regex result handling and search toolbar copy
Problem: Valid regex patterns such as .* lost non-empty matches when they also emitted a zero-length result, and the floating copy button remained hidden under the open toolbar.
Root cause: Match collection rejected on the first zero-length result, while the copy affordance was rendered independently of search state.
Fix: Skip zero-length matches with Unicode-safe advancement, report unsupported only for zero-only patterns, move copy into the toolbar while search is open, and let narrow toolbar actions wrap as a group.
Verification: pnpm test; pnpm build; git diff --check; in-app browser verification at a 242 px code-pane width.
* fix(desktop): preserve pending code search requests
* fix(desktop): bound and clarify regex preview search
Problem: Regex worker startup could remain pending without entering the 200 ms timeout, and line-by-line evaluation silently changed regular-expression semantics.
Root cause: The deadline was created only after the async worker import resolved, while regex matching reset state for every source line.
Fix: Start the deadline before worker creation, scan regex requests against the full source with line anchors preserved, and surface explicit localized errors for newline-spanning matches. Add regression coverage for both paths.
Verification: node --import tsx src/__tests__/line-number-code.test.tsx; node --import tsx src/__tests__/workspace-changes-errors.test.tsx; node --import tsx src/__tests__/workspace-preview-css.test.ts; pnpm typecheck; pnpm test:typecheck; pnpm build; git diff --check.
* fix(tui): add paste undo on Windows via Ctrl+Z (#6531)
On Windows Ctrl+Z has no job-control semantics, so repurpose it as
undo for paste operations. A LIFO undo stack (capped at 50 entries)
records the input state before each paste; Ctrl+Z pops and restores.
Folded pastes and image refs participate without double-pushing.
* fix(desktop): implement paste undo via explicit undo stack (#6531)
Replace the unreliable browser-native undo approach (execCommand /
setRangeText in WebView2) with an explicit paste undo stack in the
Composer. Before each paste the current input text is pushed onto a
ref-managed stack; a Ctrl+Z keydown handler pops and restores the
last saved state. Falls through to browser-native undo when the
stack is empty, preserving normal text-editing undo.
* Expose composer undo and redo controls
Problem:
Composer undo and redo behavior was not discoverable in Settings, help, context menus, or the user guides. YOLO tooltips also kept showing its default binding after a rebind.
Root cause:
The composer handled editing chords and Reasonix transactions locally instead of registering the actions in the shared shortcut catalog. The custom context menu exposed clipboard actions only, and documentation still described Ctrl/Cmd+Y as YOLO-only.
Fix:
Register locked platform undo and redo actions, protect their editing chords from legacy global bindings, reuse the transaction history from the context menu, render live YOLO shortcut labels, and update English and Chinese guidance.
Verification:
pnpm test:all
pnpm build
go test ./... in desktop
Real browser checks for Settings rows, live YOLO rebind labels, context-menu undo/redo, and a clean console
* Fix rich composer edit context menu
Problem:
The rich composer did not open Reasonix's edit context menu, so drafts containing skill or subagent tokens could not use the transaction-aware Undo and Redo actions.
Root cause:
Only the plain textarea wired the shared context-menu handler; RichComposerInput did not expose or forward a contextmenu event.
Fix:
Forward context-menu events from RichComposerInput and reuse the existing Composer edit menu for both editable surfaces. Add regression coverage for undoing and redoing a paste after an inline invocation.
Verification:
- pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx
- pnpm exec tsx src/__tests__/composer-session-draft.test.tsx
- pnpm exec tsx src/__tests__/composer-context-menu-clipboard.test.tsx
- pnpm test:typecheck
- pnpm build
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* Protect composer history from legacy YOLO bindings
Problem:
Legacy custom YOLO bindings on Ctrl/Cmd+Z or Ctrl/Cmd+Shift+Z intercepted
Composer undo and redo, toggling approval mode instead of editing the draft.
Root cause:
Composer matched the local YOLO shortcut before its transaction-aware history
handler and did not apply the reserved editing-chord policy used globally.
Fix:
Exclude reserved Composer undo and redo chords from local YOLO handling. Add
upgrade regression coverage for legacy bindings, rebound YOLO, and the default
Ctrl+Y behavior.
Verification:
- pnpm exec tsx src/__tests__/composer-session-draft.test.tsx (130 passed)
- pnpm exec tsx src/__tests__/keyboard-shortcuts.test.ts (71 passed)
- pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx (185 passed)
- pnpm exec tsx src/__tests__/shortcut-recorder.test.tsx (25 passed)
- pnpm test:typecheck
- pnpm exec vite build --logLevel error
- git diff --check
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* fix: redact sensitive data from error logs
Problem: CodeQL reported two error logging paths that could expose credentials embedded in runtime errors.
Root cause: The heartbeat and bot controller failure paths logged raw error values, while the stronger authentication scrub was private to the control package.
Fix: Add shared credential and error redaction helpers, mask URL userinfo, reuse the helper for authentication errors, and sanitize both reported logging sinks.
Verification: go test ./...; cd desktop && go test ./...; git diff --check.
* fix: close redaction edge cases
Problem: URL userinfo containing raw @ characters could leave a password suffix visible, and repeated credential redaction changed already-redacted KEY=value output.
Root cause: The userinfo regexp stopped at the first @, while redactKeyValues remasked the stronger helper's **** marker as [redacted].
Fix: Consume the complete userinfo segment through the final @ and preserve existing redaction markers during key/value masking. Add regressions for raw @ proxy credentials and idempotent KEY=value errors.
Verification: go test ./...; git diff --check.
Problem:
The repository had twelve open Dependabot alerts across the site, desktop frontend, and Cloudflare Worker development toolchains.
Root cause:
Direct dependency ranges and generated lockfiles still resolved vulnerable releases of Astro, PostCSS, sharp/libvips, SVGO, DOMPurify, and js-yaml.
Fix:
Upgrade the site to Astro 7.1.3, refresh the affected frontend transitive dependencies, and move all Worker projects to Wrangler 4.114.0 with compatible workers-types 5.x releases.
Verification:
- Site tests and Astro static production build
- Desktop TypeScript check, Mermaid sanitizer tests, and Vite production build
- Worker TypeScript checks and Wrangler deployment dry-runs
- Crash-report Worker test suite
* feat: add deterministic planner routing and depth
Problem:
The two-model coordinator invoked the planner for nearly every work-shaped turn, adding latency to questions, contextual follow-ups, and clearly atomic edits while producing plans with inconsistent engineering detail.
Root cause:
The planner gate returned only a boolean and inferred intent from the composed prompt, so it could not represent planning depth, host-owned approval, trusted turn state, or observable routing reasons.
Fix:
Introduce deterministic executor-only, plan-and-execute, and plan-for-approval routes with light/full depth and privacy-safe reason codes. Feed the gate pristine user text plus trusted Goal, Delivery, Plan Mode, synthetic, and conversation metadata. Add stable planner contracts, bounded per-turn research, host-enforced plan approval, executor validation guidance, documentation, and regression matrices.
Verification:
- ./scripts/cache-guard.sh
- go test ./internal/control ./internal/agent ./internal/boot -count=1
- go vet ./internal/control ./internal/agent ./internal/boot
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- focused race tests for planner routing, metadata, approval, and research budgets
Cache impact:
Low. The planner system prompt changes once to define stable light/full contracts; per-turn route and depth remain in the user turn. Tool schemas and ordering are unchanged, and the cache guard remains above its 90 percent threshold.
* fix: preserve planner handoff semantics
Problem:
Bare plan-first requests were classified as approval-only turns, so they could stop after planning instead of reaching the executor.
Root cause:
The deterministic directive table conflated plan-and-execute, plan-for-approval, and explicit no-execution intent.
Fix:
Add a distinct plan-only route, reserve plan-for-approval for explicit confirmation language, route bare plan-first requests through the executor, and preserve both execution boundaries on planner failure. Update English and Chinese contracts plus regression coverage.
Verification:
- go test ./internal/agent ./internal/control
- env -u DEEPSEEK_API_KEY go test ./...
- go vet ./...
- ./scripts/cache-guard.sh
* fix(planner): preserve execution after bounded planning
Problem: Task-first plan-only and approval boundaries could be missed, while a planner research-budget pause stranded ordinary work without a usable executor continuation.
Root cause: Routing only recognized leading planning directives, and Coordinator preserved tool-loop pauses even though planner research limits are host-owned per-turn policy.
Fix: Parse unquoted execution boundaries across the request with negation handling; roll back incomplete planner turns; fall back to the executor with the original task for ordinary plan-and-execute while keeping plan-only and approval routes fail-closed. Update docs and regression coverage.
Verification: go test ./... -count=1; go test -race ./internal/control ./internal/agent -count=1; go vet ./...; ./scripts/cache-guard.sh; live Planner-to-Executor tests with DeepSeek, LongCat, and GLM Coding Plan.
Co-authored-by: SivanCola <[email protected]>
* test(planner): verify plan-only continuation
Problem: Plan-only coverage proved that execution was suppressed and the plan was persisted, but did not prove that a later user command actually reached the executor with that saved plan.
Fix: Add a two-turn coordinator regression that plans without execution, then sends an execution continuation and verifies the executor receives the persisted plan while the planner is not invoked again.
Verification: go test ./internal/agent -count=1
Co-authored-by: SivanCola <[email protected]>
* fix(planner): skip bounded read-only actions
Problem: Short read-only commands such as running tests, reviewing a PR, or inspecting one file still invoked the planner and could be upgraded to a full plan in Delivery mode.
Root cause: Read-only verbs shared the generic work-term bucket with mutations, and anchored work was evaluated before ambiguous scope.
Fix: Detect requests containing only read-only work terms and route bounded non-ambiguous cases directly to the executor. Preserve full planning for high-risk, cross-surface, structured, complex, or ambiguous read-only work, and prioritize ambiguous scope over an anchored target. Update English/Chinese docs and the routing matrix.
Verification: go test ./... -count=1; go test -race ./internal/control ./internal/agent -count=1; go vet ./...; ./scripts/cache-guard.sh.
Co-authored-by: SivanCola <[email protected]>
* fix(release): sign Windows payload executables
Problem:
Windows Defender can quarantine reasonix-desktop.exe after install or update even though the downloaded NSIS installer is signed.
Root cause:
The release workflow applied Authenticode only to the finished NSIS container. The desktop executable, support binaries, portable archive entries, and generated uninstaller remained unsigned after extraction.
Fix:
Sign the exact Windows payload through a versioned SignPath configuration, rebuild the portable archive and NSIS installer from those signed files, verify the payload before signing the outer installer, and fail official releases closed when signing is unavailable. Preserve and inject a signed NSIS uninstaller in the second packaging pass.
Verification:
- go test ./... (desktop module)
- env -u DEEPSEEK_API_KEY go test ./...
- bash scripts/release-workflows.test.sh
- ./scripts/cache-guard.sh
- actionlint release workflows
- bash syntax and git diff checks
* fix(release): separate canary installer signing config
* fix(release): automate gated SignPath approvals
Problem:
Release certificates require one SignPath approval per request, causing four extra manual approval actions after the GitHub release gate.
Root cause:
The desktop workflow submitted each request synchronously and waited for an interactive SignPath approver even though the same release authority had already approved the protected GitHub environment.
Fix:
Submit requests asynchronously, validate the expected signing policy, approve them through the dedicated CI identity, poll to a final successful state, and securely download the signed artifacts. Exercise the same path in canary with an approval-enabled test policy and document the administrator contract.
Verification:
- cd desktop && go test ./...
- bash scripts/release-workflows.test.sh
- actionlint release-stable.yml release-desktop.yml
- git diff --check
* fix(release): default SignPath API endpoint
The GitHub connector does not expose a SignPath API URL when requests are submitted asynchronously. Use SignPath's documented SaaS endpoint by default and keep the script override available for self-hosted installations.
Verified with desktop Go tests, release workflow contract tests, actionlint, and git diff --check.
* fix(release): restrict production signing origin
Problem:
Repository-level SignPath credentials were available to workflows on ordinary repository branches, while the production signing policy allowed every branch origin. A writer could therefore construct an unreviewed workflow that requested a trusted production signature.
Root cause:
Stable and prerelease builds ran directly from tag origins, so the SignPath policy could not use one exact protected branch without breaking tag releases. The policy had consequently been configured with an unrestricted branch wildcard.
Fix:
Relay stable and desktop prerelease tags to the current workflow on protected main-v2, keep immutable tags as build candidates, add a gated non-publishing production-signing smoke mode, and codify the exact main-v2 SignPath origin in tests and administrator documentation.
Verification:
- bash scripts/release-workflows.test.sh
- actionlint v1.7.12 on all changed workflows
- cd desktop && go test ./...
- cd desktop && go test . -run signing contract tests
- ./scripts/cache-guard.sh
- git diff --check
* fix(release): wait for manual smoke approval
Problem:
The first production-certificate smoke run failed immediately after submitting its two payload requests when the CI identity had intentionally been removed from the production approver list.
Root cause:
The request completion helper always called the SignPath Approve API as soon as a request entered WaitingForApproval, even in the explicitly human-reviewed smoke mode.
Fix:
Add an external-approval mode that polls without approving, enable it only for production signing smoke runs, and document and test the manual verification boundary. Normal canary and release automation retains CI approval.
Verification:
- bash scripts/release-workflows.test.sh
- actionlint v1.7.12 on release-desktop.yml
- cd desktop && go test . -run signing contract tests
- git diff --check
* fix(cli): isolate TUI diagnostics and recovery ownership
Problem:
Background structured logs and plugin stderr could overwrite the Bubble Tea composer. Recovery branches could also continue without the CLI session lease, and post-autosave metadata rewrites could be misclassified as same-revision conflicts.
Root cause:
The interactive CLI left the process logger and initial boot stderr attached to the terminal, did not wire OnSessionRecovered to its lease keeper, and classified some in-place session mutations separately from the mutation itself.
Fix:
Route interactive diagnostics through a private bounded log, propagate the diagnostic writer and recovery callback through every controller build, move the lease before recovery commits, and make persisted-history rewrites atomic.
Verification:
- go test -race ./internal/cli ./internal/control ./internal/agent ./internal/guardian
- env -u DEEPSEEK_API_KEY go test ./...
- go build ./cmd/reasonix
- Windows amd64 test-binary cross-compile
* fix(cli): preserve recovery lease across all runtimes
Problem:
The recovery lease callback was only wired into interactive chat. Headless run and serve sessions could move to a recovery branch while still guarding the original path, and serve controller rebuilds could publish a post-snapshot recovery path before its lease followed.
Root cause:
Session recovery ownership lived in a CLI-specific helper, serve attached its keeper only to explicit HTTP binding operations, and model switches rebound the pre-snapshot path after publishing the replacement controller.
Fix:
Move the recovery handoff onto SessionLeaseKeeper, install it for chat, run, serve, and rebuilt serve controllers, and bind the replacement controller's actual post-snapshot path before publishing it. Preserve the outgoing controller on binding failure and keep lease errors path-free.
Verification:
- go test -count=1 ./internal/control ./internal/cli ./internal/serve
- go test -race -count=1 ./internal/control ./internal/cli ./internal/serve
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- cd desktop && go test -count=1 ./...
- go vet ./...
- go build ./cmd/reasonix
* Fix plugin hook execution contracts
Problem: Windows plugin hooks with compound commands or nested quoting could still fail after the narrow quoted-batch compatibility fix, while explicit empty args could lose exec-form semantics across JSON round trips.
Root cause: Hook launch behavior was inferred from argument length and platform shell heuristics instead of preserving the manifest's exec-form or shell-form contract. CMD and PowerShell command strings also passed through incompatible quoting layers.
Fix: Preserve args field presence and shell selection through plugin parsing and loading, add explicit legacy/exec/shell execution modes, transport raw CMD and encoded PowerShell scripts without token re-rendering, retain the existing simple batch compatibility path, and document the cross-platform contract.
Verification: Focused Hook and plugin package tests, race and shuffled tests, fuzzing, root and Desktop full suites, go vet, and Windows/Linux cross-compilation.
* Fix Windows hook output regressions
Problem: The new compound CMD regression expected cmd.exe to strip quotes from %1, leaving the Windows job red. PowerShell hook scripts also preserved UTF-16LE input transport without forcing captured output to UTF-8.
Root cause: The test confused %1 with the dequoting %~1 form, and the Hook-specific EncodedCommand path did not reuse the shell runtime's existing PowerShell output-encoding prologue.
Fix: Align the CMD assertion with native batch semantics, share the PowerShell UTF-8 script wrapper across shell and Hook launchers, and exercise Chinese plus emoji output in the Windows runtime test.
Verification: Focused Hook/plugin/sandbox tests and vet; race/shuffle count 3; root and Desktop full suites; full vet; Windows amd64 test cross-compilation; PowerShell encoding fuzzing.
* feat(desktop): add native crash diagnostics
Problem:
Windows users could report that the desktop closed and would not reopen, but native process exits, UI-thread stalls, WebView2 failures, and window-restore hangs left too little durable evidence for analysis.
Root cause:
The desktop only persisted panics from explicitly wrapped goroutines in one overwrite-prone file. Startup tracking stopped distinguishing a healthy process that later vanished, and Windows native/WebView2/window-restore failure paths were not connected to the diagnostic pipeline.
Fix:
Capture Go runtime fatal output, retain a bounded backward-compatible report queue, heartbeat startup lifecycle state, and attribute abnormal exits to coarse uptime, install, and update buckets. Add a Windows Wails-window watchdog, WebView2 process-failure classification, and durable window-restore journaling. Extend aggregate metric ingestion and the health dashboard, while redacting panic values, secrets, and personal path segments and documenting the telemetry behavior.
Verification:
- cd desktop && go test ./...
- cd desktop && go test -race . -run focused crash queue tests
- go test -race ./internal/repair -run TestStartupTracker
- Windows amd64 cross-compile of the desktop test binary
- desktop frontend typecheck, focused settings tests, and production build
- crash-report worker: 42 tests and TypeScript typecheck
- git diff --check
* test(desktop): make hang assertions platform-aware
Problem: Windows desktop CI expected macOS crash labels and metric buckets even though the watchdog correctly emits Windows-specific diagnostics.\n\nFix: derive the expected label, error type, and metric bucket from the same platform identity helpers used by the implementation.\n\nVerification: targeted macOS tests pass; Windows amd64 test binary cross-compiles successfully.
* fix(desktop): remove crash watchdog stalls
Problem: the Windows UI watchdog allocated a permanent syscall callback on every one-second heartbeat, and the window-restore journal used a crash-safe fsync/retry path immediately before showing the window.
Root cause: Windows callbacks cannot be released and the Go runtime has a finite callback table; AtomicWriteFile can also wait on antivirus-held rename locks.
Fix: register one serialized EnumWindows callback for the process, keep the restore journal best-effort without fsync/retry, and add long-run Windows plus journal regression coverage.
Verification: go test ./... (desktop); targeted race tests; go vet ./...; Windows amd64 cross-compile; cache guard.
* fix(desktop): make crash diagnostics process-safe
Problem:
Window restore diagnostics recorded success after Wails only queued UI-thread work, and routine duplicate launches shared the primary process's fatal-output file.
Root cause:
The restore timeout watched the WindowShow call instead of native window state, while fatal output used one process-global path before Wails enforced its single-instance lock.
Fix:
Wait asynchronously for the Windows window to become visible and non-minimised, fence superseded restore attempts, and retain the journal until a real acknowledgement. Store fatal output and structured-panic markers per PID, process only dead owners, and keep backward-compatible legacy dump ingestion.
Verification:
- cd desktop && go test ./...
- cd desktop && go test -race .
- cd desktop && go vet ./...
- Windows amd64 desktop test cross-compile
- ./scripts/cache-guard.sh
- git diff --check
* fix(desktop): canonicalize runtime process identity
Problem: executable-based single-instance IDs let stable, canary, and portable builds concurrently write the same Reasonix home, while path aliases could split session lease identity.
Root cause: process ownership was keyed by binary location and fresh paths resolved only their immediate parent.
Fix: hash the canonical REASONIX_HOME and resolve every missing path component through the nearest existing symlink or junction ancestor, preserving isolated homes and dev-mode parallelism.
Verification: Go agent tests, Desktop tests, Windows cross-compilation, and canonical-path race tests pass.
* fix(desktop): unify writable session runtime ownership
Problem: Goal and Delivery mode rebuilds, duplicate tab restore, background reattachment, and saved-session navigation could build a second controller for one session or tear down the live source before a target was proven usable.
Root cause: controller startup, lease ownership, deferred retries, detached-session attachment, and path switching were coordinated per WorkspaceTab without a canonical process-wide admission record or one atomic controller/lease/path commit.
Fix: add a SessionRuntime registry keyed by runtime ID and canonical session key; attach or wait on existing local runtimes; safely reclaim only orphaned current-process leases; and make saved-session switches build, restore, validate, and lease a candidate before atomically publishing it. Target failures preserve the source controller, lease, path, mode profile, and epoch.
Verification: full Desktop and repository tests, Desktop/Agent/Controller/lease race suites, all 18 collaboration-token-mode orderings, deterministic A/B/A navigation fencing, frontend tests/build, Wails bindings, and a production Desktop build pass.
Co-authored-by: erphenheimer <[email protected]>
* fix(frontend): fence sends and events by runtime state
Problem: the composer could append an optimistic user bubble while a workspace was starting or lease-blocked, and late events from a replaced controller could mutate the new transcript.
Root cause: frontend readiness used only legacy booleans and controller events had no process-runtime generation fence.
Fix: expose structured runtime phase and issue metadata, gate every submit path before optimistic dispatch, seed and advance per-tab runtime epochs, and ignore events from obsolete epochs while retaining legacy event compatibility.
Verification: the full frontend suite, focused send-failure tests, TypeScript checking, and the production Vite build pass.
* docs: record desktop session ownership fix
Problem: the user-visible impact and compatibility boundary of the Desktop self-lock fix were not documented.
Fix: describe single-runtime session ownership, epoch-fenced events, readiness-gated sends, and REASONIX_HOME-scoped single-instance behavior in the Unreleased changelog.
Verification: Markdown diff inspection and repository privacy scan pass.
* test(desktop): align CI fixtures with runtime ownership
Problem: CI found an unused private runtime helper and a Linux timing path where a Heartbeat test injected Ctrl plus Ready without publishing the matching SessionRuntime state.
Fix: remove the unused helper and advance the injected test runtime exactly as the production controller publication path does; production readiness checks remain strict.
Verification: the Heartbeat submission and pending-prompt tests pass for 20 repetitions and for 5 race-enabled repetitions, and go vet passes.
* fix(desktop): wait for starting session runtimes
Problem: two persisted tabs restoring the same session could attach the first tab's starting placeholder before its controller was published, retire both candidate builds, and leave the visible session permanently starting.
Root cause: startup called the general attach path before claimSessionRuntime classified the registered phase, and applyRuntimeTab could create a second registry record when adopting a legacy runtime into a target that already owned a placeholder.
Fix: make claimSessionRuntime the startup admission entry, refuse attachments from non-ready runtime owners, reuse the target placeholder when adopting compatible visible or detached runtimes, and cover the starting-to-ready duplicate-tab handoff.
Verification: focused runtime tests passed for 10 repetitions; the runtime/mode/navigation race matrix passed for 3 repetitions; Desktop go test ./..., go test -race ., go vet ./..., go build ./..., and git diff --check passed.
---------
Co-authored-by: erphenheimer <[email protected]>
* 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
DeepSeek API rejects image_url content parts with HTTP 400
"unknown variant 'image_url', expected 'text'". When the DeepSeek
protocol is detected, convert image attachments to text references
("[Attached image: filename.png]") instead of image_url parts.
This prevents the 400 error while still informing the model that
images were attached. Tool-result images are also skipped for
DeepSeek since they would produce the same error.
Fixes#6682.
* fix(desktop): keep provider retries cancellable
Problem: During provider retry backoff, a stale idle tab snapshot could clear the frontend running state, hiding Stop until all retry attempts finished.
Root cause: The retrying event only updated retry counters even though it is authoritative evidence that the foreground turn remains active and cancellation-aware.
Fix: Restore running, turn-active, cancellable, and timing state when handling retrying events, with a regression that reproduces the stale snapshot ordering.
Verification: Focused controller progress, cancel reconciliation, and composer tests pass; frontend typecheck, CSS checks, and git diff checks pass.
* fix(desktop): fence retry state from stale snapshots
Record retry activity on the same monotonic clock used by runtime snapshot fetches. Ignore idle snapshots started before the latest retry so Stop and Escape cancellation stay available, while allowing a fresh idle snapshot to reconcile a missed turn_done and clear the retry indicator.
* fix(recovery): reduce Auto failure interruptions
Problem:
Auto injected a long guard message after ordinary tool and verification failures, then treated unrelated follow-up work as recovery that could reach the reviewer or a user confirmation. Recovery pause copy could also appear duplicated or in the wrong language.
Root cause:
The recovery gate treated an active failure as a task-wide boundary, did not distinguish transient timeouts, and allowed non-plan reviewer outcomes to escalate to human prompts. Localized clients also rendered the backend English fallback alongside localized copy.
Fix:
Classify failure evidence, isolate retries to the exact failed operation, keep unrelated and host-proven read-only work automatic, and reserve human decisions for structured plan changes. Add bounded timeout guidance, backward-compatible persistence, localized recovery_paused copy, and matching desktop, CLI, serve, and documentation coverage.
Verification:
- go test ./... -count=1 with DeepSeek credentials unset
- go test -race ./internal/recovery ./internal/control -count=1
- go vet ./internal/recovery ./internal/agent ./internal/control
- ./scripts/cache-guard.sh
- go test ./... -count=1 in desktop/
- pnpm typecheck and focused frontend recovery tests
* fix(recovery): enforce runtime retry boundaries
Problem:
Runtime-shaped recovery proposals always carried a preview, so their fingerprint did not match completed observations and the exact-operation failure limit was not enforced. Tool-owned MCP deadlines were also reported as cancellations and skipped transient recovery classification.
Root cause:
One fingerprint served both operation accounting and preview-bound approvals, despite those records having different shapes. The Agent adapter also inferred cancellation from the returned error cause instead of the parent turn context.
Fix:
Separate preview-free operation identity from preview-bound approval identity, and treat a deadline as cancellation only when the parent context ended. Add regressions for real proposal previews, internal MCP deadlines, and parent deadline preservation.
Verification:
- go test ./... -count=1 with DeepSeek credentials unset
- go test -race ./internal/recovery ./internal/agent ./internal/control -count=1
- go vet ./internal/recovery ./internal/agent ./internal/control ./internal/cli ./internal/serve
- ./scripts/cache-guard.sh
- go test ./... -count=1 in desktop/
- focused frontend recovery tests and pnpm typecheck
* docs(release): prepare v1.17.20 notes
Summary:
Add reviewed bilingual stable release notes for v1.17.20, covering the machine CLI interfaces, trusted MCP delegation, Debian updater bootstrap, recovery changes, and desktop reliability fixes.
Compatibility:
Document the one-time Debian package bootstrap, matching Desktop/Remote host requirement, machine identity initialization, and installed-MCP trust boundary without changing runtime behavior.
Verification:
- node scripts/release-notes.mjs validate
- rendered the v1.17.20 GitHub release notes successfully
- node --test scripts/release-notes.test.mjs
- bash scripts/release-workflows.test.sh
- git diff --check
* ci(release): reuse release-bound pull requests
Problem:
Preparing reviewed bilingual notes opened a dedicated pull request for every stable version even when an existing final product or release-infrastructure pull request already defined the candidate boundary.
Root cause:
The preparation workflow always selected a version-specific release-notes branch and had no validated way to update an existing review branch.
Fix:
Add an optional target_pr path that accepts only an open same-repository pull request into main-v2 containing the latest trunk. Reuse that branch, expose the rendered draft in the workflow summary, retain the dedicated pull request as a fail-closed fallback, and document the release-PR frequency rule.
Verification:
- bash scripts/release-workflows.test.sh
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- rendered the v1.17.20 release notes successfully
- git diff --check
* feat(cli): add redacted machine interfaces
* fix(cli): repair machine lifecycle projections
Problem:
Persisted running jobs could be resurrected after restart with a closed done channel and no cancel function, while task projections reported running subagents as finished and artifact-complete. Machine session IDs also exposed model-bearing transcript names.
Root cause:
Writing start metadata invalidated the loader's terminal-only assumption, and the machine projection treated update timestamps and file allocation as terminal completion.
Fix:
Normalize restored running jobs to an interrupted terminal state, keep in-flight artifacts incomplete, project stale state from the session lease, verify subagent artifact presence, hash machine session identities, reject conflicting event-output flags, and document the schema contract in English and Chinese.
Verification:
- go test ./internal/jobs ./internal/cli
- go test -race ./internal/jobs
- go test -race ./internal/cli -run machine-interface focused tests
- go vet ./internal/jobs ./internal/cli
- ./scripts/cache-guard.sh
- root test sweep passed all deterministic packages; the invalid-key live probe was isolated and its package passed with DEEPSEEK_API_KEY unset
* fix(jobs): preserve live tasks across runtime rebuilds
Problem:
A replacement jobs manager could rewrite a still-running background task as interrupted while the original controller continued executing it. CLI and serve runtime switches could trigger that overlap because they only guarded foreground turns.
Root cause:
Persisted running artifacts did not identify their live manager, so every loader assumed they belonged to a dead runtime. Runtime rebuild entry points also used inconsistent active-work checks.
Fix:
Add a private optional manager owner ID to job metadata, defer artifacts owned by a live in-process manager, and reload them after terminal completion. Normalize model, effort, skill, work-mode, and serve rebuild guards to reject foreground work, pending prompts, or background jobs.
Verification:
- go test ./...
- go test -race ./internal/jobs
- go test -race ./internal/cli ./internal/serve
- go vet ./internal/jobs ./internal/cli ./internal/serve ./internal/i18n
- ./scripts/cache-guard.sh
* fix(jobs): require session ownership for stale repair
Problem:
A manager loading persisted running job artifacts could rewrite them as interrupted while another Reasonix runtime still owned and wrote the same transcript. Bot and Remote Workbench sessions also did not retain transcript leases across their full lifecycle.
Root cause:
Process-local manager owner IDs cannot prove that a missing owner is dead in another process, and the artifact loader had no session-write ownership check. Bot and Remote create, restore, fork, rotate, and close paths did not consistently acquire, transfer, and release session leases.
Fix:
Inject a fail-closed session ownership probe into jobs managers, repair abandoned running artifacts only while the current process holds the transcript lease, and keep leases attached to Bot and Remote sessions until controller teardown. Add regression coverage for denied repair, lease transfer, concurrent restore, and close-time release.
Verification:
- go test ./internal/jobs ./internal/agent ./internal/control ./internal/bot ./internal/remote/workbench/runtime ./internal/boot
- go test -race ./internal/jobs ./internal/agent ./internal/bot ./internal/remote/workbench/runtime
- go vet ./internal/jobs ./internal/agent ./internal/control ./internal/bot ./internal/remote/workbench/runtime ./internal/boot
- go test ./... (all deterministic packages passed; the invalid local DeepSeek credential probe passed with DEEPSEEK_API_KEY unset)
- cd desktop && go test ./...
- ./scripts/cache-guard.sh
* test(remote): release session leases before cleanup
Problem:
Windows CI could not remove Remote runtime TempDir trees because two tests ended while the new session lease lock files were still open.
Root cause:
The tests created or rotated leased runtime sessions but never closed their Server instances before testing.TempDir cleanup. Unix permits unlinking open files, while Windows correctly rejected the cleanup.
Fix:
Register snapshotAndClose cleanup for the rotation fixture and broker integration test so controllers close and session leases release before temporary directories are removed.
Verification:
- go test ./internal/remote/workbench/runtime -run 'TestSessionRotationRegistryFailureReturnsCommittedEpoch|TestRuntimeControllerUsesDesktopBrokerWithoutHostKey' -count=1
- GOOS=windows GOARCH=amd64 go test -c -o /tmp/pr6859-runtime.test.exe ./internal/remote/workbench/runtime
- git diff --check
* fix(cli): preserve legacy run session IDs
Problem:
Existing json and stream-json automation received a hashed session_id, so the documented resume query could no longer resolve the value emitted by a run.
Root cause:
runAgent applied the new redacted machine-session transform before finalizing every structured output format instead of limiting it to events-jsonl.
Fix:
Select the final session ID by output format: preserve the established raw branch ID for text, json, and stream-json, and hash only events-jsonl. Add focused compatibility coverage for every format.
Verification:
- go test ./internal/cli -run 'TestRunOutput(SessionIDPreservesExistingFormats|JSONResult|EventsJSONLIsStructuredAndRedacted)' -count=1
- go test ./internal/cli ./internal/jobs ./internal/agent ./internal/bot ./internal/remote/workbench/runtime ./internal/serve ./internal/boot
- go test -race ./internal/cli ./internal/jobs
- merged with origin/main-v2: go test ./internal/cli ./internal/jobs
- merged with origin/main-v2: go test -race ./internal/cli
- merged with origin/main-v2: ./scripts/cache-guard.sh
- git diff --check
* fix(machine): fail closed on lifecycle repair errors
Problem:
- Session rotation could leave remote and bot controllers serving an unleased transcript after lease transfer failed.
- Machine event JSONL exposed provider-controlled tool identifiers and names.
- Failed interrupted-job metadata repair could publish an in-memory state that was not durable.
Root cause:
- Post-mutation lease errors and recovery write errors were treated as recoverable without restoring a consistent ownership or persistence boundary.
Fix:
- Retire rotated remote and bot sessions when the replacement lease cannot be acquired.
- Replace provider tool identities with stable per-run opaque aliases.
- Defer interrupted tombstones when metadata repair fails, emit a warning, and retry on the next bind.
- Add focused regression coverage for each failure mode.
Verification:
- go test ./internal/cli ./internal/jobs ./internal/bot ./internal/remote/workbench/runtime
- go test -race ./internal/cli ./internal/jobs ./internal/bot ./internal/remote/workbench/runtime
- go test ./internal/agent ./internal/control ./internal/serve ./internal/boot
- env -u DEEPSEEK_API_KEY go test ./...
- go vet ./...
- ./scripts/cache-guard.sh
- git diff --check
* fix(machine): harden IDs and preserve active runtimes
Problem:
- Runtime profile and bot routing switches could close controllers that still owned pending prompts or background jobs.
- Machine session IDs used an unkeyed hash of guessable transcript branch names, allowing offline enumeration.
Root cause:
- Lifecycle guards only covered foreground turn state and did not serialize bot admission with runtime replacement.
- The public ID derivation had no installation-secret entropy.
Fix:
- Gate Remote close/profile operations and Bot routing rebuilds on running turns, pending prompts, and background jobs, with a second pre-swap check and an admission-serialized Bot switch.
- Initialize a private 32-byte per-install key under the Reasonix state directory with a cross-process lock and atomic 0600 write, derive 128-bit HMAC session IDs, and fail closed on corrupt identity state.
- Load the key before events-jsonl provider execution, document first-use initialization, and add focused lifecycle, concurrency, and privacy regressions.
Verification:
- go test ./internal/cli ./internal/remote/workbench/runtime ./internal/bot -count=1
- go test -race ./internal/remote/workbench/runtime ./internal/bot ./internal/cli ./internal/jobs -count=1
- go vet ./...
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- cd desktop && go test ./... -count=1
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* fix(machine): bind lifecycle repair to live ownership
Problem:
Startup subagent cleanup could interrupt work owned by another process, repair authorization survived lease handoff windows, and hook machine output labeled unusable entries active.
Root cause:
Subagent cleanup rewrote a shared directory without parent lease ownership, one registry mixed pending and active lease generations, and hook status did not reuse actual loader and matcher semantics.
Fix:
Acquire each parent transcript lease before stale subagent repair, publish only active lease generations and revoke them before unlock, and classify hook executability with shared event matcher semantics. Add deterministic cross-process, controlled handoff, invalid-hook, and compatibility regressions.
Verification:
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- env -u DEEPSEEK_API_KEY go test -race ./internal/agent ./internal/jobs ./internal/bot ./internal/remote/workbench/runtime ./internal/cli ./internal/serve -count=1
- cd desktop && env -u DEEPSEEK_API_KEY go test ./... -count=1
- go vet ./...
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* fix(machine): verify projected runtime state
Problem:
- task JSON could report missing or failed artifacts as complete
- plugin context paths that only existed could appear executable
- session help described opaque machine IDs as branch IDs
Root cause:
- persisted metadata and filesystem readiness were projected separately
- hook status and diagnostics used existence-only checks
- localized usage text retained an earlier identifier name
Fix:
- fail closed unless a terminal job has a readable regular persisted artifact without an artifact error
- share plugin context usability checks across machine status and diagnostics
- correct machine session ID help in all supported locales and add regressions
Verification:
- go test ./...
- go test -race ./internal/agent ./internal/jobs ./internal/bot ./internal/remote/workbench/runtime ./internal/cli ./internal/serve
- go test ./... (desktop)
- go vet ./...
- ./scripts/cache-guard.sh
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* feat(agent): trust installed MCP in Planner and task sub-agents via stable use_capability
Problem:
The dual-model Planner and task/fleet children could not reliably query installed
MCP servers (e.g. databases) without readOnlyHint or dynamic mcp__ schemas, so
plans and delegated research were often ungrounded. Issue #6854.
Root cause:
Planner and strict/ordinary child registries either excluded MCP or required
readOnlyHint, and MCP tools entered provider-visible schemas so connect churn
broke prompt cache. Delivery alone had use_capability.
Fix:
- NewPlannerAgent with PlannerMCPExecution: authorized non-destructive MCP via
use_capability without readOnlyHint; destructive left for Executor.
- Session-shared MCPCapabilityRuntime (Host/specs/connections) with per-agent
use_capability frontends (isolated ledger/audit).
- Ordinary task/fleet children get full proxy (list/inspect/call); destructive
follows normal writer permissions.
- Strict read-only children share proxy schema/reuse but keep authorized +
readOnlyHint + non-destructive execution.
- Strip direct mcp__* from Planner and sub-agent registries; convert profile
MCP allowlists to capability IDs; action=list for discovery.
- NonDestructiveMCPExecutionIntent re-check before tools/call; Desktop MCP
tool card + docs.
Verification:
- go test ./internal/agent ./internal/boot ./internal/control ./internal/plugin ./internal/capability
- ./scripts/cache-guard.sh
- pnpm --dir desktop/frontend typecheck
- git diff --check
Fixes#6854
* fix(agent): close trusted MCP auth, connect gate, and review proxy gaps
Problem:
PR #6865 left three P1 boundaries incomplete: deferred MCP targets treated
!RequireLaunchApproval as authorized (bypassing Spec.Authorized), authorized
mcp_connect__* still entered the ordinary permission Gate, and review/read_only_skill
children never received the shared capability runtime or use_capability allowlist.
Root cause:
Connect helpers invented trust instead of using ServerAuthorized(); lifecycle
connect targets were excluded from the installed-MCP fast path; boot skill
runners still called registry builders without capRuntime and builtin review
AllowedTools omitted use_capability.
Fix:
- Deferred MCP targets and ensureServerTools use only Spec.ServerAuthorized().
- Authorized MCP tools and lifecycle connects share the explicit-deny-only gate.
- Wire capRuntime into read_only_skill and subagent skill runners; add
use_capability to builtin review/security-review allowlists.
- Regression tests for zero-start unauthorized non-project MCP and trusted
first connect under deny-all ordinary gates.
Verification:
- go test ./internal/agent ./internal/skill ./internal/plugin ./internal/boot
* fix(agent): stabilize restricted proxy prefix and isolate planner ledger
Problem:
Restricted use_capability proxies embedded dynamic capability IDs in their
Description, so MCP install/tool-list changes altered provider-visible tool
bytes (cache churn). Delivery dual-model still shared Executor use_capability
and ledger with the Planner. Restricted action=list leaked every configured
server. An empty if in subagent_registry_test triggered staticcheck SA4006.
Root cause:
Allowlist text was baked into tool descriptions; PlannerToolRegistry copied
parent use_capability before the clone branch; attachSubagentCapabilityProxy
returned early when a parent proxy was already present; list delegated to the
unfiltered frontend.
Fix:
- Keep restricted proxy description/schema identical to the base proxy; enforce
allowlists only in host checks and filtered list results.
- Always strip and reinstall an isolated use_capability frontend for sub-agents
and the Planner; give the Planner its own ledger and audit in boot.
- Filter list output to allowlisted servers only.
- Assert FilterReadOnlyRegistry keeps authorized RO MCP; add stability and
isolation regression tests.
Verification:
- go test ./internal/agent ./internal/boot ./internal/skill
- git diff --check
* fix(agent): fail-closed restricted MCP list filtering
Problem:
Malformed allowed-tools entries such as "mcp-server:" produced a non-empty
capability allowlist but an empty server set, so filterCapabilityListResult
returned the unfiltered inventory. Unreadable list payloads also passed through.
Root cause:
Incomplete capability IDs were accepted literally, and empty-server / parse
errors used fail-open returns.
Fix:
- Accept only complete mcp-server:<name> and mcp-tool:<server>/<tool> IDs.
- Skip installing a restricted proxy when no servers resolve from the allowlist.
- Empty server sets and JSON parse/encode failures return an empty servers list.
- Regression tests for malformed IDs, empty servers, and bad list payloads.
Verification:
- go test ./internal/agent -run 'TestRestricted|TestMalformed|TestFilterCapability|TestPlannerTool|TestCustomProfile'
* fix(ui): show resolved MCP capability effects
Problem:
The stable use_capability proxy made Desktop and CLI present MCP calls only by the proxy surface. Writer MCP calls could be grouped as quiet research, and terminal users saw the implementation-level proxy name instead of the selected capability.
Root cause:
Reasonix resolved the real MCP target only inside execution. That target name, capability ID, and read-only classification were not carried through durable session history, frontend events, or Remote history projections.
Fix:
- Persist optional resolved target metadata while keeping the provider-visible tool call unchanged.
- Emit deterministic same-ID dispatch refreshes after MCP resolution and carry the same classification on tool results.
- Classify Desktop MCP cards by the resolved effect, fail closed for legacy history, and preserve the metadata across Local and Remote reloads.
- Render use_capability as MCP(capability_id) in the CLI TUI and headless output.
- Add concurrency, serialization, history, protocol, Desktop, and CLI regression coverage.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- cd desktop && go test ./...
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend test
- go test -race ./internal/agent -run 'TestResolvedCapabilityDispatchRefreshesWriterClassification|TestResolvedCapabilityRefreshesParallelCallsInProviderOrder|TestUpdateToolCallResolutionPersistsAfterMidTurnSnapshot'
- ./scripts/cache-guard.sh
- go run ./cmd/remote-protocol-gen -check -root .
- git diff --check
* fix(agent): close trusted MCP proxy safety gaps
Problem:
Restricted sub-agent MCP allowlists could reveal sibling tool schemas, proxy-resolved writers could execute concurrently, and Balanced Executors could not directly call capability IDs discovered by their Planner.
Root cause:
Tool grants were widened to server IDs, batching trusted the proxy's read-only surface before resolving the real target, and dual-model Balanced installed use_capability only on the Planner registry.
Fix:
Keep tool grants exact and filter concrete tool inspection, serialize use_capability for every agent, and install isolated stable proxy frontends on both Balanced Planner and Executor while sharing the MCP runtime. Update bilingual contracts and add focused regressions.
Verification:
- env -u DEEPSEEK_API_KEY go test ./...
- go test -race ./internal/agent
- go vet ./...
- ./scripts/cache-guard.sh
- git diff --check
* fix: enforce trusted MCP runtime boundaries
Problem:
Path-scoped sub-agents could resolve use_capability into unscoped MCP writers, and the stable MCP proxy kept boot-time specs after add, update, disable, or removal.
Root cause:
use_capability was classified as statically read-only before dynamic resolution, while MCPCapabilityRuntime and capability routing copied immutable boot snapshots that were not synchronized with controller lifecycle changes.
Fix:
Keep the provider-visible proxy contract stable while restricting explicit write_paths tasks to proven read-only MCP targets. Make each controller runtime an authoritative, race-safe MCP inventory, synchronize CLI/Desktop lifecycle changes and shared-host tabs, clear stale live tools, and route from live runtime snapshots.
Verification:
go test ./internal/agent ./internal/boot ./internal/control ./internal/plugin
go test -race ./internal/agent ./internal/control
env -u DEEPSEEK_API_KEY go test ./...
cd desktop && go test ./...
go vet ./...
./scripts/cache-guard.sh
git diff --check
* fix(agent): bind trusted MCP dispatch to live runtime identity
Problem:
Planner and stable capability frontends could reuse a same-name MCP client from another controller or stale configuration. Plan mode also blocked authorized opaque non-destructive MCP, lifecycle snapshots could mix generations, and legacy Desktop history lost built-in reader classification.
Root cause:
Shared Host reuse checked only the server name, resolved targets were not bound to the controller's current enabled/authorized spec, lifecycle mutations were not linearized with tools/call, and routing read configuration and live tools in separate snapshots. Documentation also described ordinary sub-agent MCP as using normal writer permission prompts even though installation is the trust boundary.
Fix:
- Compare complete host-local MCP runtime identity and require exact identity for Host/tool reuse.
- Bind resolved MCP targets to the live runtime and re-check enablement, authorization, identity, and destructive safety before dispatch.
- Linearize connect/call against update, disable, and removal; return atomic catalog/live-tool snapshots.
- Preserve Planner Plan-mode access to authorized non-destructive MCP without readOnlyHint and safely complete connected server-directory calls.
- Restore legacy Desktop reader classification and align bilingual contracts with explicit-deny-only installed MCP trust and direct-Executor cache limits.
- Add identity, shared-Host, real opaque Planner, revocation, concurrency, and frontend regressions.
Verification:
- go test ./internal/agent ./internal/plugin ./internal/control ./internal/boot -count=1
- go test ./internal/agent -run 'TestPlannerPlanModeExecutesAuthorizedOpaqueMCPThroughRuntime|TestRuntimeDisableLinearizesWithInFlightMCPDispatch|TestMCPCapabilityRuntimeConcurrentUpdatesAndSnapshots' -count=1
- go test ./internal/plugin -run TestMCPRuntimeSpecMatchesExactHostIdentity -count=1
- ./scripts/cache-guard.sh
- pnpm --dir desktop/frontend exec tsx src/__tests__/tool-subject.test.ts
- pnpm --dir desktop/frontend typecheck
- git diff --check
* fix(agent): remove obsolete MCP runtime wrapper
Problem:
The latest PR head failed golangci-lint because ensureServerTools remained as an unused private wrapper after every dispatch path moved to the identity-bound ensureServerToolsForSpec helper.
Root cause:
The runtime-identity refactor replaced all callers but left the compatibility wrapper behind. Go compilation and vet allow unused methods, while CI's unused analyzer correctly rejected it.
Fix:
Delete the dead wrapper so all connect paths continue to require the already-authorized current runtime spec explicitly.
Verification:
- go test ./internal/agent -count=1
- go vet ./...
- go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m
- git diff --check
* fix(agent): preserve MCP images and route IDs
Problem:
The first use_capability call to an unconnected MCP server dropped structured image results, and Planner transient routing rewrote concrete MCP capability IDs to source targets that use_capability cannot call.
Root cause:
onDemandMCPTool exposed only the text Execute interface, so Agent dispatch could not select ExecuteWithImages. RenderTransientBlock applied legacy source rewriting before accounting for Planner MCP proxy routing.
Fix:
Give the deferred MCP target a shared image-aware execution path that retains the runtime authorization lock through tools/call. Keep concrete MCP IDs for capability-proxy routes while preserving ordinary source routing for non-MCP capabilities. Add full Agent-path image coverage and focused route rendering regressions.
Verification:
- go test ./internal/agent ./internal/capability ./internal/control ./internal/boot
- env -u DEEPSEEK_API_KEY go test ./...
- go test -race ./internal/agent -count=1
- go vet ./...
- ./scripts/cache-guard.sh
- git diff --check
* fix(desktop): stabilize rich composer caret after invocation tags
Problem:
After selecting a skill or plugin invocation, mid-text edits in the desktop
rich composer often jumped the caret to the end of the draft on the first
keystroke, especially on Windows WebView2.
Root cause:
DOM→model counted user text that landed inside the NBSP caret anchor, but
model→DOM selection restore skipped the entire anchor. When selection was
temporarily unavailable, the code also faked the caret at text.length.
Fix:
Unify DOM walk rules for model reads, selection reads, and selection restore
so invocation chips stay zero-length while anchor body text remains editable.
Restore both selection ends, keep afterInvocationId behavior, and recover the
insertion point from a beforeinput snapshot when the browser drops selection.
Add focused rich-composer selection regressions and an Unreleased note.
Verification:
- pnpm --dir desktop/frontend exec tsx src/__tests__/rich-composer-selection.test.tsx
- pnpm --dir desktop/frontend exec tsx src/__tests__/composer-goal-toggle.test.tsx
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend build
* fix(desktop): recover rich caret after IME blackout and data-null repeats
Problem:
Two caret-recovery edges remained after the initial #6868 fix. IME
compositionend could restore the pre-composition offset when WebView2 dropped
selection, and data-null inserts of a repeated character (e.g. "aaa" → "aaaa"
at offset 1) recovered to the end via maximal prefix/suffix matching.
Root cause:
compositionstart never froze an edit snapshot while composing beforeinput was
skipped, so compositionend had no mid-edit baseline. recoverSelectionAfterEdit
also trusted a longest common prefix that claims the whole string when the
inserted run matches surrounding characters.
Fix:
Snapshot model text and caret on compositionstart; keep that baseline through
composition. Reconstruct post-edit carets with a selection-anchored head/middle/
tail splice before falling back to prefix/suffix, and prefer that recovery when
a live selection is parked at the end after a mid-text edit. Cover both edges
in rich-composer-selection tests.
Verification:
- pnpm --dir desktop/frontend exec tsx src/__tests__/rich-composer-selection.test.tsx
- pnpm --dir desktop/frontend exec tsx src/__tests__/composer-goal-toggle.test.tsx
- pnpm --dir desktop/frontend typecheck
* fix(desktop): preserve late WebView2 IME commits
Problem:
Windows WebView2 can dispatch compositionend before the committed IME DOM and final non-composing input are visible. Synchronizing the controlled model at compositionend publishes the stale pre-composition text and erases the selected candidate.
Root cause:
The composer assumed every Chromium host exposed the committed DOM by compositionend. Deferring every compositionend avoided the WebView2 blackout but regressed browsers and submit paths that require the committed text synchronously.
Fix:
Keep the pre-composition snapshot, synchronize immediately when compositionend already exposes changed DOM, and defer only while the DOM still matches the snapshot. Let the following non-composing input own late WebView2 commits, with a next-frame fallback and cleanup cancellation. Cover both event orders explicitly.
Verification:
- rich-composer-selection: 80 passed
- composer-goal-toggle: 171 passed
- pnpm --dir desktop/frontend test
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend build
- git diff --check
* fix(desktop): sync provider model state
* fix(desktop): preserve provider state boundaries
Problem: The source PR made per-session model switches rewrite the global default model and silently disabled existing provider balance endpoints.
Root cause: Session-local model state and user-global defaults were treated as one setting, while the stale-balance race fix was coupled to an unrelated provider capability restriction.
Fix: Keep hot model switches tab-local, retain existing balance_url behavior, and add regression coverage proving the global default and sibling tabs remain unchanged.
Verification: go test ./... (desktop); pnpm test; pnpm build; focused config/frontend tests; ./scripts/cache-guard.sh; git diff --check.
Co-authored-by: par73e <[email protected]>
* fix(desktop): serialize provider model switches
Problem:
Rapid model selections could let an older completion repopulate stale wallet state, while no-op or failed switches discarded the last known balance.
Root cause:
Balance request generations did not order SetModelForTab calls or distinguish stale model-switch completions. The current model also remained actionable without accounting for an in-flight switch.
Fix:
Serialize model switches per tab, track switch ownership and successful backend transitions, restore cached balance only when safe, ignore failed balance confirmations, make settled current-model selection a no-op, and invalidate provider state when tabs are discarded.
Verification:
- pnpm test
- pnpm build
- go test ./...
- go test -race . -run TestSetModelForTab -count=1
- ./scripts/cache-guard.sh
* fix(desktop): reconcile provider state across races
Problem:
Queued model fallback, cross-tab pending picks, and failed tab closes could leave provider UI state stale or trigger unnecessary switches.
Root cause:
Failure reconciliation omitted model metadata, pending pick ownership was component-global, and close invalidation ran before backend success.
Fix:
Refresh metadata after an older queued success, scope pending picks per tab, and invalidate provider generations only after a successful close. Add regression coverage for all three overlaps.
Verification:
pnpm test
pnpm build
pnpm exec tsc --noEmit -p tsconfig.test.json
go test -race . -run TestSetModelForTab -count=1
* Fix failed model switch reconciliation
Problem:
Rapid queued model switches could lose the pre-switch balance when every rebuild failed, and a failed selection could remain optimistically marked as current in the model picker.
Root cause:
Each queued attempt captured the already-cleared balance independently, while the picker did not receive or sequence the controller switch outcome.
Fix:
Keep one fallback balance for each per-tab switch queue, return an explicit switch result through App and Composer, and roll back plus reload the catalog only for the latest failed pick. Invalidate pre-click catalog requests so stale results cannot overwrite a retry.
Verification:
- pnpm test:all
- pnpm build
- go test ./...
- go vet ./...
- cd desktop && go test ./...
- cd desktop && go test -race ./...
- ./scripts/cache-guard.sh
- git diff --check
---------
Co-authored-by: par73e <[email protected]>
Co-authored-by: par73e <[email protected]>
* fix(desktop): support authorized Debian package updates
Problem:
Ubuntu users who installed the desktop .deb could not update in app because the updater could not replace root-owned system files.
Root cause:
Linux installs were treated uniformly as writable portable layouts, and the release manifest exposed only the tarball updater artifact.
Fix:
Detect dpkg-owned installs, publish signed native .deb metadata, and install upgrades through a packaged Polkit helper that re-verifies ownership, signatures, package identity, architecture, and version before delegating to apt. Preserve the portable tarball path and add user-facing progress, metrics, tests, and recovery documentation.
Verification:
- cd desktop && go test ./...
- cd desktop && go vet ./...
- pnpm --dir desktop/frontend test:typecheck
- pnpm --dir desktop/frontend test:updater
- pnpm --dir workers/crash-report typecheck
- pnpm --dir workers/crash-report test
- Linux amd64 helper test binary cross-compilation
- git diff --check
* fix(desktop): recover from updater mode changes
Problem:
The desktop CI failed on two unused updater helpers, and the UI could remain stuck in downloading or authorizing when install mode or manifest availability changed between updater steps.
Root cause:
The backend opened the manual download page and returned nil after reclassification, while the frontend treated nil as an in-flight operation with no terminal state. Two obsolete helper functions also remained after the refactor.
Fix:
Return and emit a typed manual-update error before opening the download page, preserve the manual fallback action in the shared updater state, add authorization/install/cancellation/reclassification coverage, and remove the unused functions.
Verification:
- cd desktop && go test ./...
- cd desktop && go vet ./...
- pnpm --dir desktop/frontend test:typecheck
- pnpm --dir desktop/frontend test:updater
- pnpm --dir desktop/frontend build
- pnpm --dir workers/crash-report typecheck
- pnpm --dir workers/crash-report test
- Linux amd64 update-helper test binary and production build
- git diff --check
* Fix Linux package smoke checks under pipefail
Problem:
Linux .deb contract smoke checks could fail on valid packages when the producer emitted more data after grep found its match.
Root cause:
The build script combined set -o pipefail with grep -q. Early grep exit could close the pipe and make dpkg-deb terminate with SIGPIPE, turning a successful contract match into a failed pipeline.
Fix:
Consume the complete dpkg-deb output with non-quiet grep redirected to /dev/null, and add a packaging guard that rejects the unsafe early-exit patterns.
Verification:
- cd desktop && go test ./...
- cd desktop && go test -run TestDesktopPackagesPreserveNativePlatformLaunchers -count=1 .
- pnpm --dir desktop/frontend test:updater
- bash -n scripts/desktop-build.sh
- git diff --check
* fix(auto): scope recovery failures to exact operations
Problem:
Auto Guard treated consecutive execution failures and reviewer rejections as task-wide state. After repeated test failures, unrelated workspace writes could remain blocked, and revising the proposal or starting a new ordinary turn did not reliably clear the latch.
Root cause:
Failure and reviewer counters were keyed only by the root task. Recovery observations carried no turn scope, revise kept the active failure, and the display surfaced only a generic Auto Guard error.
Fix:
Scope failure budgets to the exact operation and recovery scope, rotate episodes for different operations and ordinary turns, clear them on revise or when leaving Auto, retain stable Goal scopes across recovery persistence, scope reviewer rejection budgets to each proposal, and surface actionable blocker text. Update the English and Chinese approval-mode documentation and add compatibility coverage.
Verification:
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- go test -race ./internal/recovery ./internal/control -count=1
- cd desktop && go test . -run 'Recovery|AutoGuard' -count=1
- ./scripts/cache-guard.sh
- go vet ./internal/recovery ./internal/agent ./internal/control
* fix(plan): add explicit non-executing exit
Problem:
Plan approval offered execution or revision, but no direct way to leave Plan without running the proposal. Rejecting the prompt kept Plan active, so desktop and CLI users could become stuck in the planning workflow.
Root cause:
The desktop already had an exit callback but did not expose it as a decision. The CLI represented Plan as a two-choice boolean approval and only cleared Plan after approval.
Fix:
Add a third exit-without-executing action on desktop and CLI, switch both UI and controller state out of Plan before rejecting that action, preserve revise and n/Esc keep-planning behavior, localize all supported languages, update docs, and add click, keyboard, duplicate-submit, banner, and state-sync coverage.
Verification:
- pnpm test
- pnpm test:typecheck
- pnpm typecheck
- pnpm check:css
- pnpm build
- env -u DEEPSEEK_API_KEY go test ./... -count=1
- go vet ./internal/cli ./internal/i18n
- git diff --check
* docs(plan): clarify cross-client shortcuts
Problem:
The approval guide described Desktop click behavior and Esc handling as if they applied identically to the CLI, leaving the new CLI Plan decision ambiguous.
Root cause:
The Plan action documentation was written before the CLI received its explicit three-action workflow.
Fix:
Document Desktop and CLI activation separately, state that CLI n/Esc keeps planning during a pending Plan confirmation, and scope the generic Esc stop behavior accordingly in English and Chinese.
Verification:
- git diff --check
* test(plan): pin localized three-action contract
Problem:
The CLI Plan workflow depends on three numbered actions in every supported locale, but tests only checked the English labels and non-empty catalog fields.
Root cause:
Localization completeness did not assert the semantic action count or the execute, revise, and exit-without-executing labels.
Fix:
Add a catalog-level regression test that requires exactly three numbered Plan actions and the expected semantic labels in English, Simplified Chinese, and Traditional Chinese.
Verification:
- go test ./internal/i18n -count=1
- git diff --check
* fix(auto): bound recovery by execution episode
Problem:
Auto recovery could still feel like a sticky lock: repeated Maven/test failures
blocked progress, different parameter retries could avoid the same-operation
cap, and restart or mode hydration risked re-arming temporary failure state.
Users needed a low-friction path where this turn auto-recovers with a hard
ceiling, then the next turn recovers automatically.
Root cause:
Failure and reviewer budgets mixed TaskScope with temporary execution state,
kept a single active failure rather than Episode-scoped counters, re-armed
locks from sidecar snapshots, and lacked a generation barrier for late tool
results after mode switches. There was no turn-level stop with a summarize-only
finalization path or a wire-compatible recovery_paused outcome.
Fix:
- Separate host-owned EpisodeID (failure/reviewer/stop budgets) from TaskScopeID
(Goal and explicit task grants).
- Fixed budgets with no settings: 3 identical operation failures, 6 Episode
execution failures since last real progress, 3 cumulative reviewer rejects,
3 re-submissions of stopped operations.
- Successful mutation/verification clears no-progress budgets; diagnostic reads
do not. Provider/cancel/user-reject/pre-exec blocks stay out of the budget.
- Gate generation invalidates stale observations; real mode changes rotate
Episode and dismiss recovery waiters without approving mutations. Same-value
mode replays are no-ops.
- Episode stop blocks remaining tools, offers one finalization round, then
returns RecoveryPauseError with turn_done.outcome=recovery_paused (info UI,
not send-failed).
- Persistence writes last_failure evidence only; restore never re-arms locks.
- Content-free metrics for stops, mode resets, and stale observations.
Verification:
- go test ./internal/recovery ./internal/agent ./internal/control ./internal/eventwire ./internal/cli -count=1
- go test -race ./internal/recovery ./internal/agent ./internal/control -count=1
- pnpm exec tsx desktop/frontend/src/__tests__/send-failed.test.ts
- ./scripts/cache-guard.sh
- go vet ./internal/recovery ./internal/agent ./internal/control ./internal/event
- git diff --check
* style(auto): format episode recovery changes
Problem:
The Episode recovery commit left three root-module Go files outside gofmt canonical formatting, causing the macOS and Ubuntu CI jobs to stop before vet, build, and tests.
Root cause:
The final Episode changes were committed without applying gofmt to all touched Go files.
Fix:
Run gofmt on the three files reported by the same command used in CI. The resulting diff is whitespace-only.
Verification:
- gofmt -l . excluding the independent desktop module returns no files
- git diff -w --exit-code
- git diff --check
* fix(auto): close Episode budget bypasses and lint gaps
Problem:
Episode hard limits could still be bypassed three ways: a successful
recovery summary returned nil so Goal auto-continue opened another Run
with a fresh finalization round; total failure and reviewer budgets were
per TaskID so each new sub-agent got a full ceiling; and a reviewer
Continue cleared cumulative rejects before any real mutation progress.
golangci-lint also failed on empty branches and unused helpers.
Root cause:
Finalization success treated the pause as a normal turn end; Episode
totals lived on taskRuntime; reviewRejects reset on Continue; dead
helpers and empty branches remained from the Episode refactor.
Fix:
- Return RecoveryPauseError after a successful summarize-only finalization
so turn_done reports recovery_paused and Goal auto-continue stops.
- Keep exact-operation counters task-scoped; move Episode totals,
reviewer rejects, stopped-op retries, hard stop, and finalization to a
Gate-level episode budget shared by root and all sub-agents.
- Clear reviewer rejects only on real mutation/verification success, a
new Episode, or revise — not on reviewer Continue or human continue.
- Remove unused helpers/empty branches and add regressions for shared
sub-agent budgets and reject→continue→reject cumulative counting.
- Block Goal on RecoveryPauseError like FinalReadinessError.
Verification:
- go test ./internal/recovery ./internal/agent ./internal/control -count=1
- go test -race ./internal/recovery ./internal/agent ./internal/control -count=1
- go vet ./internal/recovery ./internal/agent ./internal/control
- gofmt clean on touched packages
- git diff --check
* fix(auto): keep Goal running after recovery pause
Problem:
RecoveryPauseError set the Goal to blocked. The next ordinary user message
opened a fresh Episode but lost the active Goal prompt and delivery scope, so
replying continue did not actually resume Goal work without an explicit resume.
Root cause:
goalShouldBlockOnError treated RecoveryPauseError like FinalReadinessError and
called stopGoal(GoalStatusBlocked). Only running Goals reinject model context
and delivery scope.
Fix:
Keep the Goal running after RecoveryPauseError so auto-continue stops but the
next real user turn reuses the same Goal and delivery scope. Reserve
GoalStatusBlocked for FinalReadinessError. Add a regression covering pause then
ordinary continue.
Verification:
- go test ./internal/control -run 'RecoveryPause|GoalReadiness' -count=1
- go test ./internal/control -count=1
* fix(recovery): present paused turns consistently
Problem:
Recovery pauses were informational in the Desktop React chat, but the Desktop host, bot bridge, Serve WebUI, TUI, metrics, and headless CLI still treated them as failures.
Root cause:
Those consumers classified turn completion from a non-nil error alone and ignored the recovery_paused outcome. The headless CLI also had separate result and exit-code classification paths.
Fix:
Honor recovery_paused across every client surface, keep ordinary errors unchanged, and centralize headless completion classification so notifications, JSON output, user messaging, and exit codes agree.
Verification:
go test ./internal/cli ./internal/serve ./internal/control ./internal/eventwire ./internal/agent ./internal/recovery -count=1
go test -race ./internal/cli ./internal/control -count=1
cd desktop && go test ./... -count=1
cd desktop && go vet ./...
pnpm exec tsx src/__tests__/send-failed.test.ts
./scripts/cache-guard.sh
go vet ./internal/cli ./internal/serve ./internal/control ./internal/event ./internal/eventwire
git diff --check
* feat(desktop): expand highlight.js language set to 52 languages
Register 26 additional languages (from 26 to 52) covering common
ecosystems: lua, dart, groovy, objectivec, r, perl, fsharp, haskell,
elixir, clojure, erlang, julia, scss, less, latex, vim, nginx, apache,
protobuf, graphql, cmake, gradle, properties, coffeescript, matlab,
and vbnet.
Map XAML (.xaml) to XML highlighting since highlight.js has no native
XAML lexer and XAML is an XML dialect.
Extend EXT, ALIASES (lang.ts) and byExt (selectedTextContext.ts) tables
with corresponding file-extension and fence-tag aliases so all three
code-preview paths — Markdown fenced blocks, tool diffs, and the
workspace file viewer — resolve the correct language.
Closes#6825 (desktop portion).
* fix(desktop): correct jsx/tsx languageFor mappings to registered hljs names
languageFor() in selectedTextContext.ts mapped .jsx and .tsx to "jsx" and
"tsx", but highlight.js does not register languages under those names --
the actual registered names are "javascript" and "typescript". While the
immediate consumer (formatSelectionReference) works correctly because the
downstream highlightToHtml() passes through resolveLang() which handles
these aliases, the mapping is technically wrong and would break for any
future consumer that uses the languageFor return value directly without
alias resolution.
Fix: map jsx to "javascript" and tsx to "typescript" to match the
highlight.js registration names, making the function self-consistent.
* fix(desktop): unify syntax highlight language mapping
Problem:
- File previews and tool diffs could disagree for special filenames such as Dockerfile, Makefile, CMakeLists.txt, and .htaccess.
- Plan-revision fences replaced semantic tags such as TSX, HTML, TOML, and XAML with fallback lexer names.
Root cause:
- Path-to-language behavior was duplicated across independent tables, and extension-only matching could not recognize extensionless build and server configuration files.
Fix:
- Centralize semantic path mapping for both slash styles, extensions, and special basenames.
- Resolve semantic tags to highlight.js lexers only at the rendering boundary.
- Add regression coverage for mapped lexers, special filenames, UI consistency, and provider-visible fence tags.
Verification:
- pnpm test:typecheck
- pnpm test
- pnpm build
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* test(desktop): cover remaining special language paths
Problem:
- The shared path resolver supported httpd.conf and .makefile suffixes, but the focused path matrix did not exercise those branches directly.
Root cause:
- The initial regression matrix covered the lexer set and primary special basenames without one concrete path for each remaining matcher.
Fix:
- Add end-to-end path, tool-diff, file-preview, and lexer assertions for httpd.conf and module.makefile.
Verification:
- pnpm exec tsx src/__tests__/language-highlighting.test.ts
- pnpm test:typecheck
- git diff --check
* fix(desktop): cap chat syntax highlighting
Problem:
- Ordinary chat and tool code blocks always ran highlight.js, even when a block exceeded the workspace preview's 512 KiB or 20,000-line safety budget.
Root cause:
- The highlighting budget lived inside the line-numbered workspace viewer instead of the shared highlighting layer.
Fix:
- Move the byte and line limits into the shared highlighter.
- Measure ordinary blocks using UTF-8 bytes without allocating a duplicate buffer.
- Fall back to escaped plain text above either limit while preserving content and copy behavior.
- Reuse the same helper from workspace and ordinary code viewers.
Verification:
- pnpm exec tsx src/__tests__/line-number-code.test.tsx
- pnpm exec tsx src/__tests__/language-highlighting.test.ts
- pnpm test:typecheck
- pnpm test
- pnpm build
- ./scripts/cache-guard.sh
- git diff --check
Co-authored-by: SivanCola <[email protected]>
---------
Co-authored-by: SivanCola <[email protected]>
* fix: plan mode focus stealing - stabilize onRevisionActiveChange and avoid unnecessary re-renders
* fix(desktop): preserve transcript selection during plan revision
Problem:
The open plan-revision textarea reclaimed focus whenever its parent rerendered, clearing transcript text selections before users could copy them.
Root cause:
The focus effect depended on a callback prop whose identity could change during periodic App refreshes, so every callback change called focus again. The original patch also relied on suppressing tab metadata rerenders instead of making the approval owner resilient.
Fix:
Keep the App callback stable, focus the revision textarea only when revisionOpen changes, track the latest callback for close and unmount cleanup, remove the unrelated JSON.stringify polling dedupe, and add a regression that churns the callback while a transcript selection is active.
Verification:
- pnpm test
- pnpm test:typecheck
- pnpm typecheck
- ./scripts/cache-guard.sh
- Browser smoke test at http://127.0.0.1:5173/
* test(desktop): document plan revision rerender regression
Problem:
The focus regression test simulated callback identity churn without documenting why that is the relevant production trigger.
Root cause:
Periodic tab metadata refreshes rerender App, which historically recreated the revision-active callback and refocused the textarea.
Fix:
Document the parent-rerender trigger next to the regression step.
Verification:
- pnpm exec tsx src/__tests__/approval-modal-file-reference.test.tsx
- git diff --check
---------
Co-authored-by: SauronSkywalker <[email protected]>
Co-authored-by: SivanCola <[email protected]>
* fix: add review evidence for complete_step
* fix: accept dedicated review tool receipts
* fix(evidence): require fresh review receipts
Problem:
complete_step accepted any successful review/task receipt in the turn, including a review that predated a later mutation or a background task that had only started.
Root cause:
The review evidence matcher scanned the whole ledger without applying mutation ordering, changed-path coverage, or the structured review_report contract.
Fix:
Require review evidence to be fresh for the latest mutation and cover its changed result. Accept collected structured review reports, reject background starts, and define review evidence as activity completion while leaving blocking verdict enforcement to the delivery gate.
Verification:
- go test ./internal/evidence ./internal/tool/builtin -count=1
- ./scripts/cache-guard.sh
- go vet ./...
- go test ./internal/plugin -run TestLazyCacheMissAsyncSpawn -count=1
- env -u DEEPSEEK_API_KEY go test ./internal/provider/openai -count=1
---------
Co-authored-by: SivanCola <[email protected]>
The scrollspy built its list of sections from the sidebar links, which are
grouped by topic rather than page order, then picked the last section
scrolled past as active. That assumes the list is in document order, so the
highlight settled on the wrong item. Sort the sections by document position.
Clicking a link also swept the highlight through every section it passed,
because the spy runs on each frame of the smooth scroll. Pin the highlight to
the clicked section and release it on scrollend, falling back to the plain
jump where scrollend isn't supported.
Set aria-current on the active link so assistive tech can announce it.
Co-authored-by: 9710willy <[email protected]>
Problem: Stable recovery publishers failed when an immutable candidate predated its reviewed release-note entry. A partially successful run also had no safe way to retry only failed channels.
Root cause: CLI and Desktop rendered notes from the candidate checkout, while preflight validated notes from the protected control plane. The orchestrator always invoked every publisher during recovery.
Fix: Upload the preflight-rendered notes and consume that exact artifact in orchestrated CLI/Desktop publishers. Add opt-out channel switches for manual recovery while retaining public postflight verification for skipped channels.
Verification: bash scripts/release-workflows.test.sh; node scripts/release-notes.mjs render --version v1.17.19 --output /tmp/reasonix-release-notes-v1.17.19.md; git diff --check.
Fixes#6683
NewSessionPath() only sanitized '/' and '\' from model names, missing ':'
and other Windows-illegal filename characters. On Windows, ':' is the
NTFS alternate data stream (ADS) marker, causing:
- System freeze / crash when file system scanners index the session file
- CLI permanently stuck: lease.lock path with ':' triggers 'session is
in use' error that can never clear
- All sidecar files (.ckpt, .lock, .lease.*, .events.jsonl) corrupted
Add all Windows-illegal chars to the replacer: :<>"|?*
2026-07-19 18:48:44 +08:00
883 changed files with 121376 additions and 14966 deletions
-m "Generate a bilingual, product-focused draft from merged pull request metadata. The reviewed catalog will drive the website, desktop app, and GitHub releases."
-m "Summary:
Generate a bilingual, product-focused draft from merged pull request metadata. Reuse the selected release-bound PR when one is available.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing."
fi
git push -u origin "$RELEASE_NOTES_BRANCH"
if ! gh pr list --head "$RELEASE_NOTES_BRANCH" --state open --json number --jq 'length > 0' | grep -q true; then
The user message is a diagnostic report whose findings carry only a severity, a machine code (e.g. config.invalid_toml, derived.invalid_json, update related codes), and a generic scope; pick actions from those codes.
Snapshot IDs are host-issued aliases ordered newest first; pendingUpdate=true means rollback_update is available.
Allowed actions only (return an empty actions array when no safe action applies):
- repair_config with scope global or project
- restore_snapshot with snapshotId
- rebuild_derived_state with target tabs, projects, window, zoom, or all
- rollback_update with no parameters
Never request shell commands, credential changes, session-content edits, arbitrary paths, plugin execution, or source-code changes. Prefer the smallest reversible plan supported by the diagnostics. Do not invent snapshot IDs.`
// providerSafeReport is the strict allowlist DTO sent to the AI provider.
// It deliberately carries no free-form diagnostic strings or state metadata:
// finding messages can quote config lines, URLs, commands, and permission
// rules, while snapshot and update metadata can be edited on disk. Only fixed
// diagnostic vocabulary, booleans, and host-issued snapshot aliases leave the
fmt.Fprintln(os.Stderr,"update rollback after failed install failed:",err)
iffailedInstallBlocksLaunch(result,err){
fmt.Fprintln(os.Stderr,"error: the failed installer may have left a mix of two releases and the rollback did not complete; refusing to start. Re-run reasonix-guard to retry the rollback, or reinstall Reasonix.")
return1
}
}elseiffailure!=nil&&result.RolledBack{
fmt.Fprintf(os.Stderr,"Reasonix Guard restored %s after the %s installer failed.\n",result.ToVersion,result.FromVersion)
fmt.Fprintln(os.Stderr,"error: the installation now mixes two releases; refusing to start. Re-run reasonix-guard to retry the rollback, or reinstall Reasonix.")
return1
}
// The compensated install is still a coherent (new-version) release
// unit, so a Safe Mode boot is a sound fallback while the pending
// transaction stays on disk for the next rollback attempt.
useSafeMode=true
}elseifresult.RolledBack{
fmt.Fprintf(os.Stderr,"Reasonix Guard restored %s after %s failed to start.\n",result.ToVersion,result.FromVersion)
script:=`button returned of (display dialog "Reasonix failed to start repeatedly. You can repair damaged configuration or start with external integrations disabled." with title "Reasonix Recovery" buttons {"Quit", "Safe Mode", "Repair and Start"} default button "Repair and Start" with icon caution)`
"--ok-label=Repair and Start","--cancel-label=Safe Mode","--extra-button=Quit")
out,err:=cmd.Output()
ifstrings.TrimSpace(string(out))=="Quit"{
returnrecoveryQuit
}
iferr==nil{
returnrecoveryRepair
}
returnrecoverySafeMode
}
ifpath,err:=exec.LookPath("kdialog");err==nil{
err:=exec.Command(path,"--warningyesnocancel","Reasonix failed to start repeatedly. Repair configuration and start?","--title","Reasonix Recovery").Run()
Name:"deploy-target",Title:"Deploy target",Description:"legacy deployment target",Scope:memory.FactScopeGlobal,Type:memory.TypeProject,Body:"Deploy payments to the legacy cluster.",
Name:"deploy-target",Title:"Deploy target",Description:"current deployment target",Scope:memory.FactScopeProject,Type:memory.TypeProject,Body:"Deploy payments to the green cluster.",
reason:"Matched permission rule: ask Bash(python3:*)\nThis command uses nested or indirect shell execution. Auto and broad allow rules cannot verify the inner command; approve this exact command or use YOLO.",
},
});
consttext=document.body.textContent??"";
ok(text.includes("命中权限规则:ask Bash(python3:*)"),"approval identifies the exact matched permission rule");
ok(text.includes("嵌套或间接执行"),"dynamic Bash approval explains the matched safety boundary in Chinese");
ok(text.includes("精确命令"),"dynamic Bash approval tells the user how to grant the command");
ok(document.body.textContent?.includes("This project"),"project MCP is grouped under This project");
ok(document.body.textContent?.includes("Global MCP"),"user-installed MCP is grouped by its global scope");
ok(document.body.textContent?.includes("Install once and use automatically in every Reasonix project."),"global MCP explains its cross-project availability");
ok(document.body.textContent?.includes("Project"),"project MCP row shows a project source badge");
ok(document.body.textContent?.includes("Declared by this project and available automatically."),"project MCP explains zero-confirmation availability");
ok(!findButton("Install and use"),"trusted project MCP has no install confirmation");
ok(!findButton("Authorize and connect"),"trusted project MCP has no authorization action");
ok(!findButton("Review changes"),"project MCP has no separate change-review workflow");
ok(!findButton("Refresh catalog"),"catalog maintenance is not part of the normal MCP workflow");
ok(!document.querySelector('[role="dialog"]'),"project authorization does not open a second modal");
eq(finalDeclaration(".footer--decision","max-height"),"calc(100% - var(--topicbar-height))","decision footer is capped above the status bar");
// AC3 — the menu stays anchored above the composer, but its preferred 360px cap
// is now bounded by the real viewport space measured by Composer.
eq(finalDeclaration(".slashmenu","position"),"absolute",".slashmenu still anchors to the composer");
eq(finalDeclaration(".slashmenu","bottom"),"calc(100% + 6px)",".slashmenu still pops above the composer");
eq(finalDeclaration(".slashmenu","max-height"),"min(360px, var(--composer-menu-available-height, 50vh))",".slashmenu uses the measured available height");
// AC4 — App.tsx applies footer--decision exactly when a decision surface owns
// the footer, and the pre-existing footer--compact wiring stays intact.
eq(/decisionSurface \? "footer--decision" : ""/.test(appSource),true,"App.tsx toggles footer--decision on decisionSurface");
Loaded 100 of 883 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.