Summary:
- add bilingual release notes for the cross-platform candidate fixes
- link the reviewed entry to PR #7625
Verification:
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- rendered English and Chinese v1.20.0 notes
Summary:
- make extension content eviction deterministic and plugin path checks portable
- stabilize the parallel-task cancellation coverage test
- keep POSIX mode assertions meaningful on Windows and serialize tmux fixtures safely
Verification:
- go test -covermode=atomic ./...
- go test -race on all changed packages
- go vet ./...
- Windows test binaries cross-compiled for all changed packages
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:
The PR conflicted with the new Extension Protocol UI and event-wire surfaces on main-v2.
Root cause:
Both branches extended the same footer state, frontend event types, controller return surface, CSS shell, and Go event contracts.
Fix:
Preserve extension forms, cards, events, handlers, and budgets while retaining compact decision mocks, plan resolution, decision receipts, and overflow disclosure behavior.
Verification:
Go provider, agent, control, and eventwire tests
Ask 87/87, decision surface 172/172, receipt 6/6, extension surface 43/43
frontend typecheck, CSS checks, and production build
Problem:
Long labels could lose their full decision text, completed Ask receipts repeated the outcome, and cancelled turns could drop structured receipt metadata after reload.
Root cause:
Overflow disclosure only measured descriptions, cancellation sidecars copied plain notice fields, and the cache-sensitive Ask contract lacked an explicit stability guard.
Fix:
Measure label and description overflow independently, keep raw answer values unchanged, persist decision receipts through cancellation sidecars, remove redundant receipt copy, and pin the existing Ask provider contract hash.
Verification:
go test ./internal/agent -count=1
focused Ask, decision-surface, and transcript frontend tests
frontend typecheck and CSS checks
git diff --check
Problem:
Sidecar protocol errors could surface credential-shaped peer messages, and stalled optional runtimes could multiply boot latency by the installed package count.
Root cause:
The host preserved JSON-RPC error messages verbatim and started every runtime sequentially with an independent handshake timeout.
Fix:
Redact peer, startup, crash, and boot-warning messages at host boundaries. Start runtime packages with a four-worker pool under one shared 30-second generation budget while preserving deterministic result ordering. Document the lifecycle and performance contract.
Verification:
- focused redaction and bounded-start regressions repeated 10-20 times
- go test -race ./internal/extension/... ./internal/boot ./internal/control ./internal/serve -count=1
- go test ./...
- go test ./... in sdk/go and desktop
- cache guard, site test/build, targeted vet, and protocol generation check
Problem: main-v2 advanced again after the previous latest-base validation.
Root cause: the updater recovery PR merged while the Remote Web regression branch was being revalidated.
Fix: merge the new base without changing the focused Remote Web recovery test or documentation diff.
Verification: the merge is conflict-free, preserves the three-file +147/-1 three-dot diff, and passes git diff --check.
Problem: PR #7368 no longer included the latest main-v2 after the task monitor and macOS alias fixes merged.
Root cause: the base branch advanced after the previous recovery regression validation, leaving the PR merge-base stale.
Fix: merge the current main-v2 while preserving the focused three-file Remote Web recovery regression and documentation diff.
Verification: the latest-base merge tree passed the SSH-drop regression ten times, the race-enabled regression three times, and git diff --check.
Problem:
The compact decision surfaces pushed the merged frontend CSS bundle slightly above the existing gzip budget.
Root cause:
Shared decision controls repeated several equivalent declarations, while the 112 KiB budget had no allowance for the approved always-loaded approval styles.
Fix:
Consolidate equivalent selectors, simplify the one-line truncation rule, and increase the CSS gzip budget by a bounded 1 KiB.
Verification:
- pnpm build
- decision-surface, ask-card-layout, approval-modal-file-reference, and footer-decision-overflow tests
- pnpm test:task-monitor
- pnpm test:typecheck
Problem:
Decision receipts could split an assistant tool call from its real result, and Plan start, revise, and exit choices collapsed into generic approval outcomes.
Root cause:
Receipts were stored as standalone local-only assistant messages, while the desktop bridge represented every Plan choice with the legacy approval boolean.
Fix:
Attach receipts to the owning assistant message, migrate the interleaved receipt shape on load, and add an explicit typed Plan decision path across the controller and desktop bridge.
Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/agent ./internal/control
- pnpm test
- pnpm build
Resolve the Task Monitor and extension UI bundle-budget overlap using measured production output. The combined build remains below bounded JavaScript, CSS, locale, and raw asset limits.
Validation:
- root tests and vet
- SDK tests and vet
- frontend test:all and production build
- site tests and production build
- Desktop vet and build
- extension/Serve/control/jobs race suite
- isolated main-v2 baseline comparison for the macOS TempDir cleanup failure
Problem:
- macOS updates could launch the replacement bundle without ever committing startup health, permanently blocking later updates.
- Legacy WebKit could reject the Markdown chunk, and native installers hid layout activation failures.
Root cause:
- The visible-window lifecycle no longer called the exact update health commit, legacy app-bundle debris had no transaction-bound retirement path, local-path regexes used lookbehind, and installer/helper handshakes exposed only generic failures.
Fix:
- Commit only the startup-captured update identity and safely archive unrecoverable legacy app-bundle transactions.
- Remove RegExp lookbehind, return structured macOS handoff errors, surface Windows activator output, and mark the localized NSIS script as UTF-8.
- Document the signed overwrite-install recovery path in English and Chinese.
Verification:
- go test ./...
- cd desktop && go test ./...
- cd desktop/frontend && pnpm test:all && pnpm build
- go test -race ./internal/repair -run TestArchiveSupersededPending
- Native macOS arm64 Wails build and Parallels Windows 11 arm64 NSIS install/activation tests
Problem:
Decision rows expanded to two lines or repeated complete danger copy below the action, making approvals visually noisy and inconsistent across Ask, tool approvals, runtime blockers, and context clearing.
Root cause:
Decision descriptions expanded inside the action row, and destructive runtime actions rendered an external detail unconditionally even when their full consequence already fit.
Fix:
Keep desktop decision rows at a stable 38px with single-line summaries, move overflow copy to a separate disclosure, auto-open destructive details only when truncated, add canonical browser mock triggers, and clarify mode-switch and active-close decisions across all locales.
Verification:
- pnpm exec tsx src/__tests__/ask-card-layout.test.ts (75/75)
- pnpm exec tsx src/__tests__/decision-surface.test.tsx (172/172)
- pnpm test:typecheck
- pnpm build
- in-app browser: /mock-mode-jobs and /mock-close-active
Problem:
Extension Protocol v1 had repository references but no website entry point or copyable installable starter, leaving developers without a clear path from concepts to a working sidecar.
Root cause:
The protocol implementation was not connected to the public discovery surfaces, SDK release guidance, or embedded-doc search contracts.
Fix:
Add bilingual website and README entry points, a buildable Manifest v1 starter sidecar, immutable SDK tag guidance, and regression coverage for the website and embedded docs.
Verification:
- go test ./... -count=1
- go vet ./...
- sdk/go tests, vet, and Windows cross-build
- site npm test and production build
- isolated plugin dry-run, linked install, and doctor
- desktop and mobile browser checks
Problem: The Task Monitor branch predated the current Remote Workbench architecture, its Simplified Chinese locale chunk exceeded the current frontend budget, workspace-controlled tmux mappings could target unrelated sessions, cleanup had a check-to-kill race, and generated names could exceed tmux limits.
Fix: Merge current main-v2; bind mappings to random tmux ownership markers; validate mapping identity and targets; use exact tmux targets with atomic ownership-checked deletion; fail closed for legacy or forged mappings; hash oversized default names; and explicitly budget the compact localized Task Monitor copy.
Verification: Backend, desktop, frontend, race, vet, Windows cross-compile, cache guard, real tmux, Remote Workbench, and live DeepSeek v4 flash Task Monitor tests pass.
Co-authored-by: SivanCola <[email protected]>
Problem:
Desktop releases since v1.19.5 can abort before opening a window when a Finder alias resolves to a non-file URL.
Root cause:
The startup icon repair passed every resolved alias URL to NSBundle. Foundation raises NSInvalidArgumentException for non-file URLs, and that Objective-C exception crossed the cgo boundary and terminated the process.
Fix:
Reject non-file alias targets before bundle inspection, convert unexpected Foundation exceptions at every native alias entry point into ordinary errors, and preserve healthy, unrelated, upgrade, and first-install behavior with focused regression coverage.
Verification:
- go test . -run 'Test(RepairMacDesktopAliases|MacBundleOwnership)' -count=10
- go vet .
- go build -o /tmp/reasonix-desktop-icon-repair-check .
- git diff --check
Problem:
PR #7368 targeted the Remote Workbench and rpcwire stacks, but main-v2 removed both in favor of SSH Serve plus per-host Remote Web windows. Keeping the old implementation would restore a superseded architecture and leave the replacement recovery path without one end-to-end regression.
Root cause:
The original reconnect state machine and control/data scheduler were coupled to the deleted Workbench protocol. The current architecture recovers at the supervised SSH, persistent forward, and native window layers instead.
Fix:
Merge the latest main-v2 deletion and Remote Web architecture, drop every obsolete Workbench/rpcwire change, add an in-process real SSH drop integration test that proves reconnect, forward continuity, and window reload, and document transient versus terminal recovery behavior in both guides.
Verification:
- cd desktop && go test -run '^TestRemoteWindowRecoversAcrossRealSSHDrop$' -count=10 .
- cd desktop && go test -race -run '^TestRemoteWindowRecoversAcrossRealSSHDrop$' -count=3 .
- go test ./internal/remote/... -count=1
- go test -race ./internal/remote/... -count=1
- cd desktop && go test -run 'Remote|SSH|Serve' -count=1 .
- cd desktop && go test -race -run 'Remote|SSH|Serve' -count=1 .
- cd desktop/frontend && pnpm test:typecheck && pnpm test:remote && pnpm build
- ./scripts/cache-guard.sh
- git diff --check
Problem:
Removing the Remote Workbench protocol test file also removed coverage for the still-supported remote add, import, remove, forward, and connect commands.
Root cause:
The obsolete Workbench build-ID assertion shared a file with unrelated remote CLI contract tests, and the whole file was deleted with the protocol package.
Fix:
Restore the supported remote CLI tests and replace the build-ID assertion with migration-stub coverage for all three removed Workbench commands.
Verification:
- go test ./... -count=1
- go test -race ./... -count=1 -timeout 15m
- go test ./... -count=1 -timeout 10m (desktop)
- go test -race ./... -count=1 -timeout 15m (desktop)
- pnpm test:all && pnpm build
- ./scripts/cache-guard.sh
Resolve the protocol drift between this branch and main-v2: both sides
regenerated the remote protocol artifacts, so all three conflicts
(remoteProtocol.generated.ts, schema.generated.json,
schema_hash.generated.go) were resolved by regenerating from the merged
protocol definitions with cmd/remote-protocol-gen. Textual auto-merges in
useController.ts, locales, controller/provider/event wiring were kept and
verified semantically: the branch's LocalOnly decision receipts coexist
with main-v2's subagent progress events.
Verification: remote-protocol-gen -check up to date; go build + go test
./... (root and desktop); frontend tsc, eslint, CSS checks; focused tests
from both sides: ask-card-layout 71/71, decision-surface 149/149,
remote-store 28/28, subagent-progress 46/46, subagent-progress-card 18/18.
Problem: Option labels on every decision surface (Ask, approvals, runtime
blockers, context clearing) rendered crushed into a ~28px column at desktop
widths, breaking words mid-label ("Maintenance window" became
"Maintena/nce/window") in all locales.
Root cause: The 44% width cap was expressed as max-width on the label grid
item. A percentage max-width on an item inside a content-sized (max-content)
grid track resolves against that track (~64px), not against the row, so
labels were capped at 44% of ~64px and overflow-wrap:anywhere shattered the
text. The existing jsdom layout tests assert CSS declarations, so only a
real-browser render exposes this cyclic percentage resolution.
Fix: Move the cap into the track itself: grid-template-columns:
fit-content(44%) minmax(0, 1fr). fit-content() resolves its percentage
against the grid container, giving labels their natural width up to 44% of
the row. Drop the label max-width; the 560px single-column fallback is
unaffected. Add string-level regression assertions so the label rule cannot
re-grow a numeric width cap.
Verification: check-css-syntax and check-z-index-tokens pass;
ask-card-layout 71/71; decision-surface 149/149; tsc -p tsconfig.test.json
clean. Real-browser screenshots (Playwright on Edge; dark/light; en/zh;
1280px and 520px) show single-line full-word labels on Ask, runtime
decision, and clear-context cards.
Problem: PR #7373 had fallen 48 commits behind main-v2, and the merge introduced protocol conflicts plus cache and provider-metadata compatibility hazards.
Root cause: extension interception and generated protocol artifacts evolved alongside new structured-response and reasoning metadata contracts on main-v2.
Fix: preserve both extension interception and main-v2 response-format semantics, clear provider-bound reasoning metadata after replacement, keep nil response formats byte-stable, and regenerate both protocols.
Verification: go test ./...; go vet ./...; targeted go test -race; SDK race tests; Desktop Go tests; frontend typecheck, remote tests, and build; protocol generator checks; cache guard; extension latency benchmarks.
Problem:
Extension catalog cold reads could issue duplicate concurrent RPCs, observation events could block the Agent behind a wedged sidecar, SDK callback concurrency was implicit, and stable enabled-extension cache behavior had no release guard.
Root cause:
Catalog caching used an unlocked check-then-fetch sequence, event notifications waited for physical writes, and equal host/SDK intercept deadlines could race into different error reasons.
Fix:
Coalesce per-plugin catalog fetches, add a bounded non-blocking notification path that preserves request capacity, freeze SDK timeout classification, document concurrent callbacks, and add real-sidecar p50/p95 benchmarks plus a stable-extension cache guard.
Verification:
go test ./...
go test -race ./internal/rpcwire ./internal/extension/providerext ./internal/extension/dispatch ./internal/extension/sidecar ./internal/boot ./internal/agent ./internal/control
(cd sdk/go && go test -race ./...)
./scripts/cache-guard.sh
go vet ./...
go run ./cmd/extension-protocol-gen -check
Problem:
Repeated pasted-text blocks and attachment rows can grow the composer footer without bound. Simply capping the pasted list also lets its column flex items shrink until their contents are clipped.
Root cause:
The footer children had no bounded overflow area, and pasted blocks retained the default flex-shrink behavior inside a capped column container.
Fix:
Bound the pasted and attachment lists, keep pasted blocks non-shrinking, and add CSS contract coverage for both overflow paths.
Verification:
- footer-decision-overflow.test.ts (19/19)
- composer-resized-input-css.test.ts (7/7)
- message-pasted-blocks.test.ts (9/9)
- composer-session-draft.test.tsx (130/130)
- composer-goal-toggle.test.tsx (228/228)
- pnpm build
- browser: 20 blocks remain readable and scroll inside the capped list
Co-authored-by: erphenheimer <[email protected]>
Problem:
- Email verification links landed on the static site instead of the accounts worker.
- Community responses exposed stored author emails, unverified users could affect moderation, and several account/community controls were incomplete.
- Related multi-statement writes and handle updates could leave partial or racy state.
Root cause:
- The frontend origin was reused as the verification endpoint origin.
- Public forum queries serialized internal identity columns and interaction routes did not share the verified-member gate.
- Forum writes used separate auto-commit statements and profile handles used a check-then-update sequence.
Fix:
- Add a canonical account worker origin, public profile and resend-verification UI.
- Return public handles, gate and rate-limit interactions, implement idempotent likes, and map validation failures to client errors.
- Use D1 transactional batches for related forum writes and atomically claim profile handles.
- Run focused tests and type checks before worker deployments.
Verification:
- accounts: 3 tests, TypeScript check, Wrangler dry-run
- forum: 4 tests, TypeScript check, Wrangler dry-run
- site: 55 tests, Astro build with 42 generated pages
Problem:
Responses reasoning metadata could be persisted beside PostLLMCall-transformed text, and the response-format regression test did not prove per-turn isolation.
Root cause:
Agent storage pinned raw reasoning for signatures and provider policies but ignored reasoning IDs and statuses. The controller test submitted requests without observing the format on either turn.
Fix:
Preserve raw reasoning whenever provider-bound metadata is present. Add a channel-driven finishing-window interleaving test that proves distinct HTTP formats remain attached to their accepted turns, and correct the reference-turn documentation.
Verification:
- go test ./...
- go test -race ./internal/agent -run PostLLMCall -count=1
- go test -race ./internal/control -run HTTPFormat\|TurnFormat\|RefTurnFormat -count=1
- go vet on the affected runtime packages
- bash scripts/cache-guard.sh
Co-authored-by: SivanCola <[email protected]>
Bring the integration branch onto the latest upstream base before applying the final approval blockers. This incorporates PR #7372 without changing the Responses feature behavior.
Problem:
- The CI lint job rejected the PR because subagentProgressPhase.terminal was never called.
Root cause:
- The helper became obsolete when group lifecycle handling moved to explicit terminal events, but it remained in the progress implementation.
Fix:
- Remove the dead helper without changing progress state transitions or event routing.
Verification:
- golangci-lint run --timeout=5m
- go test ./...
- go test -race ./internal/agent -count=1
- git diff --check
Problem: Footer decision cards were visually oversized and mixed primary choices with secondary escape paths, making Ask and runtime decisions harder to scan.
Root cause: Each surface encoded its own layout density and cancellation affordance, while user decisions had no durable local receipt that could be replayed without entering provider history.
Fix: Unify decision-card sizing, scrolling, action rows, and secondary footer actions across Ask, approvals, recovery, runtime blockers, and context clearing. Persist bounded Ask/approval/recovery receipts as LocalOnly transcript metadata, wire them through history and remote events, and localize the rendered receipts.
Verification: go test ./...; (cd desktop && go test ./...); frontend typecheck; CSS checks; Ask 69/69; decision surfaces 149/149; remote protocol generator check.
Problem:
- The CLI recognized only the four current reserved progress names, so a future reasonix.subagent.* channel could fall through to ordinary tool output.
Root cause:
- Known-channel decoding and reserved-namespace routing shared one exact-match predicate.
Fix:
- Add a prefix predicate for the reserved namespace while retaining exact matching for supported channels.
- Suppress unknown reserved channels before ordinary tool streaming.
- Add a regression test proving no transcript, tool-stream, or known-channel state mutation.
Verification:
- go test ./...
- go test -race ./internal/event ./internal/cli -count=1
- ./scripts/cache-guard.sh
- git diff --check
Problem: Provider setup refreshed configuration while holding the credential edit lock, opposite to the documented config-then-credential ordering. Concurrent configuration and credential edits could therefore block each other.
Root cause: refreshProviderSetup used the migration-capable config.Load path even though setup only needs a read-only snapshot.
Fix: Use LoadForRootReadOnly while the credential snapshot is locked, and add a regression that holds the config edit lock while refresh completes.
Verification: focused Provider/config race tests, root go test ./..., go vet ./..., cache-guard, and git diff --check all pass.
Replace full-URL strings.Contains matching with host-level exact/suffix
matching (via officialProviderHost) in isMimoEntry and detectCacheVendor, so
unrelated or attacker-controlled URLs (e.g. api.xiaomimimo.com.attacker.example,
example.com/?u=api.anthropic.com) can't be misdetected as a known vendor.
- isMimoEntry: MiMo effort only enabled on real api.xiaomimimo.com hosts
- detectCacheVendor: cache TTL only shortened to 5m on real
dashscope/anthropic hosts (prevents premature cold-resume pruning)
Adds attack-URL regression cases to both tests.
Problem:
Documented task monitor commands put the task ID before options, but status, events, and control commands rejected that ordering with exit code 2.
Root cause:
Go's flag parser stops at the first positional argument, while only the tmux path normalized task IDs and its helper hard-coded a subset of flags.
Fix:
Normalize registered task flags before positional IDs for query and control commands, preserving flags-first compatibility and covering the documented order with focused tests.
Verification:
- go test ./internal/cli -count=1
- go test ./...
- documented-order status/events CLI smoke
Co-authored-by: SivanCola <[email protected]>
Problem:
- tmux mapping I/O could follow repository-controlled symlinks, while valid cleanable project scopes were rejected.
- persisted failure and control summaries could expose paths or commands through the content-free monitor interface.
- stop and cancel cleared runtime ownership before exit, leaving alive tasks without a reconciliation deadline.
- the navigation contract test still assumed the pre-helper queue wiring.
Root cause:
- tmux mapping storage bypassed the FileStore path boundary, free-form summaries crossed the machine-output boundary, and control admission was treated as runtime completion.
Fix:
- reuse FileStore symlink checks and private storage modes for tmux mappings, including the default project scope.
- persist stable failure codes only and scrub legacy summaries at every monitor output boundary.
- preserve live runtime leases after admitted control and bound legacy lease-less snapshots.
- validate the current navigation helper forwarding chain.
Verification:
- go test ./...
- go vet ./...
- go test -race ./internal/taskmonitor
- pnpm test
- pnpm build
Co-authored-by: SivanCola <[email protected]>
A 26-account network posted 84 emoji-padded template comments across 20
issues over ten hours, burying real reports. The accounts are structurally
distinct from real reporters: recently registered, zero followers, a pile of
forked repos, and sub-50 character comments with no code, link, or log.
Replayed against all 7604 issue comments in the repo, requiring every signal
to fire catches all 84 spam posts with no false positive. Any single signal
relaxed pulls in real users.
Problem:
- The original optional forwarding patch was behind main-v2 and did not define bounded, isolated rendering or group lifecycle semantics.
Root cause:
- Raw child events crossed the parent sink without a stable progress contract, pacing budget, or explicit group ownership.
Fix:
- Integrate bounded and UTF-8-safe progress forwarding across agent, Desktop, CLI, and headless consumers.
- Preserve explicit group lifecycle, fair backpressure, isolated in-memory rendering, and terminal-state convergence.
- Keep the original contributor commit in the PR history while resolving the implementation onto current main-v2.
Verification:
- go test ./...
- go test -race ./internal/agent ./internal/event ./internal/cli -count=1
- go test ./... (desktop)
- pnpm test:typecheck && pnpm test && pnpm build (desktop/frontend)
- ./scripts/cache-guard.sh
- Remote zero-diff gate
Co-authored-by: aakashshetty <[email protected]>
Problem:
- Task Monitor actions could resolve the active workspace after the user switched tabs.
- Delayed open-session lookups could override a newer navigation intent.
- Valid parent-relative project directories were rejected as traversal.
Root cause:
- Wails task methods derived scope from process-wide active-tab state, open-session claimed navigation only after asynchronous lookups, and project paths were screened with a substring check instead of treated as caller-selected scopes.
Fix:
- Add tab-bound task/session Wails methods while preserving the legacy bindings.
- Claim and reuse the navigation intent before the first await, discarding stale successes and failures.
- Clean valid project scopes while retaining task-ID and task-store symlink defenses.
Verification:
- go test -count=1 ./...
- cd desktop && go test -count=1 ./...
- go test -race -count=1 ./internal/taskmonitor
- cd desktop && go test -race -count=1 -run TestStopTaskForTab .
- cd desktop/frontend && pnpm test:task-monitor
- cd desktop/frontend && pnpm build
- go vet ./...
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
- Provider activation failures saved the key but made the advertised retry path unusable.
- A remote web child could outlive a crashed Desktop and be adopted without a process registration.
- One global frontend generation let launches for different SSH hosts cancel each other.
Root cause:
- The setup state retained the pre-save credential revision after a failed controller rebuild.
- Remote child single-instance identity was host-only and children did not observe owner death.
- Remote launch freshness was tracked globally instead of per host.
Fix:
- Track activation-pending setup state and retry controller activation without rewriting the secret.
- Scope child identities to a random Desktop owner and close children when the owner process exits.
- Track launch generations independently for each remote host.
Verification:
- go test -count=1 ./...
- go test -race -count=1 ./internal/config ./internal/serve
- desktop go test -count=1 -timeout 300s ./...
- desktop focused race tests and Windows amd64 cross-compile
- frontend test:all, test:remote, and build
- cache-guard.sh and git diff --check
Problem:
- Background fleet group IDs were namespaced twice and child progress could stop after the job-ID handoff.
Root cause:
- The group merger emitted through a nested sub-sink and Execute closed it even after handing lifecycle ownership to the background job.
Fix:
- Emit already-namespaced group and child progress through the raw call sink.
- Transfer merger close ownership only for background jobs while preserving foreground cleanup.
- Add deterministic background lifecycle and stable-ID coverage.
Verification:
- go test ./...
- go test -race ./internal/agent ./internal/event ./internal/cli -count=1
- go test ./... (desktop)
- pnpm test:typecheck && pnpm test && pnpm build (desktop/frontend)
- ./scripts/cache-guard.sh
- Remote zero-diff gate
Integrate the latest main-v2 changes before validating and publishing the task monitor remediation. This preserves the contributor branch history and avoids a force-push.
Problem:
- Desktop stop/cancel could target a same-named session in another workspace.
- A heartbeat resumed after lease expiry observed its own task as stale and stopped renewing.
- Open session only displayed the session ID without navigating.
Root cause:
- Runtime routing omitted workspace identity, lease renewal used reconciled observer reads without an owner generation, and the panel was not connected to the desktop navigation queue.
Fix:
- Scope runtime cancellation by the captured workspace.
- Add owner-bound raw lease renewal with CAS and deterministic regression coverage.
- Route task session opens through the existing serialized resume-session navigation flow.
Verification:
- go test -race -count=1 ./internal/taskmonitor
- cd desktop && go test -race -count=1 -run 'TestDesktopTaskJobKiller|TestTaskMonitor|TestStopTaskRoutes' .
- cd desktop/frontend && pnpm test:task-monitor
- cd desktop/frontend && pnpm typecheck
- git diff --check
Co-authored-by: SivanCola <[email protected]>
* feat(ablation): switch subsystems off behind one shared vocabulary
Nothing could actually be ablated. The only variable axis was the runtime
profile, a coarse bundle, so attributing a change in solve rate to evidence
gating, the planner, subagents, retrieval or cache-preserving compaction was
impossible and every claim about those subsystems rested on assertion.
Add internal/ablation as the single vocabulary: one Set flows from --ablate
through boot into the agent, the controller and spawned sub-agents, and names
the published arm. Each subsystem gets exactly one switch point instead of
scattered conditionals — the planner at effectivePlannerModel, subagents at
the task-tool registration, retrieval at the two BM25-backed tools plus
auto-recall, evidence at finalReadinessCheckFor, and compaction at a new
compactThresholds that collapses the cache-preserving deferral.
boot.Options.DisablePlanner folds into the same Set; ACP's --planner=off maps
onto it rather than carrying a second switch for one subsystem. The arm is
recorded in --metrics and heads the e2e report, and the control arm's command
line stays byte-identical to the pre-ablation one so old numbers remain
comparable.
* docs(cli): document the --ablate benchmark arm
Problem:
- The Other row nested interactive controls, long-tail tooltips could grow without bound, and repeated palette navigation to usage statistics was ignored.
Root cause:
- Expansion semantics were split across a role button and child button, tooltip detail rendered the full tail, and model focus was deduplicated by target instead of request identity.
Fix:
- Use one native row button, cap tooltip breakdowns at five with an omitted count, and derive a monotonic focus request ID from shared overlay state.
- Add regression coverage for accessibility, bounded breakdowns, and repeated focus requests.
Verification:
- pnpm --dir desktop/frontend pretest
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend check:css
- pnpm --dir desktop/frontend exec eslint src/App.tsx src/components/SettingsPanel.tsx src/components/UsageStatsPanel.tsx src/__tests__/usage-stats-panel.test.tsx src/__tests__/startup-settings-contract.test.ts
- pnpm --dir desktop/frontend exec tsx src/__tests__/startup-settings-contract.test.ts
- git diff --check
Co-authored-by: SivanCola <[email protected]>
The backend now emits an explicit lifecycle for group cards: running when
children start and exactly one terminal (completed/cancelled/failed,
including validation failures and zero-child cancellation) after the group
settles, keyed by the group call ID. Frontends no longer infer group
completion from the children observed so far — a background group's children
dispatch asynchronously, so a job-id result arriving first or a fast first
child finishing before later children dispatch could previously settle the
group permanently while children were still running. The reducer now settles
group cards only from their own terminal event; the child-inference helpers
are removed. Adds reverse-order, zero-child cancellation, group failure, and
group lifecycle integration tests.
Race caught by TestBootExtensionUIHubWired on the latest base: a sidecar
host/ui/publish arriving during preflight lands on the hub's sink fallback
from its wire-handler goroutine, while the build reassigned the same sink
variable afterwards with the stats recorder and goalUsageTee wraps
(boot.go:409 vs the closure at boot.go:279).
Fix: perform both wraps immediately after event.Sync, before the hub is
constructed, so the closure's captured sink is never reassigned.
Verification: TestBootExtensionUIHubWired x5 and the full internal/boot
package under -race pass.
The goal-runtime PR adds the provider-visible update_goal builtin; record
the new tool contract in the golden baseline (upstream cache-impacting
addition, not extension-kernel drift).
The e2e report could say what a suite cost but not what one solved task
cost, and every failure rendered as a bare cross — a wrong patch and a
guard that deliberately stopped the run were indistinguishable. Neither
number survives a comparison against another harness.
Record wall time, tool-call accounting and the stopping guard in
--metrics, name the guards through agent.PauseClass so hosts stop
reaching into unexported pause types, and lead the report with
cost-per-solved plus a failure-class breakdown. Wall time is the
harness's own clock so the same suite stays comparable when it runs
against a harness that cannot emit our metrics file.
Post-merge test alignment for PR #7373 against the latest base:
- The CLI unknown-slash path now sends the input as a regular message
(#5756); the resolution-order test keeps proving that extension actions
resolve BEFORE that fallback and asserts the new send-as-message
behavior.
- Regenerate the golden baselines: main-v2 added two provider-visible
builtin tools (docs, read_subagent_result), growing the tool contract
and cache prefix. This is an upstream cache-impacting change, not
extension-kernel drift — recorded here so the golden gate keeps its
byte-exact meaning.
Verification: go test ./internal/cli ./internal/boot, race on boot/agent/
rpcwire, TestGoldenBaselineNoExtensions, frontend capabilities-panel test,
pnpm test + build all pass on the merged tree.
Problem:
- PR #7528 became conflicting after main-v2 added Goal and Delivery hardening changes that also modified the legacy Remote Workbench stack.
Root cause:
- The base branch added a remote Pause Goal RPC path and regenerated protocol artifacts after this branch had removed that entire stack.
Fix:
- Merge the latest main-v2 without rewriting branch history.
- Preserve deletion of the obsolete Workbench protocol, runtime, projection, and generated frontend bindings.
- Remove the auto-merged PauseGoal remote branch while retaining the new local Goal and Delivery behavior.
Verification:
- go test -count=1 ./... (root and desktop)
- focused race tests for remote windows, Serve lifecycle, and provider credential CAS
- frontend typecheck, remote tests, full tests, production build, and bundle budgets
- go vet and golangci-lint in both Go modules
- Windows amd64 desktop test cross-compile
- cache guard, diff checks, and Workbench static residual scan
Problem:
Concurrent disconnects could miss a window process registered after teardown, the Serve port file became visible before a slow balance diagnostic completed, a stale Provider setup page could overwrite a newer credential, and the removed OpenSSH transport left an unused resolver that failed Desktop lint.
Root cause:
Remote window open/close entry points had no shared per-host ordering boundary; runServe performed balance synchronously before starting the HTTP accept loop; Provider setup cached missing-key state without a credential-store compare-and-apply token; and the transport deletion retained its private path helper and tests.
Fix:
- Serialize and generation-fence window lifecycle operations per host, including reconnect and terminal status callbacks, with a deterministic late-registration regression.
- Run balance diagnostics asynchronously and prove the setup page responds while a balance endpoint is blocked.
- Add credential revision compare-and-apply under the existing cross-process lock and reject stale setup submissions without exposing the revision.
- Delete the orphaned OpenSSH resolver and its obsolete tests.
Verification:
- go test -count=1 ./...
- cd desktop && go test -count=1 -timeout 300s ./...
- focused root/Desktop race tests
- go vet and golangci-lint in both Go modules
- Windows amd64 Desktop test-binary cross-compile
- pnpm test:typecheck
- scripts/cache-guard.sh
- git diff --check
- desktop: a background parallel_tasks/fleet whose job-id result arrives
before any child dispatched now stays running instead of settling as
completed on an empty progress tree; it settles when children appear and
finish, and a cancelled/failed aggregate settles it regardless.
- serve: reserved reasonix.subagent.* ToolProgress channels are recognized
and ignored, so sub-agent status/reasoning/text never land in ordinary
tool output.
- agent: ordinary phase transitions now share the 32 events/s group budget
with content previews (only initial queued/running and the terminal event
bypass it), so a fleet of phase-flapping children cannot exceed the
advertised queue protection.
- agent: a budget trim that drops buffered content propagates the truncation
on the next actually-emitted channel, or surfaces a truncated notice at
the terminal flush, so frontends always learn that preview content was
discarded.
- tests: reverse-order background group lifecycle, budget sharing across
statuses and previews, and trim-truncation propagation; docs updated to
match the enforced contract.
Problem:
Ordinary turns still invoked the Goal evaluator, and Delivery treated negated inspection or persistence language as affirmative work.
Root cause:
The shared turn wrapper entered Goal advancement without a Goal turn recorder, while the Delivery classifier matched observable and durable keywords without applying its clause-aware negation rules.
Fix:
Require a current epoch-scoped Goal recorder before evaluator/FSM work, reuse negation-aware intent matching for observable and persistent actions, add focused regressions, and format the two files blocking CI.
Verification:
- go test ./...
- go test -race ./internal/agent ./internal/control ./internal/evidence ./internal/provider ./internal/permission ./internal/shellsafe ./internal/goaleval ./internal/tool/...
- cd desktop && go test ./...
- cd desktop/frontend && pnpm typecheck && pnpm test
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
- go run ./cmd/remote-protocol-gen -check -root .
- git diff --check
Describe the reserved ToolProgress channel contract, the phase state
machine, pacing and memory bounds, what is deliberately not done (no
parent-body leakage, no persistence, no Remote/ACP/bot changes), and the
desktop and CLI behaviors in English and Simplified Chinese.
Agent: fake-clock merger tests (250ms merge window, 32/sec group budget
round-robin, 8 KiB pending bound, UTF-8-safe tails), wrap conversion and
forwarding isolation (Message/Reasoning/Text never leak), two-child
deterministic interleave, queued→running→terminal state machines for
foreground and background runs, single terminal on cancel/deadline/error/
panic, late events ignored, and close without leaked goroutines. CLI:
per-child blocks, verbose tails, terminal folding, native scrollback phase
lines, and no cross-streaming. ACP and bot: regression tests locking that
reserved ToolProgress bodies stay silently ignored.
Give every sub-agent child its own progress state and a fixed transcript
slot keyed by its namespaced call ID, independent of the single live
toolStreamID, so concurrent children never cross-stream. By default the
block shows phase, running elapsed, and recent activity; /verbose (Ctrl+O)
adds bounded 4 KiB reasoning/text tails with a truncation marker. Terminal
children fold to a one-line summary; Termux native scrollback queues a
status line on phase changes and terminal only (verbose previews at most
once per 2s per child). Adds localized phase labels and progress formats.
Add an in-memory-only subagentProgress field to tool items: the reducer
routes reserved ToolProgress channels into the target card's preview (never
tool.output or the parent LiveStream), initializes task/read_only_task/
parallel_tasks/fleet dispatches, keeps background cards running while child
progress is non-terminal, and settles group cards when their whole child
progress tree is terminal. The card header shows a phase chip with running
elapsed and recent activity (ticking once a second while live); expanding
the card shows isolated reasoning / response preview / notices. Preview
retention is capped per channel (8 KiB reasoning/text, 2 KiB notice) and
never hydrated from history. Adds en/zh/zh-TW copy and compact styles.
Forward each sub-agent run's Reasoning/Text/Notice/Retrying as reserved
ToolProgress channel events (reasonix.subagent.status/reasoning/text/notice)
so local frontends can show live per-child progress without the child's
bodies entering the parent transcript or provider context. A shared merger
per parent task group paces previews: one pending slot per (child, channel),
a 250ms merge window, a 32 event/sec group budget round-robined across
children, and an 8 KiB per-child pending buffer with UTF-8-safe tails.
RunProfileSpec emits exactly one terminal status (completed/cancelled/failed)
after synchronously flushing pending previews; late events are ignored.
Background runs emit queued at registration and running on slot acquisition;
parallel_tasks and fleet share one merger per call via context. Real
sub-agent tool progress is namespaced and forwarded like ToolDispatch.
Problem:
The first reviewed release candidate was superseded by two merges on main-v2, and the regenerated draft again narrowed the release surface to Desktop while omitting the DeepSeek web-search privacy and billing risk.
Root cause:
The generated draft only reflected selected user-facing categories and did not preserve the reviewed metadata corrections from the prior release-notes PR.
Fix:
Record PRs #7531 and #7532, restore the full product surface inventory, and document the default-on DeepSeek server-search opt-out and billing implications.
Verification:
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- bilingual render for v1.19.7
- git diff --check
- jq empty release-notes/releases.json
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.
Observe-first increment toward lease-holder-is-single-writer: one deduped structured warning per unleased save path; no behavior change. Store-P2 groundwork for the snapshot-conflict cluster.
Problem:
- Goal token limits were checked only after complete turns, allowing tool loops to overshoot.
- Delivery treated safe command substitutions as mutations and over-required structured reviews or conversation evidence.
- Non-Goal update_goal calls could waste repeated model rounds.
Root cause:
- Usage accounting was turn-final instead of provider-request scoped.
- Readiness conflated evidence with its production mechanism and used a binary task classifier.
- Workflow-only tool misuse had no host-side short circuit.
Fix:
- Add request admission, concurrent reservations, exact usage accounting, and fail-closed Goal budget transitions.
- Resolve narrow read-only shell substitutions and propagate trusted read-only receipts.
- Accept host-proven Medium reviews, distinguish conversational and persistent intents, and bound non-Goal update_goal recovery.
- Remove two stale symbols that blocked the repository lint gate.
Verification:
- go test ./...
- go vet ./...
- go test -race ./internal/agent ./internal/evidence ./internal/control
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
- git diff --check
sessionProfileForMessage now consumes the persisted chat→session binding (soft, degradable) instead of only the in-memory /attach override; degraded states stay stable per turn. Fixes#6917, #6934
Problem:
The generated catalog listed Desktop as the only affected surface and omitted the default-on DeepSeek web-search privacy and billing caveat.
Root cause:
The release-note generator retained its example surface and did not promote the supported opt-out guidance into the risk section.
Fix:
Record every affected product surface and add bilingual, PR-grounded web-search privacy, billing, and opt-out guidance.
Verification:
- node scripts/release-notes.mjs validate
- node --test scripts/release-notes.test.mjs
- bilingual render review
- git diff --check
Co-authored-by: SivanCola <[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.
Problem:
The Remote Server workspace input displayed ~/project as a placeholder while its real value was empty. Fresh hosts therefore had no enterable zero-configuration workspace, and status-bar opens were redirected to manual setup.
Root cause:
Frontend workspace resolution stopped after the last-opened and configured values even though the bootstrap already resolves ~ to the authenticated SSH user home.
Fix:
Centralize remote workspace resolution and fall back to ~ for fresh hosts. Use the same resolver in the Server tab and status-bar launch path, show ~ in the input, and remove the now-dead missing-workspace prompt.
Verification:
- pnpm test:typecheck
- pnpm test:remote (101 assertions)
- pnpm build
- git diff --check
Problem:
EnsureServer published the new workspace's "starting" state and cleared the
token before launching the Serve; when the launch or the tunnel Replace
failed, it then published the new workspace's error view over the still-
running previous Serve. Stop/Logs afterwards targeted the failed workspace,
the previous token was lost, and the frontend error state allowed stopping
the wrong workspace.
Root cause:
Only previousServer was captured; the failure paths unconditionally published
the failed view (and an empty token), transferring ownership of the host's
server state away from the Serve that actually keeps running.
Fix:
- Capture previousToken alongside previousServer.
- On ensureServe or tunnel Replace failure, publishFailedServeStart restores
the previous ready view and token when one exists (forward Set.Replace is
atomic, so the previous tunnel stays live on failure); the error still
surfaces through the EnsureServer return value and the caller's actionErr.
First-start failures keep publishing the informative error view.
- Add manager-level tests for the ensureServe-error and Replace-error paths
asserting Stop and Logs keep operating on the previous workspace, plus the
first-start error view; the Replace-failure test also asserts the just-
started Serve is cleaned up.
- Align openRemoteWindowForHost's comment with the committed-switch contract.
Verification:
- cd desktop && go test -run 'TestEnsureServer' green; targeted -race green
- Full desktop suite green modulo a pre-existing macOS TempDir/EBADF
environment flake (reproduced identically on pristine origin/main-v2,
2/6 runs, test TestWorkspaceChangeDetailBoundsTrackedPatch cleanup only)
Co-authored-by: SivanCola <[email protected]>
Problem:
Upstream #7465 added desktop/open_local_path.go with a Remote Workbench
capability gate that referenced the removed activeRemoteWorkbench().
Root cause:
The file landed on main-v2 after the workbench removal branch diverged, so the
rebase applied it without the deleted projection API.
Fix:
Remove the gate and the stale comment: every session is local now, so remote
workspace paths never reach this binding.
Verification:
- cd desktop && go build . and go test ./... green on the rebased base
Co-authored-by: SivanCola <[email protected]>
Problem:
Re-opening a host spawned a short-lived handoff process whose Wait deleted the
host's window registration, so the still-running window became invisible to
disconnect/stop/quit and to reconnect re-pointing. Tickets were also only
cleaned by a parent-process AfterFunc, so a crashed parent could leave an
expired token file on disk.
Root cause:
The registry stored a single child per host and replaced it on every spawn,
and consumeRemoteWindowLaunch never checked the ticket's age.
Fix:
- The registry now tracks a per-host process set with a per-entry generation:
each spawn appends its own entry, and Wait clears only that entry. A handoff
process exiting at the single-instance gate no longer clears the live
window, so explicit disconnect/stop, real quit, and reconnect refresh still
reach the window. Add the handoff regression test (live window + exited
handoff) at both the registry and App level.
- consumeRemoteWindowLaunch rejects tickets older than the TTL (mtime check)
and deletes them, bounding leftovers even when the spawning process died.
- OpenRemoteWorkspace documents the two-phase contract precisely and gains a
regression test for the window-open-failure path (serve stays ready for the
new workspace; the recorded last workspace matches the running serve).
- Drop the stale compactRatioRemote field from the frontend SettingsView.
Verification:
- go test ./desktop: registry handoff/generation, TTL expiry, disconnect-after-
handoff, and window-open-failure suites green
- cd desktop && go test ./... green
Co-authored-by: SivanCola <[email protected]>
Problem:
The removed Remote Workbench left remote-mirrors/ and
remote-provider-trust.json behind, its dedicated docs stayed in the tree, and
shared code comments still described the Broker/projection model.
Root cause:
No cleanup surface was designed when the protocol stack was deleted, and the
docs/comments were not part of the removal.
Fix:
- Settings -> Remote SSH now scans for legacy artifacts (path-free: mirror
count and bytes, trust-file presence) and shows a "Legacy Remote Workbench
data" card only when data exists. Mirrors and the trust file are cleaned
separately behind a confirmation dialog; the backend accepts only the two
whitelisted targets inside the Reasonix private directory and rejects
symlinks and path escapes. Historical source=remote statistics are
deliberately not deletable.
- Remove docs/REMOTE_WORKBENCH(.zh-CN).md; GUIDE/CLI remote sections now
describe the restored web window and state that remote pages use the remote
host's provider configuration and API keys.
- CHANGELOG Unreleased documents the removal, the remote-provider semantics,
and the one-release command stubs; stale Remote Workbench/Broker comments in
shared boot/provider/jobs/control/stats code are rewritten.
Verification:
- go test ./desktop (legacy scan/cleanup/symlink/escape/idempotency suites)
- Frontend remote-hosts-page suite extended with card visibility, separate
cleanup, and cancelled-cleanup cases (16 assertions green)
- reasonix remote attach-workspace/runtime-workbench/workbench-build-id all
exit 1 with the connect pointer
Co-authored-by: SivanCola <[email protected]>
Problem:
The frontend still exposed the Remote Workbench projection: a workbench target
store and events, the Provider Trust modal, target tokens threaded through
Goal submission, and a dead compactRatioRemote banner — while the backend
stack had been removed.
Root cause:
The App.tsx target state, goalSubmit target tokens, bridge Workbench* surface,
and the settings banner were all built for the same-window projection.
Fix:
- Delete workbenchTarget.ts, ProviderTrustDialog.tsx, the generated
remoteProtocol.generated.ts, and their tests. App.tsx keeps only the local
layout_style "workbench" surface (sidebar, dock, chrome) untouched.
- Remove every Workbench* bridge method, the remote:workbench-target and
remote:provider-trust subscriptions, and the target token from
activateGoalAndSubmitOnTab / SubmitInitialGoalToTab. Remote panel and
status bar converge on the single "Open Remote Web" entry, which still
connects over SSH, waits, resolves the last workspace, and calls
OpenRemoteWorkspace; a missing workspace still routes to the Server tab.
- Remote Server tab: one "Open Remote Web" action (start serves and opens the
web window idempotently), plus the fixed hint that models, API keys, and
sessions are managed by the remote Reasonix configuration. Remote serve
progress continues to flow through remote:server events.
- Delete the unused EnsureRemoteServer binding and the compactRatioRemote
settings field/banner.
- test:remote drops the workbench/provider-trust suites and adds a Server-tab
suite (entry label, provider hint, Serve progress states, workspace-missing
guard, open call) via a new svg-stub loader for component tests.
Verification:
- pnpm test:typecheck, pnpm test:remote (92 assertions), pnpm test (4008
assertions), and pnpm build all green
- cd desktop && go test ./... green after the binding/field removal
Co-authored-by: SivanCola <[email protected]>
Problem:
Remote Workbench (rpcwire JSON-RPC, schema codegen, Provider Broker,
TargetManager, workbench projection in the main window, and system OpenSSH
transport) was superseded by the restored SSH Serve web window. The full
stack, its build ldflags, and its hidden CLI commands had to disappear.
Root cause:
The Workbench was added in #6728 and its same-window projection made every
controller path conditional on an active remote target.
Fix:
- Delete internal/remote/{workbench,protocol,protocolgen,broker},
internal/rpcwire, internal/remote/bootstrap/workbench.go, and
cmd/remote-protocol-gen. eventwire stays (shared with CLI/serve/desktop).
- Delete the desktop workbench layer (workbench_adapter, projection,
runtime_adapter, ssh) and the system OpenSSH/AskPass/Job Object code
(remote_askpass, remote_ssh_transport, remote_ssh_process). Normal remote
SSH keeps using the shared internal/remote.Client.
- Remove every activeRemoteWorkbench()/activeWorkbenchTargetIsRemote() gate
from app.go, tabs.go, workspace_changes.go, terminal.go,
memory_suggestions.go, settings_app.go, and stats_app.go, restoring pure
local controller semantics. The local layout_style "workbench" and the
shared Wails remote bindings (hosts, SFTP, forwards, serve) are preserved.
- remote attach-workspace / runtime-workbench / workbench-build-id stay
routable for one release as exit-1 stubs pointing at
`reasonix remote connect <host> --open`.
- Drop the protocol source-revision ldflag from scripts/desktop-build.sh and
update the packaging test; refresh releaseasset comments to describe remote
Serve CLI provisioning.
Verification:
- go test ./... (root) and cd desktop && go test ./... all green
- go vet and gofmt clean on both modules; go mod tidy both
- Static closure: no workbench/protocol/rpcwire references outside the
intentional CLI stubs (frontend convergence lands in the next commit)
Co-authored-by: SivanCola <[email protected]>
Problem:
Opening a remote SSH workspace projected the session into the main window
through the Remote Workbench RPC stack, and the pre-Workbench ticket-based
Serve HTML child window had been deleted, leaving no web-mode entry.
Root cause:
b9e036b84 removed the remote-window tickets, spawning, middleware, and
lifecycle branches when the App bindings migrated to TargetManager.
Fix:
- OpenRemoteWorkspace is the idempotent web launcher again: EnsureServer
(install/reuse), atomic 127.0.0.1:0 tunnel Replace, token appended to the
first-visit URL, last workspace saved, then open/re-point the host window.
A failed switch leaves the previous window and tunnel untouched.
- Restore the mode-0600 one-shot ticket (size bound, 2-minute TTL, consume
deletes, loopback-only URL validation, JSON-escaped navigation JS). argv
carries only the ticket name and a non-secret per-host digest.
- Child windows derive a per-host Wails SingleInstance identity from the host
digest: reopening a host routes the new ticket to the existing window,
which consumes it, updates the title, navigates, restores, and focuses.
Different hosts run independent windows in parallel.
- The main process keeps a generation-based child registry so a stale Wait
can never clear a newer registration; real quit terminates survivors, while
closing a window only releases its registration (Serve and SSH stay up).
- Terminal SSH failure or explicit disconnect/stop-server closes the host
window (the reason already surfaces via remote:status); transient
reconnects keep it and re-point it at the rebound tunnel URL.
- Child processes expose no Wails bindings, menu, or file drops and skip
local tabs/tray/heartbeat/providers.
Verification:
- go test ./... in desktop, including new ticket, registry, host-identity,
lifecycle, middleware, atomic-switch, and reconnect tests
- go test -race on the remote window and open-workspace suites
- gofmt/go vet clean
Co-authored-by: SivanCola <[email protected]>
Problem: Long parallel_tasks and fleet answers could exceed the single-tool output limit, combining and truncating independent child reports.
Root cause: Both aggregators concatenated every full child answer before the global output guard applied head/tail truncation.
Fix: Persist each child transcript, return bounded fair previews with stable references, and add a conversation-scoped read_subagent_result pager with lineage and workspace checks.
Verification: go test ./... -count=1; go test -race ./internal/agent -count=1; ./scripts/cache-guard.sh; git diff --check.
Problem:
Goal mode relied on assistant footer markers and treated missing or malformed markers as a reason to keep opening turns. Delivery readiness also owned a separate retry loop, allowing simple answers or stalled work to consume tokens until a hard turn ceiling.
Root cause:
Completion state was encoded in free-form text, continuation ownership was split between Goal and Delivery, and the host lacked a unified evaluator, progress signal, and token budget.
Fix:
Add the stable update_goal tool, epoch-scoped turn reports, a bounded independent evaluator, centralized Goal readiness decisions, progress-aware budgets, usage accounting, and compatible CLI/Desktop/remote runtime state. Delivery now reports structured readiness without scheduling hidden retries.
Verification:
- go test ./...
- cd desktop && go test ./...
- cd desktop/frontend && pnpm typecheck && pnpm test:all && pnpm build
- go test -race ./internal/control ./internal/agent ./internal/recovery ./internal/goaleval ./internal/tool/... ./internal/evidence
- ./scripts/cache-guard.sh
- go run ./cmd/remote-protocol-gen -check -root .
- git diff --check
Problem: The desktop style selector is buried below language and currency even though it changes the application's primary layout.
Root cause: General settings render the selector as the third field.
Fix: Render the existing desktop style field first without changing its behavior, and add a DOM ordering regression assertion.
Verification:
- pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- pnpm exec eslint src/components/SettingsPanel.tsx src/__tests__/settings-refresh-snapshot.test.tsx
- pnpm typecheck
- git diff --check
* feat(deepseek): add server-side web search controls
* Harden DeepSeek web search defaults and replay
Problem:
- Existing official DeepSeek configurations did not inherit web search defaults.
- Responses API search output was discarded between stateless turns.
- Endpoint gating and billing copy were broader than the documented contracts.
Root cause:
- web_search used a two-state boolean, endpoint checks matched hosts loosely, and the shared stream/session model had no slot for provider-issued Responses items.
Fix:
- Resolve web_search as a persisted tri-state on exact official endpoints with explicit opt-out.
- Persist validated completed web_search_call items and replay them only on official DeepSeek Responses.
- Carry search replay items across the remote Broker protocol with bounded JSON validation.
- Align endpoint gating and cost copy with the official contract and add regression coverage.
Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/provider/... ./internal/config ./internal/agent ./internal/remote/protocol ./internal/remote/broker
- pnpm --dir desktop/frontend test:all
- pnpm --dir desktop/frontend lint:hooks
- ./scripts/cache-guard.sh
- go vet ./...
* fix(desktop): account for Traditional Chinese search copy
Problem:
- The Desktop and desktop-windows CI jobs failed because the Traditional Chinese locale compressed to 52.1 KiB against a 52 KiB budget.
Root cause:
- The new server-side web search controls intentionally add localized capability, privacy, billing, protocol, and model guidance, while the zh-TW chunk had no remaining budget headroom.
Fix:
- Give only the zh-TW on-demand locale a 53 KiB budget while retaining the 52 KiB Simplified Chinese limit and every existing initial bundle limit.
Verification:
- pnpm build
- pnpm test:all
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/provider/... ./internal/config ./internal/agent ./internal/remote/protocol ./internal/remote/broker
- ./scripts/cache-guard.sh
* fix(desktop): simplify local file URL normalization
Problem:
- The Desktop CI job failed staticcheck S1017 on the latest main-v2 integration tree.
Root cause:
- normalizeLocalOpenPath checked for the file URL prefix before calling strings.TrimPrefix, even though TrimPrefix is already a safe no-op when the prefix is absent.
Fix:
- Normalize the optional file URL prefix unconditionally while preserving plain absolute path behavior.
Verification:
- go test . -run 'TestNormalizeLocalOpenPath|TestOpenLocalPath' -count=20
- go vet ./...
- golangci-lint run --timeout=5m
* test(cli): make unknown slash routing deterministic
Problem:
- Coverage and Ubuntu CI intermittently failed TestUnknownSlashCommandDoesNotStartTurn.
Root cause:
- Since #5756, unknown slash input intentionally starts an ordinary model turn with a typo notice, but the stale test asserted that no turn starts. Because controller submission is asynchronous, the assertion raced the runner and passed or failed depending on scheduling.
Fix:
- Assert the current routing contract, wait for TurnDone, and verify exactly one ordinary turn plus the visible unknown-command notice.
Verification:
- go test ./internal/cli -run TestUnknownSlashCommandStartsOrdinaryTurnWithNotice -count=100
- go test -race ./internal/cli -run TestUnknownSlashCommandStartsOrdinaryTurnWithNotice -count=20
- go test ./internal/cli -count=1
Two stream-robustness fixes in the OpenAI-compatible provider (DeepSeek,
MiMo, MiniMax-M3, and any OpenAI-compatible gateway).
1. A watchdog stall bypassed stream recovery. The idle watchdog exists to
turn a half-open TCP drop — a local proxy (v2rayN/sing-box) that silently
drops the long-lived SSE connection during a reasoner's first-token gap,
with no FIN/RST — into a recoverable error. It detected the stall but
returned a plain "stream stalled" error with no sentinel, while the
clean-FIN idle-close path wraps io.ErrUnexpectedEOF. streamWithReconnect
gates reconnect/StreamInterruptedError on provider.IsConnReset, which the
plain error failed, so the stall hard-failed the turn instead of replaying
— even though a clean idle-close on the same connection recovered. Wrap
the stall error with io.ErrUnexpectedEOF so the half-open stall is treated
identically to the clean-FIN drop.
2. An empty or heartbeat `data:` line fatally aborted the whole stream.
readStream unmarshaled every data: payload with no empty guard, so
json.Unmarshal("") returned "unexpected end of JSON input" and ended the
turn with a confusing decode error. The Anthropic and Responses transports
both tolerate empty data: lines; the OpenAI transport was the outlier. Skip
empty payloads like Anthropic does.
Cancellation and deadline errors still bypass reconnect (IsConnReset excludes
context.Canceled/DeadlineExceeded), verified by TestStreamCancelDoesNotReconnect.
Apply agent.UserPreviewText at the three ingestion seams (LoadBranchMeta, desktop title sidecars, auto-title derivation) so polluted legacy titles self-heal and wrappers can't seed new titles. Fixes the remaining half of #5666
Plain Windows drive paths, UNC paths and file:/// URLs in AI replies are now rendered as clickable links that open via the native OpenLocalPath binding instead of the system browser.
- remark plugin rewrites path text nodes into file:/// links (CJK punctuation, escaped spaces, trailing closers, %xx round-trip)
- RichMarkdownLink routes file:/// clicks to OpenLocalPath; http links keep using the system browser
- new OpenLocalPath backend binding: absolute-path validation, remote-workspace rejection, executable-target refusal
- 48 unit cases + 10 headless click e2e cases; Go tests included
* feat(evidence): recognize swift test as a delivery verification command
Why: Swift projects using SwiftPM had no host-recognized verifier for
delivery finalization, so every sign-off fell back to git diff --check.
Add the classifier branch so swift test (like cargo/dotnet test) counts
as a non-mutating verification, while swift build/run/package stay
fail-closed mutations since they emit binaries or mutate the package.
* fix(evidence): reject swift test --xunit-output as a verifier
Why: review found swift test --xunit-output report.xml writes a JUnit
report to an arbitrary path but was still classified as a non-mutating
verification. Add xunit-output to writeOutputFlags (same treatment as
pytest --junitxml / gotestsum --junitfile) and cover it in the Swift
negative cases; document the --enable-code-coverage boundary.
* fix(evidence): reject swift test scratch-dir redirects as verifiers
Why: review found --scratch-path (legacy alias --build-path) redirects
the SwiftPM build dir outside the package, and --generate-linuxmain was
flagged as a source-tree writer. Verified against Swift 6.0.3: only
--scratch-path exists, so add it to writeOutputFlags and document the
boundary; cover the = form of --xunit-output and back the
--enable-code-coverage acceptance with a test.
* fix(evidence): actually reject swift test --build-path
Why: the previous commit's comment claimed --build-path is a legacy
alias of --scratch-path but only scratch-path was added to
writeOutputFlags, leaving swift test --build-path fail-open. Add both
flags and cover the legacy form in the negative test cases.
* test(evidence): cover --build-path equals form in swift negatives
Why: review nit noted --xunit-output tests both space and equals forms
but --build-path only the space form; the equals form exercises the
same TrimLeft/equals-trim path as other flags, so pin it explicitly.
* fix(evidence): reject Swift 6.x event-stream and attachments flags
Why: security review found swift test (Swift 6.x) gained four write
flags not covered by writeOutputFlags: --event-stream-output-path /
--experimental-event-stream-output (swift-testing JSON output) and
--attachments-path / --experimental-attachments-path (Swift Testing
attachments dir). All four write to caller-specified paths and would
bypass the mutation gate. Add them with negative test cases and note
the Package.swift build-plugin trust boundary in the swift comment.
* fix(evidence): align gofmt, reject cache-path and swift control modes
Why: security review flagged gofmt misalignment (CI would fail), the
--cache-path shared-cache redirect was not rejected, and control modes
(--help/-h/--version/--list-tests/-l) that do not run the test suite
could masquerade as verification receipts. Mirror the tsc treatment of
--help in the swift branch and add negative cases.
* fix(config): stop custom providers inheriting DeepSeek's defaults
A user-defined provider silently acquired DeepSeek's balance_url, USD price
table and context_window = 1000000 the first time its config was loaded, and
those values were then persisted into the user's own file on the next rewrite.
TOML array-of-tables decoding is positional: BurntSushi/toml unifies
[[providers]][i] onto whatever the destination slice already holds at index i
rather than onto a zero value. Every config load seeds from Default(), which
ships two official DeepSeek entries, so the first two [[providers]] a user
declares were overlaid onto them and inherited every field left unset.
Consequences were not limited to cosmetics. A self-hosted endpoint was pointed
at api.deepseek.com/user/balance, cost readouts applied a foreign price table,
and compaction was sized against a 1M window instead of the real one, so it
never fired before the endpoint began rejecting requests.
Re-decode the providers array onto an empty slice so a declared entry carries
only what the file declares. Endpoint-keyed backfills still apply the defaults
that are genuinely correct for an entry, so official DeepSeek providers keep
their pricing.
Fixes#7357Fixes#7358
* fix(config): keep official DeepSeek endpoint defaults after provider isolation
Isolating the decoded provider list stopped custom providers inheriting
DeepSeek's fields, but it also dropped context_window and balance_url for
configs that declare the official endpoint and legitimately omit them. Runtime
then saw ContextWindow == 0, which disables compaction, and an empty
balance_url removes the wallet readout.
Reapply both from backfillDeepSeekOfficialPrices, which already runs on the
runtime and edit loaders and already matches on api.deepseek.com. Keying on the
endpoint is what makes the values safe to infer, and is why this cannot leak
onto a custom provider the way the positional decode overlay did.
Declared values are never overwritten, so narrowing the window for the official
endpoint still works.
This also fixes a pre-existing gap: on main-v2 the runtime loader already
returned ContextWindow == 0 for an official provider that omitted the field,
because only the edit loader benefited from the accidental overlay.
The 256 KB LimitReader on model-list responses truncates OpenRouter's
~531 KB response, causing a JSON parse failure. Raised to 2 MiB with
overflow detection (the +1 pattern used throughout the codebase), and
extracted into a named constant matching the project's conventions.
Fixes#6746
runHookCommand defaulted options.homeDir to config.ReasonixHomeDir(),
but hook.Inspect treats HomeDir as an OS user home and appends .reasonix
inside the hook package. The double append resolved global hooks from
<home>/.reasonix/.reasonix/settings.json instead of the real
<Reasonix home>/settings.json, so 'reasonix hook status|list --json'
reported global hooks as missing everywhere, and on Windows even the
single .reasonix append was wrong (%AppData%\Roaming\reasonix).
Leave HomeDir empty so the hook package resolves the platform Reasonix
home itself — correct on every OS and with --home-dir overrides
unchanged.
Fixes#7420
Problem:
Versioned installations can inherit a valid pending update transaction that still targets the former flat install layout. Recovery rejects that transaction as outside the current Guard installation, so every later update remains blocked even after reinstalling.
Root cause:
The superseded-transaction recovery path called the ordinary transaction reader. That reader correctly requires rollback targets to belong to the current launcher directory, but the requirement makes the recovery path reject the exact flat-to-versioned transition it was designed to heal.
Fix:
Validate superseded transactions through a dedicated fail-closed path that proves the current executable is the active versioned desktop, reuses the ordinary transaction and backup validation, and requires every old release member to belong directly to the same flat install root. Re-read the full transaction identity under the pending lock, archive with no replacement, verify the moved transaction, and preserve rollback backups. Run recovery before download and again before install so users do not pay bandwidth or retry costs for a recoverable state.
Verification:
Root and Desktop Go tests and vet passed. Focused repair and updater tests, deterministic concurrency coverage, race tests, repeated recovery runs, and Windows amd64 cross-compilation passed.
Extend the [no_changes] contract to answer-only turns so the relay path handles pure Q&A (one answer, no executor round); strip the contract lines from display like [goal:*]. Fixes#6789
Reuse UserMessageText / DisplayAssistantText at ACP replay so injected wrappers and protocol markers never reach the client; history untouched. Fixes#6882
The model usage charts (per-model donut and stacked daily trend) drew every
model in one monochrome accent ramp, which review feedback found too uniform
("all one colour"). Replace the ramp with GitHub Primer's official data-viz
tokens as a fixed two-set palette — --chart-1..5 plus the gray --chart-other,
light and dark variants defined in styles.css — so the top five models each
keep a distinct rank colour and everything beyond collapses into the gray
"Other" step, with no JS theme detection.
Each series colour is mixed 72% toward --bg-elev like the heatmap levels, so
the pure hexes sit softly on the card in every theme style and theme pack.
The dark orange uses the palette's brighter #c46212 step because the official
#984b10 emphasis reads brownish on the dark background. Palette values were
verified against primer.style and the published @primer/primitives npm CSS.
Also folds in review fixes: the command-palette usage-stats entry now consumes
its focus target once (one-shot subtab switch, mirroring BotsSection), and the
expandable "Other" row is keyboard-accessible (role=button + Enter/Space)
instead of click-only. Chart colour mapping is pinned by the usage-stats panel
assertions (18/18). No data or storage format changes — stats stay append-only
daily JSONL, so existing users' data renders unchanged.
Unknown slash input falls through to a normal model turn at the controller fallthrough and the TUI's local dispatch, with the typo notice kept. MCP prompt namespace misses keep their hard error. Fixes#5756
Content-bearing sessions outrank content-free ones in topic resolution; newest-wins only among equals. Lazy newest-first content probe keeps the common case at one file read. Fixes#7305
Unify assistant display filtering into agent.DisplayAssistantText (goal markers + evidence blocks), applied at both Message emission points; controller reuses the shared strip; TUI adopts the canonical Message text. History stays raw for parsing. Fixes#6665
Double Ctrl+C, Ctrl+C after cancel, Ctrl+D, typed exit/quit/:q, and /quit //exit returned tea.Quit directly, skipping the Snapshot + lease-follow finalization. Route all quit gestures through tuiShutdownMsg so every exit persists the session. Fixes#5879
The section claimed no Authenticode certificate exists. Stable Windows
builds have been signed through SignPath since desktop-v1.14.1, and the
release fails if any payload binary is unsigned — the stale text sent at
least one reporter chasing signing as the cause of an unrelated network
problem. macOS is still unsigned; say so separately.
WinHttpGetIEProxyConfigForCurrentUser fails on a service account or an
RDP session with no per-user IE config, and the failure was returned as
a plain nil — indistinguishable from "the system is set to direct". Log
the errno, and log which of the three sources were present when nothing
matched, so a corporate machine that can't reach the API is diagnosable
from the debug log instead of by guessing.
The Wails drag runtime drops any mousedown with detail != 1, so a double
click on a --wails-draggable region is swallowed rather than reaching the
OS. Windows already handled it here; macOS hides its title bar the same
way and had nothing, so double-clicking the header did nothing at all.
The panel printed the raw porcelain code, so an untracked file showed as
"??" and staged/unstaged pairs as "MM". Map the codes to reader-facing
labels in all three locales, passing unknown codes through unchanged.
The unmerged set is matched as whole codes: AA and DD carry no U and
would otherwise read as a plain add or delete.
Co-authored-by: GTC2080 <[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.
Workbench rendered the collapse/expand control first in the action row,
so it sat left of copy, export, changed, terminal and the palette —
visually mid-bar rather than against the edge of the panel it controls.
Creation already rendered it last; render one block at the end for both.
Prompts that open with @path references produced a preview — and so a
sidebar title — made entirely of paths, with the actual request pushed
past the 80-rune clamp. Skip the leading reference run; a prompt that is
nothing but references keeps them.
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.
A gateway putting non-JSON on a data: line produced only "invalid
character 'D' looking for beginning of value", which identifies neither
the sender nor what arrived — the reports it generates are unactionable.
Quote a bounded excerpt of the offending payload instead.
A rate-limit window is routinely longer than the 15s exponential-backoff
ceiling, and clamping the server's Retry-After to it spent attempts
re-hitting the same closed window until MaxRetries ran out. Honor the
header up to 60s, and accept its HTTP-date form as well as delta-seconds
— gateways in front of rate-limited backends send both.
Thinking headers, tool card labels, attachment and pasted-block chrome,
memory citations and the inline edit box carried literal pixel sizes, so
Ctrl+= and the appearance setting left them fixed while the surrounding
message text grew. Point them at the proportional tokens the transcript
region already redeclares.
os.ReadDir on a regular file reports ENOTDIR on unix but ENOENT on
Windows, so the IsNotExist branch accepted a corrupt store as "no
subagents yet" and startup continued past it. Confirm the path is
really absent before taking that branch.
TestNewSubagentStoreNeverCachesCleanupError failed on every Windows
checkout for this reason.
A deep or network-backed tree could keep glob walking for tens of
minutes with no way out short of interrupting the turn. Mirror grep:
default the walk to 30s, accept timeout_seconds up to 300, and return
the matches found so far labelled as incomplete rather than an error.
Cancellation still surfaces as an error so an interrupt is not mistaken
for a finished search.
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.
Keep the generated bilingual notes focused on user outcomes, replace the duplicate version title, and declare every affected product surface.
Verification: release catalog validation; release note rendering tests; public PR citation review.
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: Prepare release exhausted or truncated the structured response because DeepSeek v4-pro spent the shared generation budget on hidden reasoning.
Fix: disable thinking for deterministic release-note generation, retain the 8K JSON budget, surface the final finish reason, and lock the request contract into the release workflow test.
Verification: node --check scripts/generate-release-notes.mjs; bash scripts/release-workflows.test.sh; git diff --check.
Problem:
The formal frontend test gate could not run because the token-format test imported an undeclared Vitest dependency. After that was exposed, a subagent layout contract still asserted the pre-responsive CSS value.
Root cause:
The token-format test was verified outside the repository test runner and never added to the package scripts. The responsive subagent layout fix changed minmax(200px, 1fr) to minmax(0, 1fr) without updating its source contract test.
Fix:
Use the repository's Node test APIs, execute the token-format coverage from pretest, and align the subagent contract with the shrinkable equal-column layout.
Verification:
pnpm --dir desktop/frontend exec tsx src/__tests__/format-tokens.test.ts
pnpm --dir desktop/frontend exec tsx src/__tests__/capabilities-panel-actions.test.ts
pnpm --dir desktop/frontend test:all
pnpm --dir desktop/frontend build
The approval banner clipped the subject to one line, so a long command
hid the part that decides the answer — the path being written or the
flag that makes it destructive. Clipped subjects now expand below the
banner, wrapped and bounded so a heredoc cannot push the composer away.
The legacy-path migration copied sessions, projects, skills, archive and
hooks.json, but global hooks live in settings.json — so upgrading to the
unified home silently emptied them. Adds settings.json, and stops any
file item from overwriting one already present at the destination.
Problem:
Dependabot reports 24 open alerts across the frontend and Worker lockfiles for vulnerable Hono, Undici, and PostCSS versions.
Root cause:
The Hono manifests allowed an affected release, while Miniflare and jsdom still pin Undici 7.28.0 transitively. The crash-report Vite tree also retained an affected PostCSS release.
Fix:
Raise the Hono security floor, pin Undici 7.29.0 through package-manager-native overrides, pin PostCSS 8.5.23 for crash-report, and regenerate all affected lockfiles.
Verification:
- pnpm/npm audit in all four projects
- Worker TypeScript checks
- crash-report Vitest suite (83 tests)
- desktop frontend production build and bundle budgets
- Wrangler dry-run builds for all three Workers
git tag counted as read-only regardless of arguments, so `git tag -d` and
tag creation passed the read-only boundary that plan mode and capability
runners rely on. Restore/checkout-discard/stash-drop carried no danger
label either, which also let them earn a broad session prefix grant.
A planner_model naming a provider that no longer resolves failed the
whole build, so the desktop opened into a startup error with no usable
chat — a stale ref from recovered config was enough. The planner is
optional; it now degrades to executor-only with a notice, matching how
the guardian model already behaves.
Problem:
The new documentation-impact workflow checks out the PR head, but this branch predates the guard script and fails before evaluating the PR declaration.
Root cause:
The branch was behind main-v2 after the documentation guard and later desktop fixes landed.
Fix:
Merge current main-v2 so the PR is evaluated with the same guard scripts and integration baseline used by the target branch.
Verification:
- merge completed without conflicts
- focused cleanup and desktop tests passed on the merged tree
- focused race tests passed on the merged tree
- root full suite and vet checks passed on the preceding latest-base integration tree
Co-authored-by: SivanCola <[email protected]>
creationEmptyHero collapsed .main for empty sessions, which also hosts SidebarImConnectionDetail. Exclude sidebarImDetailConnection and assert in app-chrome-tabs.
Problem:
Concurrent desktop controller builds could bypass an in-flight sweep, while the permanent process marker prevented later crash leftovers from being repaired.
Root cause:
A boolean sync.Map conflated in-progress and completed cleanup and never invalidated successful entries.
Fix:
Run cleanup on every build, protect other live desktop parents with a process-local liveness probe, and let an initial build repair its own crash leftovers before binding the session lease. Keep the bounded startup lease retry as defense in depth.
Verification:
- go test ./internal/agent ./internal/boot -run Test(SubagentStoreCleanupStaleRunning|NewSubagentStore) -count=20
- go test -race ./internal/agent ./internal/boot -run Test(SubagentStoreCleanupStaleRunning|NewSubagentStore) -count=5
- cd desktop && go test . -run Test(SessionParentLive|CleanupStaleRunningWithDesktopParentProbe|WithSessionLeaseContentionRetry) -count=20
- cd desktop && go test -race . -run Test(SessionParentLive|CleanupStaleRunningWithDesktopParentProbe|WithSessionLeaseContentionRetry) -count=5
- go test ./...
- go vet ./internal/agent ./internal/boot
- cd desktop && go vet .
Co-authored-by: SivanCola <[email protected]>
Co-authored-by: siwenyu666-beep <[email protected]>
Topic-state writers ran MkdirAll on <root>/.reasonix, so a workspace
folder deleted outside Reasonix came back on the next launch — the tab
still pointed at it and the first title write rebuilt the whole path.
Project-scoped writes now require the root to still exist; the global
store is unaffected.
Keep the headline above the slim composer in the same footer stack so the pair centers together. Also force-remove footer border-top under Creation (theme-style specificity).
Problem:
The Desktop bot bridge remained a second IM delivery sink that forwarded every warning, including recovery notices explicitly scoped to local operators.
Root cause:
The new notice-audience policy was enforced only by the CLI bot renderer. The Desktop event forwarder still treated warning severity as end-user delivery policy.
Fix:
Skip operator-scoped notices before the Desktop forwarder queues IM output, while preserving ordinary user warnings. Add a deterministic queue-level regression that proves the two behaviors together.
Verification:
- cd desktop && go test . -run TestBotEventForwarder -count=1
- cd desktop && go test -race . -run TestBotEventForwarder -count=1
- cd desktop && go test ./...
- git diff --check
Problem:
- documentation search accepted unbounded queries before tokenization and BM25 scoring
- delimiter-only corpus hashing did not encode file boundaries unambiguously
Root cause:
- the shared search path had no provider-visible or runtime query limit
- digest input concatenated names and bytes with a delimiter that valid UTF-8 content could contain
Fix:
- cap search queries at 4096 Unicode characters in both schema and runtime
- hash a versioned stream of length-prefixed file records
- add regression coverage for both entry paths and the legacy framing collision
Verification:
- go test ./...
- go test -race ./internal/productdocs ./internal/boot ./internal/control ./internal/cli ./internal/remote/workbench/runtime
- ./scripts/cache-guard.sh
- pnpm --dir desktop/frontend build
- npm test and npm run build in site/
Documentation:
- add bilingual guidance for the embedded documentation command
- explain qualified-name fallback when another skill owns /docs
Verification:
- npm test in site/
- npm run build in site/
The GitHub API fallback ran anonymously, and that quota is 60/hour per
IP — shared by everyone behind the same NAT, so `reasonix upgrade` could
fail with 403 on a network where nobody had upgraded that hour. Uses
GITHUB_TOKEN/GH_TOKEN when set, and says so when a refusal is the quota.
Problem:
Successful local session recovery emitted Warn notices that the CLI bot gateway forwarded to WeChat, QQ, and Feishu as if they were actionable user warnings.
Root cause:
Notice severity doubled as delivery policy. Recovery events had neither an explicit audience nor stable identities, so every Warn notice was treated as an end-user chat message.
Fix:
Add a backward-compatible operator notice audience and stable recovery codes, mark every controller recovery outcome as operator-only, and suppress only that audience in the bot renderer. Preserve ordinary user warnings and local recovery handling. Use the stable codes for Desktop localization and quiet-notice routing, with text fallback for older records.
Verification:
- go test ./...
- go test -race ./internal/event ./internal/control ./internal/bot
- go vet ./...
- cd desktop && go test ./...
- pnpm build
- focused frontend recovery notice tests
Problem: CleanupStaleRunning released a parent session lease before skipping a corrupt metadata reread, so later refs for the same parent could be rewritten without ownership protection.
Root cause: The lease release ran before the code classified the reread failure as a skippable decode error or a fatal storage error.
Fix: Retain the parent lease for skippable decode failures, release it only before fatal returns, and add a deterministic interleaving test that corrupts the first reread and probes the lease before the second ref.
Verification: go test ./internal/agent -count=1; go test -race ./internal/agent -count=1; go vet ./internal/agent; go test ./internal/boot -count=1; go test ./... -count=1; focused cleanup tests passed 20 times normally and 5 times under race.
Co-authored-by: SivanCola <[email protected]>
Problem:
Hidden plugin command and skill aliases could own /docs at runtime while Desktop and remote menus still advertised the built-in documentation command.
Root cause:
Dispatch, CLI discovery, Desktop discovery, and remote session catalogs derived slash ownership independently, and the remote protocol did not advertise host-owned built-in routes.
Fix:
Centralize slash ownership resolution, preserve every existing command, allocate a deterministic conflict-free Reasonix-qualified fallback for built-in docs, and publish the host-resolved route through an additive optional catalog field. Update local help and bilingual documentation to show the effective invocation.
Verification:
- go test ./...
- cd desktop && go test ./...
- pnpm --dir desktop/frontend build
- pnpm --dir desktop/frontend test:composer-menu-viewport
- go test -race ./internal/productdocs ./internal/control ./internal/cli ./internal/remote/workbench/runtime
- ./scripts/cache-guard.sh
- go run ./cmd/remote-protocol-gen --check
- bash scripts/check-docs-impact.test.sh
Third review round fixes for PR #7373.
Problem: a graceful shutdown stored a nil terminal error, so a Notify after
EOF reported success for a frame that was never written; queued jobs were
completed with nil while being discarded; and the enqueue path could admit
frames after the writer exited. Separately, NewConn started the writer
goroutine eagerly with no lifecycle entry, the session-save test searched
raw JSON for an unescaped path (fails on Windows), and the redaction
regression test exercised a helper the production path did not use.
Fix:
- rpcwire: terminalError() never returns nil (a clean EOF synthesizes a
connection-closed error; Serve keeps the raw closeReason so graceful
ends still report nil). After close, the writer loop switches to a
permanent drain mode: frames enqueued in the close race are answered
with the terminal error instead of silently dropped. The writer loop
and stall watchdog now start lazily on the first write or Serve, so a
Conn constructed but never served leaks no goroutine. Regression:
TestNotifyAfterGracefulCloseFails (post-EOF Notify/Request must fail,
20x).
- control test: decode the session.save payload into
dispatch.SessionPayload and compare typed fields instead of searching
raw JSON for a path (backslashes are escaped on Windows).
- desktop: deferredReloadFailedText is now the single production
formatter for both the log and the tab warning, so the tested helper IS
the production path.
Verification: go test ./internal/rpcwire incl. -race; go test
./internal/extension/...; GOOS=windows vet; desktop redaction test;
gofmt clean.
Follow-up: close the completed-write vs teardown race deterministically.
writerBusy now spans a whole job including the result send, and shutdown
waits for the writer to go idle (bounded at 100ms; the stall watchdog owns
the wedged-write case) before closing the connection, so a finished write's
result is always visible before the terminal error. Also align the
capabilities-panel test with main-v2's narrowed subagent picker columns
(#7362 changed minmax(200px,1fr) to minmax(0,1fr) without updating the
expectation).
Integration:
Bring the contributor branch up to main-v2@80ae90e8 while preserving both the Task Monitor and responsive settings frontend test gates.
Conflict resolution:
Keep test:task-monitor and test:settings-responsive, and run both from pretest.
Verification:
- go test -count=1 ./internal/taskmonitor ./internal/cli ./internal/jobs
- go test -race -count=1 ./internal/taskmonitor ./internal/cli ./internal/jobs
- focused desktop task monitor routing tests
- pnpm test:task-monitor
- pnpm test:settings-responsive
- pnpm typecheck
- pnpm check:css
- pnpm build
Co-authored-by: SivanCola <[email protected]>
Problem:
CLI recovery GC exposed a Desktop trash entry before its authoritative event log and sidecars had moved. A crash, restore, or purge in that window could strand a split artifact set. Numeric /resume also ran GC after completion had assigned an index, allowing the index to shift before execution.
Root cause:
The cleanup protocol moved the primary transcript before publishing its durable operation marker, and the numbered resume path mutated the same ordered list it was about to resolve.
Fix:
Stage the transcript and all owned artifacts in a hidden, durably marked directory, reconcile interrupted stages on startup, and publish the complete trash directory with one same-filesystem rename. Retain compatibility for the earlier recovery-trash marker protocol. Keep numbered resume resolution read-only and update its ordering documentation.
Verification:
- go test ./internal/agent ./internal/cli
- go test -race ./internal/agent ./internal/cli
- go vet ./...
- go test ./...
- cd desktop && go test ./...
Problem:
Namespaced monitor IDs were passed directly to jobs.Manager, so Stop and Cancel could not reach their live runtime. A recorder completion or heartbeat could also win the post-kill snapshot CAS and make an admitted cancellation look like a version conflict.
Root cause:
Task snapshots did not retain the manager-local job ID, and the control path treated every CAS advance after Kill as an unrelated write.
Fix:
Persist an optional backward-compatible job_id, recover the local ID for snapshots written before that field, route cancellation through the session-scoped controller API, and reconcile expected recorder or heartbeat version advances after runtime admission. Add deterministic package and Desktop assembly regressions.
Verification:
- go test -count=1 ./internal/taskmonitor ./internal/cli ./internal/jobs
- go test -race -count=1 ./internal/taskmonitor ./internal/cli ./internal/jobs
- focused desktop task monitor routing tests
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem: CleanupStaleRunning still aborted startup for custom decoder failures such as invalid time values, and its lease-protected second read had no corruption tolerance.
Root cause: LoadMeta wrapped read and decode failures with only textual context, so callers had to enumerate concrete JSON error types.
Fix: Return a private typed decode error, skip that category at both cleanup reads, preserve fatal I/O behavior, and cover invalid timestamps alongside truncated and empty metadata.
Verification: go test ./internal/agent -count=1; go test -race ./internal/agent -count=1; go vet ./internal/agent; go test ./internal/boot -count=1; go test ./... -count=1; focused cleanup tests repeated under normal and race builds.
Co-authored-by: SivanCola <[email protected]>
Settings writes the user config, but a workspace reasonix.toml outranks
it, so toggles like sandbox and permission mode could read one way and
behave another with nothing on screen to explain it. The panel now names
the file actually in effect when it isn't the one being written.
Integration: Merge main-v2 at 8c8f9410e5 so PR #7402 remains mergeable without rewriting existing history.
Resolution: Preserve the base settings-responsive test and pretest hook while retaining the PR virtual-menu identity regression in test:composer-menu-viewport.
Verification: go test for productdocs, control, CLI, fileref, and Desktop; focused race tests; frontend typecheck; composer menu, settings responsive, app chrome, and token-format matrices; go vet and diff checks.
Problem: Stable release queries could score preview notes as exact matches, while CLI and Desktop discovery could show duplicate /docs entries when custom overrides existed.
Root cause: Version boosting matched virtual paths by substring, and discovery surfaces appended builtin, skill, and custom entries independently of runtime dispatch precedence.
Fix: Track the structured release version for exact score boosting and resolve /docs discovery to the runtime-winning custom command, skill, or builtin across CLI completion, CLI help, and Desktop.
Verification: go test ./internal/productdocs ./internal/control ./internal/cli; cd desktop && go test ./...; focused go test -race for productdocs, CLI, and Desktop; go vet for touched modules; pnpm --dir desktop/frontend test:composer-menu-viewport.
Problem:
The CreatedAt sidecar pre-seed could fail before SaveFailed reached its terminal metadata write, leaving a failed subagent durably marked as running.
Root cause:
SaveFailed returned immediately from ensureBranchCreatedAt even though its existing failure-atomic contract deliberately saved subagent metadata after transcript errors.
Fix:
Accumulate the branch sidecar error, continue transcript persistence, always record the failed terminal status, and join all storage errors. Add a corrupt-sidecar regression that proves the error is reported while failed status still reaches disk.
Verification:
- go test ./internal/agent -run "TestSubagentStoreSaveFailedPersistsTerminalMetaWhenBranchMetaIsCorrupt|TestSubagentStoreTerminalSaveKeepsBranchStartAndActivityTimes" -count=20
- go test ./internal/agent -count=1
- go test -race ./internal/agent -count=1
- go vet ./internal/agent
- go test ./... -count=1
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Second review round fixes for PR #7373.
Problem: the first write-bound attempt spawned a goroutine per write and
returned on caller-context expiry while the underlying Write kept running —
frames could interleave on one io.Writer, cancelled frames could still
land, blocked goroutines leaked, a 5s intercept ctx preempted the 10s
stall bound, and wmu acquisition itself ignored context.
Fix: exactly one writer goroutine per rpcwire.Conn, fed by a 256-slot
bounded queue. Frames are atomic and ordered by construction: the loop
skips a frame only when its caller gave up before the physical write
began, and finishes every frame it started. Callers enqueue and await
results with their own context (an abort fails only that call). An
independent stall watchdog (MaxWriteStall, default 10s on the sidecar)
fails the connection with WriteStallError when a physical write makes no
progress, and cannot be preempted by any per-call timeout; the sidecar
supervisor kills the process tree on any unexpected connection end.
Deterministic coverage: gated-writer proof that a queued-cancelled frame
never lands and no torn/interleaved bytes ever appear, no-reader pipe
stall, primer-based two-phase test where the caller cancels at 200ms and
the watchdog still fails the connection and reaps the wedged sidecar.
- Windows PID probe: switch to golang.org/x/sys/windows (Go's syscall
package has no PROCESS_QUERY_LIMITED_INFORMATION); only
ERROR_ACCESS_DENIED counts as alive — other OpenProcess failures mean
gone. GOOS=windows go vet passes.
- desktop deferred reload: the failure text reaching logs and the tab is
now secrets.RedactCredentials-wrapped (CodeQL #280); credential-shaped
regression test.
- Mechanical gates: gofmt the fake sidecar helper, and SA1012 — pass
context.Background() explicitly instead of nil.
Verification: go test -race on rpcwire (50x on the response-close and
queued-cancel tests), sidecar full package under -race, extension 9/9
packages, desktop package incl. the redaction test, golden baselines
unmodified, both protocol generators -check, cache-guard 8/8, GOOS=windows
cross-build.
Problem:
Recovered macOS app backups were preserved indefinitely, and the updater had no real protected-directory smoke coverage for the quarantine lifecycle.
Root cause:
The pending transaction did not retain ownership evidence for the quarantined tree, so terminal cleanup could not distinguish it from an unrelated sibling safely.
Fix:
Bind the exact quarantine path and digest to optional transaction fields, include that path in the mutation lock set, and remove it only after an exact terminal transition and digest verification. Preserve changed or concurrently recreated paths. Add an opt-in Darwin smoke test that exercises the real /Applications boundary with a uniquely owned synthetic app.
Verification:
- go test ./...
- go vet ./...
- (cd desktop && go test ./...)
- (cd desktop && go vet ./...)
- go test -race ./internal/repair -run 'AppBundle|PendingUpdate|Handoff|Quarantine|Orphan' -count=1
- REASONIX_MACOS_APPLICATIONS_SMOKE=1 go test ./internal/repair -run TestDarwinApplicationsOrphanBackupLifecycleSmoke -count=1 -v
- pnpm --dir desktop/frontend test:updater
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend check:css
- pnpm --dir desktop/frontend build
Problem:
The memory tail wrapper stayed a nested flex item above 900px, causing the Suggestions button to overflow the settings row near the breakpoint.
Root cause:
The grouping styles applied at every width, so the outer row shrank the wrapper without preserving the original wide-screen flex relationship.
Fix:
Flatten the wrapper on wide screens and enable its flex grouping only at the narrow breakpoint. Add a focused CSS contract test for both modes.
Verification:
- pnpm --dir desktop/frontend test:settings-responsive
- pnpm --dir desktop/frontend test:typecheck
- pnpm --dir desktop/frontend build
- Browser measurements at 900, 901, 910, and 920px
Co-authored-by: SivanCola <[email protected]>
Problem:
Recovery-family presentation ordering made --continue prefer an older recovery leaf over a more recently active parent session.
Root cause:
The non-interactive continue path reused the interactive picker ordering even though --continue promises to resume the most recent session.
Fix:
Select --continue targets directly from the chronological session listing while keeping recovery-family grouping in interactive resume surfaces. Add a regression test that proves the two ordering contracts remain independent.
Verification:
- go test ./internal/cli
- go test -race ./internal/cli
- git diff --check
Problem:
Seeding branch metadata with the subagent start time also assigned that value to UpdatedAt, so newly completed or failed runs appeared stale in recency-based session ordering.
Root cause:
Session.Save preserves an existing branch UpdatedAt. The pre-seeded start timestamp therefore survived the terminal transcript save and became ListSessionOrder.LastActivityAt.
Fix:
Seed only BranchMeta.CreatedAt and let the terminal metadata write assign UpdatedAt. Replace the timing sleep with deterministic completed/failed coverage that verifies creation time, terminal activity time, and list ordering.
Verification:
- go test ./internal/agent -run TestSubagentStoreTerminalSaveKeepsBranchStartAndActivityTimes -count=20
- go test ./internal/agent -count=1
- go test -race ./internal/agent -count=1
- go vet ./internal/agent
- go test ./... -count=1
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Context:
The complete bilingual v1.19.5 release record was present but remained hidden from the public changelog.
Change:
Mark the v1.19.5 catalog record as published so both the changelog index and exact-version route render it.
Verification:
- node scripts/release-notes.mjs validate --version 1.19.5
- npm test --prefix site
- npm run build --prefix site
CleanupStaleRunning runs inside every controller build (boot.Build) and
transiently acquires the session lease of each running sub-agent's parent
session while rewriting its metadata. Desktop tabs share one global session
dir, so concurrent tab builds raced that probe against their own startup
bind: a tab whose bind lost the race surfaced a spurious "this session is
already open in another Reasonix window" error for a lease that was
genuinely free milliseconds later.
Two minimal fixes:
- desktop/app.go: startup lease binds (ensureTabSessionLeaseForRebuild,
acquireCandidateSessionLease) now retry briefly on ErrSessionLeaseHeld,
absorbing sub-millisecond in-process contention windows. A lease held by
another window or process stays held for its whole lifetime and still
fails fast after the bounded retries.
- internal/boot/boot.go: the stale-running sub-agent sweep runs once per
session dir per process instead of once per controller build, eliminating
the repeated global re-scan that created the contention. CLI/serve runs
are fresh processes, so their sweep-once semantics are unchanged. A failed
sweep drops its marker so a later build retries.
Adds regression tests for both layers. The desktop test reproduces the exact
reported error message before the fix.
Fixes#7399
Four independent token formatters produced four shapes for the same
number (142000 / 142,000 / 142.0k / 142.0K), and the long forms overflow
the sidebar's token column at its default width.
Co-authored-by: HUQIANTAO <[email protected]>
Problem: The CI lint gate rejected the embedded documentation parser because Heading.Text is deprecated in Goldmark v1.8.2.
Root cause: Heading extraction relied on the legacy aggregate Text method instead of reading supported inline AST node values.
Fix: Collect heading text from Text, String, AutoLink, and RawHTML nodes while preserving soft line breaks. Extend the parser regression test to cover emphasis, inline code, auto-links, C#, and Setext headings.
Verification: golangci-lint run --timeout=5m; go test ./...; go test -race ./internal/productdocs.
Problem:
Reasonix installations could not reliably search the exact documentation and release notes shipped with their build, and dynamic slash-menu filtering could leave stale virtual row gaps.
Root cause:
Documentation lived only as source files without a compiled retrieval identity or deterministic slash-command path. The shared virtual menu also let TanStack Virtual cache rows by numeric index while React used different keys.
Fix:
Embed docs and reviewed release notes, add the read-only docs tool and /docs command across CLI and Desktop, stamp and verify corpus identity in release builds, enforce documentation-impact declarations, and align virtual-list semantic keys across slash and file menus.
Verification:
- go test ./...
- go test -race ./internal/productdocs ./internal/boot ./internal/control ./internal/cli
- cd desktop && go test ./...
- pnpm --dir desktop/frontend build
- ./scripts/cache-guard.sh
- bash scripts/release-workflows.test.sh
- bash scripts/verify-embedded-docs.sh v0.0.0 e67b2aa824
The skip list only knew build/ and dist/ plus this repo's own paths, so a
Maven target/, a Python __pycache__ or a venv flooded the "@" menu with
generated files. Extends it to the common ecosystem outputs and gives the
desktop the same list instead of its own drifting copy.
An undeclared user-level provider_access means "allow all". Merging it as
a union with a project reasonix.toml that does declare one narrowed the
desktop model switcher to the project's list, so account-level providers
disappeared in that workspace. A project-only list is now ignored; the
union still applies once the user config declares its own.
Co-authored-by: ttmouse <[email protected]>
A ./reasonix.toml outranks the user-global config, so a session started in
a directory holding one ignores edits to the global file with nothing on
screen to explain it. /status now names the file actually in effect.
Review round fixes for PR #7373.
Problem: rpcwire.Conn.Request wrote synchronously before observing the
caller context, so a sidecar that stayed alive but stopped reading stdin
filled the pipe and blocked the write forever — intercept timeouts,
provider/UI calls, bounded shutdown, and /reload could all hang the host.
Root cause: the write path had no cancellation or stall detection, and the
sidecar crash supervisor only fired on process death, not on a live but
unreadable child.
Fix:
- rpcwire: writes now honor the caller's context (an abort fails only that
request, never the connection) and a new opt-in MaxWriteStall bound —
any write with no progress past the bound fails the connection with
WriteStallError. The sidecar client sets a 10s default
(ClientOptions.WriteStallBound), and its supervisor now kills the
process tree on any unexpected connection end, covering the
alive-but-wedged case. Deterministic tests: a pipe that is never drained
(WriteStallError in ~100ms, connection terminal, later calls fail fast)
and a fake sidecar wedged after initialize (stall error, crash marked,
process reaped).
- Golden baseline: pin [tools.search] engine="native" in the fixture — the
grep tool description (and thus tool schemas, provider request, and
cache prefix) varies with rg on PATH, which made the golden
machine-dependent and failed rg-less CI hosts. Regenerated artifacts
differ only in that description; verified passing with rg excluded from
PATH.
- providerext: out-of-order chunk buffering now has a 256-entry sequence
window; a sidecar emitting ever-higher seqs without the missing chunk is
failed interrupted instead of growing host memory (regression test).
- CI: the sdk job's bash steps declare shell: bash (Windows runners
defaulted to PowerShell); the PR race sweep gains internal/extension/...,
boot, control, and tool, and the desktop job gains go test -race ./....
- Windows vet: the first-boot test's syscall.Kill PID probe moves behind
build-tagged helpers (signal-0 on Unix, OpenProcess query on Windows).
- Lint gates: SA1012 nil-context in the ACP test, QF1007 conditional merge
in theme_plugin.go.
Verification: go test ./internal/rpcwire (x5 plus -race x3, incl. the
pre-existing response-close race test at -count=200), go test
./internal/extension/... (sidecar also -race), ./internal/remote,
./internal/boot (golden unmodified from the pinned fixture), desktop
focused theme tests, and go build ./... all pass.
bubbles binds word-wise cursor motion to Alt+arrows only, which is the
macOS convention; on Windows and Linux terminals Ctrl+Left/Right is what
users press, and it previously did nothing in the composer.
colorprofile picks a Windows profile from the NT build number alone and
never inspects the console mode, so on a legacy conhost (Win10 PowerShell
outside Windows Terminal) it reports TrueColor while the console prints
the SGR escapes literally. Bubbletea turns VT on for the TUI, but
`reasonix setup`, `config` and `doctor` print outside it.
Enable VT for stdout/stderr at package init and fall back to plain output
when the console refuses it.
The daily badge reads '#2 Repository Of The Day' and goes stale the moment that day passes; the monthly-Go and repository badges stay accurate on their own.
Address PR review: flow-based title/composer stack for small windows, multi-line hero textarea growth, hero only after history hydration, and CSS trim to stay under the frontend bundle budget.
Drop welcome prompt cards and lift a short model-only composer under the
headline. On first message, ease back to the full bottom composer with
lighter control weights.
Problem:
PR #7368 conflicted with the latest transactional rewind work and its CI was blocked by formatting and lint failures. A mechanical merge would also let the new rewind and file-revert bindings fall through to Local while a Remote projection was reconnecting.
Root cause:
Both branches extended the Desktop workbench surface independently, while the Remote scheduler changes retained one unformatted composite literal, an ineffectual generation assignment, and an empty test branch.
Fix:
Merge main-v2, keep both lazy-loaded workbench components, route every new rewind and file-revert entry point through the Remote fail-closed boundary, add reconnect regressions, and fix the formatting and lint findings.
Verification:
- go vet ./... && go build ./... && go test ./...
- cd desktop && go vet ./... && go build ./... && go test ./...
- go test -race ./internal/rpcwire ./internal/remote/protocol ./internal/remote/broker ./internal/remote/workbench/...
- cd desktop && go test -race -run 'Remote|Workbench|Rewind|Workspace' ./...
- pnpm test:all && pnpm build
- golangci-lint run --timeout=5m (root and desktop)
- go run ./cmd/remote-protocol-gen -check
- ./scripts/cache-guard.sh
Reviewer follow-ups to the extension provider wiring:
- ACP clients now discover installed extension models even when the current
model is a builtin: the session config state is enriched from the live
controller's ProviderCatalog on session/new, on openExistingSession, and
on every later config-state read, filling both the legacy model list and
the category:"model" select (creating them when the config backend has
none). Previously only an already-current plugin/... ref appeared.
- A sidecar may legally issue host/ui/request right after
extension/initialized, while the build is still assembling the
controller; preflight made that window real. Such requests now wait on a
readiness gate (controllerReady / buildFailed / request context) instead
of failing outright, so an extension asking at startup can neither error
nor hang a dying build. Deterministic tests cover wait-then-serve,
build-failure unblock, and cancellation.
- Deterministic regression for the ContractEntries AB-BA fix: a
channel-controlled test parks ContractEntries inside a tool's ReadOnly
callback and proves a concurrent registry Add still completes — the
pre-fix interleaving deadlocked. Stress/race runs alone could not pin
this invariant.
Verification: go test ./internal/acp ./internal/cli ./internal/boot
./internal/tool pass; go test -race ./internal/tool ./internal/acp pass;
TestGoldenBaselineNoExtensions green unmodified; cache-guard 8/8 pass;
gofmt/go vet clean.
Problem:
A macOS update can remain blocked forever when the fixed app-bundle backup directory survives but no pending update transaction exists.
Root cause:
Preparation treated every existing handoff backup as owned rollback material, even when no transaction could identify or recover it.
Fix:
After validating the staged and installed bundles, atomically quarantine the exact orphaned sibling backup under the update locks. Preserve its contents, verify the move, and fail closed if the path or contents change concurrently. Classify failures as recovery errors and provide actionable non-destructive guidance.
Verification:
Focused handoff and reconciliation tests, the internal repair race suite, root and Desktop Go tests and vet, frontend updater tests, typecheck, CSS checks, production build, and diff checks passed.
The alias alignment step rewrote latest/canary/next unconditionally, so a
recovery rerun performed three registry mutations per package even when
all seven packages already pointed at the release. npm treats dist-tag
writes as sensitive operations — an automation token is refused where an
interactive session is not — so a rerun with nothing left to do still
failed on the first write it did not need to make.
That is the shape of the v1.19.5 recovery: the publish itself succeeded
and the aliases have to be realigned by hand, after which rerunning the
workflow should be a no-op. It was not.
The step now reads the current aliases first and writes only what is
missing, keeping the candidate-SHA validation ahead of any mutation.
Also fixes the staging cleanup, which named "official-staging" — a tag
nothing creates. The publisher stages under "<dist-tag>-staging"
(npm/publish.mjs), so cleanup never removed anything and a staging alias
left behind by a failed publish stayed on the registry, which is exactly
what happened to latest-staging in 1.19.5.
Tests record every dist-tag invocation, so they assert how many registry
writes each path takes rather than only its exit status: none when the
aliases are already correct, only the stale ones otherwise, none at all
when provenance validation fails, and one staging removal per package
under the name the publisher really uses.
Preparation and reconciliation each had their own notion of "a pending
update exists". Preparation checked only that the marker file was
present and refused. Reconciliation read and validated it, and bailed
out without clearing anything when validation failed.
A marker that fails validation therefore satisfied neither: nothing could
resume it, roll it back, or remove it, while its mere presence kept
refusing every future update. Users hit a permanent dead end — retrying
did nothing, and reinstalling did nothing either, because the marker
lives in the state directory rather than the install directory. Startup
then reported the failed recovery and dropped into safe mode, so it
presented as "the whole app is broken" rather than "an update failed".
Both paths now classify through one function. The line between debris
and a recoverable transaction is drawn at self-description: bytes that
do not parse, or that do not say which release they target, for which
platform, and when they were opened, name nothing to roll back to, so
discarding them loses nothing. Every other validation failure is
environment-relative — the launcher path, whether the target sits inside
this installation, where the backup lives — and can fail for a perfectly
good transaction observed from the wrong install, so those keep the old
refusal rather than risking real rollback material. IO failures stay
errors: an unreadable marker is not an absent one.
Debris is quarantined rather than deleted. It is the only evidence of
what went wrong, and someone reporting a stuck updater should still have
it; repeated recovery never overwrites an earlier copy.
Fixes#7342.
Follow-up to stage 7 closing the reviewer-flagged gap between the streaming
provider adapter and any production path.
Problem: sidecar providers merged into a resolver only after the controller
was built, and the merged resolver had no consumer — an extension provider
could not be the initial model, `default_model = "plugin/..."` failed with
unknown model, the CLI/Desktop model pickers enumerated only config
providers, and the stage-7 E2E bypassed the real resolution path.
Fix:
- Preflight extension runtimes before model resolution: sidecars start once
via a single Manager (a build calls StartPackages exactly once), every
pre-ownership error path closes it, and the provider merge (with
provider:<ref> claims from the same manifest data the kernel uses) happens
up front so conflicts fail before anything is built.
- The merged resolver feeds every resolution site: the model entry
(plugin/-namespaced refs only; config models keep their full config entry,
broker stays authoritative), the executor, the dual-model planner, the
guardian, the recovery reviewer, and the subagent provider. Unknown-model
errors list available plugin refs; RequireKey skips plugin refs (no config
credential exists by design).
- Discovery and switching: Controller.ProviderCatalog() on the Capabilities
port; the CLI picker, /model persistence (config.SetDefaultModel accepts
plugin refs), Desktop ModelsForTab/SetModelForTab, and ACP
session/new + SessionConfigState all enumerate and resolve plugin/... refs.
- Tests prove the real paths: first boot with default_model="plugin/..."
streams the fake completion through Controller.RunTurn; unknown plugin ref
wraps ErrUnknownModel and lists plugin refs; builtin->plugin switch via
Rebuild streams from the sidecar with the old runtime alive until closed;
required-preflight failure and unclaimed provider conflicts fail with the
fake PID reaped; a failing new plugin on Rebuild leaves the old controller
and sidecars untouched; CLI/Desktop/ACP discovery and switch coverage.
- Also fixes a merge-blocking AB-BA deadlock the branch exposed:
tool.Registry.ContractEntries held the registry read lock across
lazyTool.ReadOnly() (which takes the lazy-spawn mutex) while a lazy spawn
swap held the mutex and wanted the registry write lock. Tool methods now
run after the lock is released; output unchanged.
Verification: go test ./internal/boot ./internal/cli ./internal/acp
./internal/extension/... and cd desktop && go test . pass;
TestGoldenBaselineNoExtensions green unmodified; both protocol generators
-check green; ./scripts/cache-guard.sh 8/8 pass; go test -race on
internal/boot, internal/tool, and internal/extension/providerext pass;
gofmt/go vet clean; no new dependencies.
Problem:
Unexpected Remote Workbench transport loss discarded the visible Remote projection, allowed some bindings to fall through to Local, and let data-heavy frames delay control traffic.
Root cause:
The target manager coupled logical session identity to one physical connection, rpcwire serialized every outbound frame behind one mutex, and reconnect lifecycle transitions were not generation-fenced across cancellable I/O.
Fix:
Keep a logical Remote slot with cached read-only projection, restore only the exact RuntimeTarget through a jittered supervisor, fail closed for unavailable Remote mutations, and add bounded control/data outbound scheduling. Make supervisor startup atomic, keep SSH bootstrap outside the projection lock, preserve Local navigation tokens during background recovery, and expose reconnect UX with failed-state manual retry.
Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/rpcwire ./internal/remote/protocol ./internal/remote/workbench/reconnect ./internal/remote/workbench/target ./internal/remote/broker
- cd desktop && go test -race -run 'Remote|Workbench' .
- go run ./cmd/remote-protocol-gen -check
- ./scripts/cache-guard.sh
- cd desktop/frontend && pnpm test:typecheck && pnpm test:remote && pnpm build
Problem:
CI lint rejected two stage-dependent if/else chains in the rewind picker with staticcheck QF1003.
Root cause:
The new confirmation state handling used repeated comparisons against the same stage value.
Fix:
Use tagged switches for Escape and Enter stage transitions without changing behavior.
Verification:
go test ./internal/cli
golangci-lint run --timeout=5m ./internal/cli
git diff --check
Problem:
Headless turns did not open checkpoints, partial-coverage CLI rewinds could commit without an explicit warning, and an empty hook runner incorrectly downgraded every file checkpoint to partial coverage.
Root cause:
Controller.Run skipped the checkpoint lifecycle used by interactive turns, the TUI called the compatibility Rewind wrapper directly, and hook coverage was inferred from a non-nil runner instead of configured tool-hook events.
Fix:
Open checkpoints for headless turns, require preview and explicit confirmation before partial file rewinds, make the compatibility wrapper fail closed, and report hook-write gaps only for actual tool hooks while keeping unknown custom hooks conservative.
Verification:
go test ./internal/control ./internal/cli ./internal/agent ./internal/hook ./internal/i18n
go test -race ./internal/control ./internal/cli ./internal/agent ./internal/hook
go test ./...
go vet ./...
(cd desktop && go test ./...)
./scripts/cache-guard.sh
Problem:
Local rewind could leave split state around crash and compensation windows, expose unsafe legacy file restores, and lose undo metadata in Desktop paths.
Root cause:
Checkpoint persistence and compensation did not model every intermediate state, queued writers were registered too late, and UI capability propagation trusted incomplete ownership metadata.
Fix:
Make checkpoint expiration and truncation failure-aware, verify blob identities, recover rename intermediates, retain pending failed compensation, normalize paths, require final ownership fingerprints, reserve queued background writers, and preserve Desktop transaction IDs for undo.
Verification:
go test ./...
go test -race ./internal/checkpoint ./internal/control ./internal/agent
cd desktop && go test focused rewind/workspace suites
cd desktop/frontend && pnpm build
./scripts/cache-guard.sh
go vet and golangci-lint for root and desktop modules
Stage 10 of the unified extension kernel / Extension Protocol v1 plan.
- New docs: EXTENSIONS.md/.zh-CN.md (capability map, install/manage,
fail-atomic reload semantics, development guide, compatibility,
full-trust security model) and EXTENSION_PROTOCOL.md/.zh-CN.md (prose
companion to the generated index: transport, lifecycle, content refs,
interception, streaming providers, structured UI, errors, v1 stability
contract, security model).
- Updated docs: PLUGIN_PACKAGES (Manifest v1 chapter: strict parsing,
merge rules, prompts/themes, runtime declaration, FULL TRUST disclosure
and --link trust inheritance), CAPABILITY_DIAGNOSTICS (additive v1
prompts/themes/runtime fields, schema_version stays 1), CLI (/reload),
ACP (_reasonix.io/session/reloadExtensions, extensionSurface
negotiation, /<plugin>:<action> discovery), RELEASING (sdk/go/vX.Y.Z
tag convention, three-product tag contract unchanged), THEME_PACK
(plugin theme kind and fallback-preserve, from stage 4).
- CI: new sdk job on the three-OS matrix (gofmt, vet, stdlib-only guard,
test, race on pushes) triggered by sdk/ or internal/extension/ changes;
root conformance tests run in the existing root matrix.
- Makefile: sdk-test / sdk-test-race targets.
- Bundle budget: initial raw JS+CSS 2250 -> 2265 KiB with a comment
recording the extension UI surface as the cause; every gzip budget
(the user-visible transfer cost) is unchanged and passing.
Compatibility audit (kept green by tests added in stages 1-9): legacy
manifests parse byte-identically, plugin-packages.json schema unchanged,
session JSONL untouched with extension state isolated to
<session>.extensions.json, desktop theme state preserves plugin: IDs
across uninstall while legacy files load unchanged, install-plan JSON is
omitempty-additive, plugin/... refs degrade to model-unavailable on old
versions, and the two new event kinds append at the enum end with
text fallbacks for old clients.
Stage 9 of the unified extension kernel / Extension Protocol v1 plan.
Problem: Extension Protocol v1 had a host implementation but no supported
way for third parties to write sidecars, no proof the contract works from
both ends, and no generator-produced SDK artifacts.
Fix:
- sdk/go: a new nested module (github.com/esengine/DeepSeek-Reasonix/sdk/go,
Go 1.23, standard library only, zero requires). Serve(ctx, Handler,
Options) drives the full lifecycle: initialize barrier with
manifest-subset enforcement, interceptor routing with wildcard fallback
and Continue/Block/Replace/Allow/Deny helpers, observation hooks, a
streaming Provider pump (1-based contiguous seqs, exactly one stream/end,
no chunks after cancel), structured HostUI publish/request helpers
(structured-only by construction), transparent content-ref rehydration
with paging and SHA-256 verification, panic isolation, bounded queues,
and bounded shutdown. 44 unit tests, race-clean, against a scriptable
fake host.
- The protocol generator now emits a fourth artifact,
sdk/go/types_generated.go (wire DTOs, enums, method names, frozen limits
and error specs), replacing the handwritten mirror; handwritten
validators/helpers moved to types_ext.go and the public API is unchanged
(the 44 tests compile and pass unmodified). A new exported
protocol.EnumValues() keeps enum sets single-sourced; the three
pre-existing artifacts regenerate byte-identical.
- sdk/go/examples/fullsidecar: the reference extension — input.receive
rewrite, tool.before block and argument rewrite, system_prompt.build
strategy, a fake streaming provider (text, tool call, usage, cancel
handling), UI status/card publish plus an action-driven form request,
and clean shutdown.
- internal/extension/conformance: 17 hermetic host<->SDK round-trip tests
(root module, so the CI three-OS matrix covers them) building the
example and driving it over the real wire: handshake accept and
capability_not_declared, intercept replace/block, strategy replacement,
provider stream/cancel/usage with seq discipline, >64 KiB content-ref
rehydration, UI action/form round-trips, intercept timeout, mid-intercept
crash fail-fast, unknown method, oversized frame, and bounded shutdown
with exit 0.
Verification: cd sdk/go && go test ./... and -race pass with a zero-require
go.mod; go test ./internal/extension/... passes (conformance also -race);
both protocol generators -check green; boot golden baselines unregenerated;
root go build ./... clean; no new dependencies anywhere.
Problem: the authoritative Desktop rewind test reported a file mode conflict on Windows.\n\nRoot cause: its seeded checkpoint used Unix 0644 permissions instead of the effective platform mode.\n\nFix: record the target file's actual permission bits in the checkpoint fixture.
Summary:
Generate a bilingual, product-focused release record for the v1.19.5 Stable candidate from merged pull request metadata.
Verification:
Validate the catalog, citations, bilingual fields, and rendered GitHub release notes before committing.
Stage 8 of the unified extension kernel / Extension Protocol v1 plan.
Problem: sidecar extensions could declare UI capabilities at handshake, but
nothing rendered them — no host hub, no events, no frontend surfaces.
Fix:
- internal/extension/uihub: the host UI hub behind every sidecar client.
host/ui/publish is strict-decoded, credential-redacted, and emitted as
the new extension_surface / extension_status events (appended at the end
of the event Kind enum, wire payloads in eventwire); host/ui/request
(confirm/input/select/multiselect) rides the existing Ask machinery.
Every surface carries plugin ID, surface ID, session ID, and generation:
stale-generation or foreign-session publishes are dropped, stale requests
cancelled, crashed clients rejected. Actions register as /<plugin>:<action>
with InvokeAction/Submit RPCs, and surfaces are structured-only — no
HTML/CSS/JS/URLs anywhere.
- CLI: status/notification render as severity notice lines, cards and forms
as transcript cards (existing markdown renderer), actions resolve after
CustomCommand/RunSkill and appear in slash completion.
- Desktop: bound ExtensionActions/InvokeExtensionAction/SubmitExtensionForm;
native transcript ExtensionCard (markdown, key-values, progress, action
buttons), ExtensionFormDialog decision surface, StatusBar severity chips,
toast notifications, and a command-palette Extensions group — with the
per-tab runtime epoch fence plus a per-surface generation guard dropping
reordered publications, and full en/zh/zh-TW strings.
- ACP: initialize advertises extensionSurface (schemaVersion 1) and parses
the client's support; supporting clients get a vendor session/update
carrying the structured DTO plus flattened text, others get text-only
fallbacks (cards/statuses flatten to agent_message_chunk, forms ride the
permission-request fallback). Extension actions merge into
available_commands_update and resolve last in resolveSlashPrompt.
- Remote protocol artifacts regenerated: the two new event kinds flow
through eventwire.KindNames into the remote schema (append-only change).
Verification: go test ./internal/cli ./internal/acp ./internal/control
./internal/event ./internal/eventwire ./internal/extension/... ./internal/boot
pass incl. golden baselines; uihub race-clean; cd desktop && go test .
passes; desktop frontend typecheck, full tsx suite (43 extension tests),
and check:css pass; both protocol generators -check green; no new
dependencies.
Summary:
- Add the official Trendshift daily achievement badge to the English and Chinese README pages.
- Link the badge to Reasonix's Trendshift repository profile.
Verification:
- git diff --check
- Verified the badge endpoint returns SVG and renders in the local README preview.
Problem: the Windows smoke test treated the seeded recovery fixture as an external file modification.\n\nRoot cause: the fixture hard-coded Unix 0644 permissions even when the platform reports a different effective mode.\n\nFix: derive restore and forward identity modes from the file's actual stat result.
Problem: CI lint rejected the checkpoint manager helper after compatibility rewind moved to the transaction engine.
Root cause: The old restoreCode wrapper no longer had callers.
Fix: Remove the dead wrapper and its fmt import.
Verification: golangci-lint run --timeout=5m; go test ./internal/control.
Problem:
The direct TSX frontend tests failed when SettingsPanel imported its code-split CSS because Node does not load CSS modules.
Root cause:
The production-only stylesheet side effect lived in a reusable component module that unit tests and SubagentsPanel import directly.
Fix:
Add a lazy production entry that owns the stylesheet import while keeping SettingsPanel reusable, and lock the split with the bundle contract test.
Verification:
- pnpm --dir desktop/frontend test:all
- pnpm --dir desktop/frontend build
- focused SettingsPanel import tests
- git diff --check
Problem: Combined rewind recovery could leave conversation and files split after a crash, background writers could cross the preview/commit window, and ancestor symlinks could redirect checkpoint I/O outside the workspace.
Root cause: Startup recovery had no conversation applier, writer registration was not coupled to the mutation barrier, compatibility rewind bypassed the transaction engine, and filesystem operations relied on lexical containment checks.
Fix: Defer combined recovery until resume, restore conversation and checkpoints before file compensation, route legacy rewind through transactions, hold writer barriers across direct and fleet background agents, and use no-follow workspace-relative filesystem operations.
Verification: go test ./...; go test -race ./internal/checkpoint ./internal/control ./internal/agent; desktop go test ./...; Windows checkpoint cross-compile; frontend rewind contract test; cache guard; go vet focused packages.
The overrides grid used repeat(2, minmax(200px, 1fr)) with a 568px
minimum, but the settings content column is only ~500-568px wide at
window sizes 760-930px, clipping the right edge of the cards. Switch the
two columns to minmax(0, 1fr) so they shrink with the column; the
existing <=760px single-column fallback stays in place.
At <=900px the memory tab row stacked vertically, leaving the project-name
selector top-left and the Suggestions button bottom-right; with a wrapping
flex row the short name could also end up beside the memory tabs while the
button dropped to its own line. Group the workspace selector and the
Suggestions button in a dedicated .memory-tabs-row__tail container whose
100% flex-basis always takes the second line, so the two always share a
row regardless of name length. The name prefers one line and wraps
naturally (overflow-wrap:anywhere, no truncation) when space runs out.
The narrow-window media query forced .settings-subtabs to display:flex;
width:100% with horizontal scrolling, so the Usage/Access/Stats pills
stretched across the whole row. Switch to inline-flex with wrap so the
pills shrink-wrap their content and wrap onto further lines instead of
stretching or scrolling. This also gives the memory page tab pills the
same compact behavior.
Problem:
Astro 7 collapsed source newlines in documentation code blocks, concatenating commands and comments and allowing copy controls to obscure code.
Root cause:
The site relied on preformatted source whitespace while JSX-style HTML compression removed line breaks. Copy buttons also shared the code text area.
Fix:
Preserve intentional documentation whitespace, reserve a dedicated top area for every copyable code block, and add regression contracts for both behaviors.
Verification:
- cd site && npm test
- cd site && npm run build
- Browser geometry at 1280x720 and 390x844 across all 15 documentation code blocks
The Session controls table names the method and the option ids, but never the
request shape, so a client has to guess the field name. The natural guess is
optionId (it is setting an option, and the ids come from configOptions), and
that fails in a way that points at the wrong thing: the handler answers
-32602 session/set_config_option: unknown config option
with an empty name interpolated at the end, which reads as "that option does not
exist" rather than "you named the parameter wrong". The option is in fact
advertised correctly in session/new's configOptions.
Cost when a client gets this wrong is not cosmetic: tool_approval silently stays
at 'ask', so an unattended host blocks on session/request_permission for every
gated call instead of running.
Documents the params in both language versions, with a worked example, an
explicit note that the field is configId, and the result/error shapes.
Problem:
The snapshot refactor left an unreferenced compatibility wrapper, which failed the repository unused-code lint gate.
Fix:
Remove the dead wrapper and update its documentation reference to the snapshot function.
Verification:
- golangci-lint v2.12.2 run --timeout=5m
- go test -race ./internal/config ./internal/boot -count=1
- ./scripts/cache-guard.sh
Problem:
Project prompt provenance was detected by rereading reasonix.toml separately from the merge, so concurrent replacement could pair a project value with trusted-user provenance.
Root cause:
Metadata and decoded values came from different filesystem snapshots.
Fix:
Read each TOML source once, validate and merge those bytes, and derive provenance metadata from the same immutable snapshot. Add a deterministic replacement regression.
Verification:
- go test ./internal/config ./internal/boot -count=1
- go test -race ./internal/config ./internal/boot -count=1
- ./scripts/cache-guard.sh
- go vet ./...
- go test ./... -count=1
Integration:
- Synchronize PR #7328 with the latest main-v2 session title changes.
- Preserve the custom supported_efforts provider contract unchanged.
Verification:
- go test -count=1 ./internal/provider/openai ./internal/config ./internal/boot ./internal/agent ./internal/serve
- go test -race -count=1 ./internal/provider/openai
- ./scripts/cache-guard.sh
- go vet ./...
- go build ./...
Co-authored-by: SivanCola <[email protected]>
Problem:
CodeQL reported a potential integer overflow in the checkpoint bounds map allocation.
Root cause:
The capacity hint added one to a slice length before allocating the map.
Fix:
Use the completed-checkpoint count as the hint and let the map grow for the optional current checkpoint.
Verification:
golangci-lint and checkpoint/control tests pass.
Problem:
A recovery callback captured before gateway shutdown could run after closeSessionState released its lease and reacquire a recovery-path lease on an already retired session state.
Root cause:
Recovery lease transfer and session teardown had no shared lifecycle gate. The handler moved the SessionLeaseKeeper before checking whether the state was still live.
Fix:
Serialize recovery ownership handoffs with state retirement, reject callbacks that arrive after retirement, and cover the late-callback ordering with a deterministic regression test.
Verification:
- go test ./internal/bot ./internal/control ./internal/agent
- go test -race ./internal/bot
- go vet ./...
- go test ./...
Problem:
The CI lint job rejected empty branches, a manual slice copy, and obsolete rollback helpers.
Root cause:
The transactional rewind replacement left legacy helpers and no-op error branches behind.
Fix:
Remove dead legacy paths, use copy for checkpoint snapshots, and surface undo fingerprint errors explicitly.
Verification:
golangci-lint v2.12.2 reports 0 issues; focused checkpoint, control, agent, and Windows cross-compile checks pass.
Problem:
- Auto mode referenced undefined app tokens, and explicit terminal themes left header controls and drawer borders on the app palette.
Root cause:
- Terminal colors were applied to xterm and part of the drawer, but higher-specificity layout rules and toolbar controls still bypassed the terminal token set.
Fix:
- Map auto mode to defined app tokens and route all terminal chrome through terminal-owned surface, border, foreground, and accent values.
- Set the native control color scheme for explicit light and dark terminal modes.
- Add CSS ownership regressions for the component and real drawer selectors.
- Raise the raw initial asset budget by the measured 1 KiB needed for the completed chrome rules.
Verification:
- pnpm --dir desktop/frontend test:terminal
- pnpm --dir desktop/frontend test:typecheck
- pnpm --dir desktop/frontend build
- Edge computed-style matrix for auto light/dark and opposite explicit terminal themes
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Problem:
Local rewind could partially restore files, silently fall back to legacy behavior, or apply a stale conflict decision.
Root cause:
Checkpoint payloads lacked a durable transaction protocol, mutation writers were not fenced consistently, and preview tokens were not revalidated at commit time.
Fix:
Add compatible v2 checkpoints, blob-backed atomic transactions with crash recovery, writer observation and barriers, controller prepare/commit/undo APIs, and immediate Desktop rewind with explicit conflict handling.
Verification:
Run root and Desktop Go suites, race tests, cache guard, Windows checkpoint cross-compile, frontend production build and targeted rewind/workspace contracts.
Problem:
A stale system_prompt_file could abort startup, but the initial fallback design allowed project config to probe Reasonix home and downgraded every read failure. That could expose credential files to provider prompts or run without explicitly configured policy.
Root cause:
Merged configuration did not retain whether system_prompt_file came from user or project scope, and boot could not distinguish a genuinely missing file from containment, permission, or other I/O failures.
Fix:
Preserve prompt-file provenance, confine project-selected paths to the workspace with os.Root, keep the trusted user-home fallback, and fall back only when every allowed location is absent. Add config and boot regressions for source precedence, credential isolation, path and symlink escapes, mixed read errors, and startup behavior.
Verification:
- go test ./internal/config ./internal/boot -count=1
- go test -race ./internal/config ./internal/boot -count=1
- go vet ./...
- ./scripts/cache-guard.sh
- go test ./... -count=1
Co-authored-by: SprayHank <[email protected]>
Problem:
Bot controllers could switch to an automatic recovery branch while their session lease and remote mapping still pointed at the original transcript. A later turn could then continue with inconsistent ownership or route an attached chat back to the stale path.
Root cause:
The bot created its SessionLeaseKeeper after boot.Build and never installed an OnSessionRecovered ownership handoff. Recovery therefore updated only the controller path.
Fix:
Create the lease keeper before controller construction, wire the failure-atomic recovery handoff, move attached runtime mappings only for the active session generation, and remember the recovered path for the host. Late callbacks from retired controllers cannot overwrite their replacements.
Verification:
- go test ./internal/bot ./internal/control ./internal/agent
- go test -race ./internal/bot
- go test ./...
A third-party endpoint can serve a DeepSeek model under its own effort
scale. Declaring supported_efforts is how the config states that, and it
is the only way to describe such an endpoint — but the DeepSeek thinking
branch validated the selected level against our built-in vocabulary and
rejected anything outside low/high/max/disabled, ignoring the
declaration entirely.
The rule already existed one case up: "low" is accepted on a non-flash
model precisely when supported_efforts declares it. This extends the
same deference to the rest of the vocabulary instead of applying it to
one level.
Undeclared levels are still rejected, and a declaration that does not
list the requested level is still a rejection — honoring the config
means honoring what it actually says.
Fixes#7273.
Problem:
- Light ANSI white and bright colours were unreadable on the light terminal surface.
- Concurrent Wails settings calls could persist an older click after newer user intent.
Root cause:
- The light palette retained colours intended for a dark terminal.
- Terminal-theme writes were fired independently without ordering or stale-refresh protection.
Fix:
- Use a WCAG-readable light ANSI palette and cover all 16 colours.
- Serialize terminal-theme persistence, protect optimistic state from stale reloads, and recover failed saves.
- Keep missing settings payloads backward-compatible.
- Raise only the raw initial-asset budget by the measured 4 KiB feature cost; gzip and per-chunk limits remain unchanged.
Verification:
- Node 24 frontend production build
- terminal and usage-stats frontend suites
- root and desktop Go suites
- focused race tests for config and desktop settings
- in-app browser terminal-theme switching check
Problem:
Serve session titles can expose pasted-text or injected-language markers, return empty output when reasoning consumes a tiny token budget, and regenerate whenever an active transcript changes mtime.
Root cause:
The title provider still used the retired effort=off value, title inputs retained UI-only labels, and cache identity tracked the whole file timestamp instead of the first user message that determines the title.
Fix:
Disable reasoning for the dedicated title provider, use a clearer bounded title prompt, strip leading pasted-text labels, skip synthetic reasoning-language turns, and persist a backward-compatible SHA-256 fingerprint of the normalized first message.
Verification:
go test ./...
go test -race ./internal/serve ./internal/agent
go vet ./internal/serve ./internal/agent
./scripts/cache-guard.sh
git diff --check
Co-authored-by: SprayHank <[email protected]>
Co-authored-by: Q1hangL <[email protected]>
Preserve the top-level routing regression coverage contributed in PR #7308 after the underlying flag registration fix landed through PR #7223.
Verification:
- go test ./internal/cli -run TestRunRoutesBareInteractiveFlagsToSession -count=1
- go test ./internal/cli -count=1
- git diff --check
Co-authored-by: SKY ZHAO <[email protected]>
The workspace probe test pinned git's full argument list byte-for-byte,
so it restated the shared invocation baseline instead of the two
settings it actually cares about — and broke as soon as the baseline
gained the Windows long-path entry. That brittleness is part of why the
baseline drifted between call sites: touching it meant touching exact-
match tests that had nothing to do with the change.
It now asserts what the test is about — the daemon-spawn settings are
present, the caller's arguments are preserved, the console is hidden on
Windows — and leaves the baseline itself pinned by internal/gitcmd.
Integration:
- Synchronize PR #7328 with the latest main-v2 provider and CI changes.
- Preserve the custom supported_efforts validation contract across the merged provider updates.
Verification:
- go test -count=1 ./internal/provider/openai ./internal/config ./internal/boot
- go test -race -count=1 ./internal/provider/openai
- ./scripts/cache-guard.sh
- go vet ./...
- go build ./...
Co-authored-by: SivanCola <[email protected]>
A checkpoint opens with the composed turn, so its stored prompt carries
whatever the controller prepended — the plan-mode marker and the
transient blocks. Nothing stripped that back out on the way to a
consumer, and every consumer treats Prompt as a human label: the TUI
rewind picker, the desktop change list, the workbench projection. The
rewind picker additionally restores the prompt into the composer, so
selecting an entry pasted controller markup into the user's input.
Users saw a rewind list whose entries all read "必须使用简体中文…" —
the reasoning-language block's first line, repeated for every turn,
with the actual prompts invisible behind it.
Checkpoints() now reduces each Prompt through StripComposePrefixes, the
existing helper that already knows about both the marker and the
transient blocks. Stripping on read rather than only on write also fixes
sessions whose checkpoints are already on disk, recorded composed.
Fixes#6903.
Stage 7 of the unified extension kernel / Extension Protocol v1 plan.
Problem: extension sidecars could declare providers at handshake, but the
host had no way to resolve or stream from them.
Fix:
- internal/extension/providerext: a provider.Resolver that merges each
sidecar's catalog into the host resolver under
plugin/<plugin>/<provider>/<model> refs, plus a provider.Provider whose
Stream drives extension/provider/stream/open|cancel with broker-grade
client semantics: 1-based contiguous sequence buffering (duplicates and
stale chunks dropped, 256-slot bounded delivery queue, drain goroutine),
stream/end.lastSeq as the frozen terminal boundary with a 1s gap timer
that fails interrupted naming the missing sequence, context cancel that
sends stream/cancel, and crash handling that drains buffered chunks then
fails the stream interrupted — never falling back to another provider.
Reasoning-policy interfaces (tool-call reasoning, reasoning round-trip,
missing-reasoning warning) are answered from the descriptor, and effort
is baked into the provider at resolve time like built-in providers.
- Ref conflicts are explicit: a sidecar descriptor colliding with an
existing host ref fails BuildRuntime unless the plugin claimed
provider:<ref> in runtime.replaces, in which case it replaces the base
catalog entry (ParseSlot now accepts plugin-namespaced provider refs;
the kernel's single-owner slot rules apply).
- internal/extension/providerconv: shared host-side provider<->protocol
DTO conversions, extracted verbatim from the agent interceptor wiring
so both consumers share one conversion path (agent behavior unchanged).
- sidecar.Client gains typed provider RPCs and a SetStreamRouter seam
with a Disconnected signal used for crash propagation; boot wraps the
effective resolver (local or remote broker — precedence preserved) and
exposes it as BuildResult.ProviderResolver. Credentials never cross the
wire: stream/open carries only ref, model, effort, and the request.
Verification: go test ./internal/extension/... ./internal/agent
./internal/boot pass (providerext and sidecar also under -race); boot E2E
with a re-exec fake sidecar resolves plugin/providerdemo/fake/x and
streams text+usage over the real wire; out-of-order/duplicate/missing/
late chunks, cancel, crash, catalog conflicts (unclaimed fails, claimed
wins), and concurrent-stream coverage included; protocol schema untouched
(generator -check green); golden baselines unregenerated; no new
dependencies.
Contract: The embedded web UI must keep localized active-session feedback, surface non-2xx response text, and report rejected delete requests.
Coverage: Add a focused index contract test for both locales and all three delete failure branches.
Verification: go test ./internal/serve; go test -race ./internal/serve; git diff --check.
Co-authored-by: SivanCola <[email protected]>
Problem: Session deletion still failed silently when the browser request rejected before an HTTP response was available.
Root cause: The UI handled non-2xx responses but did not handle Promise rejection from fetch or response-body reads.
Fix: Add localized fallback feedback for rejected delete requests while preserving the existing active-session and HTTP-error paths.
Verification: Focused serve tests and browser checks cover the active-session guard, HTTP errors, and dropped connections.
Co-authored-by: SivanCola <[email protected]>
The set of blocks the host prepends to a user turn was written out three
times: twice inside the strip regex (once for the opening alternation,
once for the closing one) and again as a switch in hasLeadingInjectedBlock.
The copies had already diverged — <autoresearch-runtime> was in none of
them, and the switch was missing active-goal, capability-route, and
interrupted-turn-recovery as well.
Two user-visible consequences. Markup for a missing tag survived into
previews, session titles, and the rewind picker, since the strip pass
never matched it. And hasLeadingInjectedBlock stopped its walk at the
first block it did not recognize, so an already-present response-language
or reasoning-language block behind one went undetected and a second copy
was prepended.
TransientUserBlockTags is now the one list: the regex is generated from
it and the walk iterates it. Attribute-carrying open tags (hook-context,
capability-route) are matched through a shared helper instead of ad-hoc
prefix checks. Tests iterate the list, so a tag added later is covered
without touching them.
Fixes#6534.
Capacity hints derived from input lengths tripped the allocation-overflow
analyzer for no benefit — these lists hold a handful of entries. Uses
slices.Contains for the flag check while here.
Problem:
An older Reasonix runtime does not recognize the new recovery-trash cleanup operation and could route an interrupted marker through its ordinary delete reconciler before the transcript reached the trash.
Root cause:
The cleanup marker was durable before the primary transcript was recoverable, so cross-version reconciliation could observe an unsafe intermediate state.
Fix:
Write Desktop-compatible trash metadata and atomically move the transcript first, then publish the cleanup marker used to finish moving sidecars. Older runtimes can now delete only redundant leftovers after the conversation is already recoverable.
Verification:
- focused internal/agent recovery trash tests
- focused internal/agent race tests
- explicit cross-version publication-order regression test
Problem:
CLI conflict-recovery copies can accumulate in resume lists, where they are indistinguishable from the original conversation and may be selected arbitrarily.
Root cause:
The CLI flattened session metadata, ignored recovery lineage, and never invoked the conservative recovery cleanup used by Desktop.
Fix:
Group recovery families in CLI resume surfaces, label copies with their parent, prefer the newest visible leaf, and move only covered, untouched, idle, unleased copies into the compatible session trash. Hold parent and branch removal guards across final revalidation and make interrupted trash moves durable.
Verification:
- go test ./...
- go vet ./...
- focused go test -race for agent recovery and CLI resume ordering
- ./scripts/cache-guard.sh
- Desktop recovery/session focused tests
Reasonix shells out to git from five places: the CLI status readout, the
desktop workspace probe, the remote workbench, worktree management, and
plugin source checkouts. Each one assembled its own argument list, so the
invocation baseline drifted — three sites disabled fsmonitor and auto
maintenance, two did not, and the environment differed at every site.
internal/gitcmd now builds them all. Call sites keep their own
preferences (core.quotepath for the workbench, core.autocrlf for plugin
checkouts) by layering config on top of the shared baseline rather than
replacing it, and the environment is uniform: credential variables
filtered out of git subprocesses, optional locks off, no interactive
credential prompts on a terminal the TUI owns.
Also folds in the settings that keep repository-supplied configuration
from selecting a program for git to run during inspection — diff-side
external and textconv drivers alongside the existing fsmonitor and
maintenance settings — since a repository's config is authored by
whoever produced the repository, not chosen by the user. Covered by a
test that inspects a repository configured that way and asserts nothing
ran; the package comment records which drivers this cannot reach.
Argument-construction tests move from internal/worktree to the new
package with the code they cover.
Two hardening fixes from the security triage:
- accounts worker: build email verification links from the configured
APP_ORIGIN instead of the request origin — the Host header is
caller-controlled, so a request-derived origin could point emailed links
at an attacker-chosen host. Tokens are now URL-encoded too. (#5550)
- subagent store: session lineage walks (sessionAncestors,
isAncestorSession) now route every session id — caller-provided and
read back from branch metadata — through parentSessionPath's
bare-filename validation instead of joining paths directly, so a
"../"-shaped id can no longer escape the session directory. (#5551)
Problem:
A single encoded message could still expand into very large nested slices while staying below the byte and top-level message limits. User-message history loading also swallowed event replay refusals and returned an older checkpoint.
Root cause:
Replay preflighted only the messages array boundary before constructing provider.Message values, and LoadSessionUserMessages treated every probe or replay failure like a foreign event log.
Fix:
Token-walk each message before typed decoding, cap all nested JSON collection elements across the live transcript, and reject over-limit elements before reading them. Propagate probe, schema, and replay errors from user-message loading while retaining checkpoint fallback for safely ignorable logs.
Verification:
go test ./... -count=1
go test ./internal/agent -count=1
go test -race ./internal/agent -count=1
go vet ./...
cd desktop && go test ./... -count=1
cd desktop && go vet ./...
REASONIX_RELEASE_CACHE_GUARD=1 go test ./internal/agent -run ^TestReleaseCacheHitGuard$ -v -count=1
Follow-up hardening for the web_search passthrough:
- formatWebSearchResults: drop the stray leading newline when every result
entry is blank, and use Fprintf per lint.
- Pin the anthTool wire shape both ways: the typed server tool must omit
input_schema, and omitempty must not strip the schema from named tools.
- Cover buildRequest tool ordering with web_search on/off.
- Full SSE round-trip test: web_search_tool_result surfaces as readable
text and server_tool_use is never mistaken for a client tool call.
Capability verified live against api.deepseek.com/anthropic: the endpoint
executes searches server-side and meters them in usage as
server_tool_use.web_search_requests.
Adds support for calling web_search via the provider.
Example provider config, using DeepSeek's support for the web_search
tool:
```toml
[[providers]]
name = "anthropic-api-deepseek-com"
kind = "anthropic"
base_url = "https://api.deepseek.com/anthropic"
models = ["deepseek-v4-flash", "deepseek-v4-pro"]
api_key_env = "DEEPSEEK_API_KEY"
context_window = 128000 # tokens; compaction triggers near this limit
prices = { "deepseek-v4-flash" = { cache_hit = 0.02, input = 1, output = 2, currency = "¥" }, "deepseek-v4-pro" = { cache_hit = 0.025, input = 3, output = 6, currency = "¥" } } # per-model prices, per 1M tokens
web_search = true # enable server-side web_search tool (Anthropic/DeepSeek API)
```
Example use, note that it doesn't try to perform the search via a call
to a search engine:
```
◆ reasonix · deepseek-v4-pro
Context is kept across turns. Type 'exit' or Ctrl-D to quit.
› search the web for hello
▎ thought for 1s
• Hello Definition & Meaning | Britannica Dictionary
https://www.britannica.com/dictionary/hello?srch_tag=e2cdp6pptoimak7ydbp6kjxddhwqysz3
• hello https://baike.baidu.com/item/hello/38467
• hello _ 百科 https://baike.sogou.com/v64731937.htm?addressbar=hide&jump_from=undefined&vrid=81001007
• hello
https://baike.sogou.com/v64731937.htm?ch=frombaikevr&fromTitle=hello&vrid=30010097&entryScene=baike_VR_ruo&j
ump_from=sogou-mobb-e447193f2b83d789&addressbar=hide#paragraph_3
• hello http://dict.youdao.com/w/hell_o/
• hello是什么意思 https://www.hanzidaquan.com/yingyu/hello.html
• Hello! https://www.ldoceonline.com/dictionary/hello
• hello是什么意思? https://wenwen.sogou.com/z/q961078741.htm
• hello是什么意思,hello怎么读,hello的意思是:“喂”的招呼声或问候 https://fanyi.kkabc.com/ci/Hello
• hello https://www.wordreference.com/enzh/hello Here are the top results from the web search for "hello":
1. Britannica Dictionary — Defines "hello" as a greeting, a telephone answer, a way to get attention, and an
expression of surprise. Pronunciation: /həˈloʊ/.
2. Baidu Baike (百度百科) — Covers the history: derived from Old High German "halâ", popularized by Edison
with the telephone. Notes its use in Chinese as "你好".
3. Sogou Baike (搜狗百科) — Similar to the Baidu entry, covering meanings, usage, and etymology including the
Edison telephone story.
4. Youdao Dictionary — Lists: interjection (greeting, telephone, getting attention), noun (a greeting), and
verb (to say hello). Also notes "Hello" as a song by Karmin.
5. Longman Dictionary — Comprehensive entry with usage examples: greeting, telephone, attracting attention,
expressing surprise/confusion. Also covers informal alternatives (hi, hey, hiya).
6. WordReference — Bilingual English-Chinese entry with translations, compound forms ("golden hello", "say
hello"), and pronunciation guides.
If you had something more specific in mind, let me know and I can refine the search!
▎ thought for 0s
· 33233 tok · in 32870 (12800 cached / 20070 new) · out 363 · ¥0.0627
```
For #4217
For #5166
Stage 6 of the unified extension kernel / Extension Protocol v1 plan.
Problem: the sidecar protocol and lifecycle existed but nothing in the
host consulted extensions — there was no dispatcher, no decision
semantics, and no replacement-strategy application.
Fix:
- internal/extension/dispatch: a host dispatcher that walks the frozen
snapshot interceptor chain sequentially (priority, plugin ID,
registration order), with per-point payload DTOs and strict
re-validation of every replacement (DisallowUnknownFields + payload
invariants + tool-args JSON-object shape). Decisions: continue, block
(user-visible, secrets-redacted reason), replace (substituted payload
flows to later interceptors and the host), allow/deny (legal only at
permission.decision; anything else is a contract violation). Error
policy: extensions owning a replacement slot or declared required:true
fail the operation on timeout/error; optional observation-only
extensions warn once and are skipped. Strategy slots (system_prompt,
context, provider_request, provider_response, compaction,
session_policy, permission, frontend_events) are single-owner: after
the chain walk, the slot owner gets the final replacement say via
RunStrategy, and its failure is always fatal.
- Wire the dispatcher through boot, control, and agent at all 17 points:
system_prompt.build (strategy output lands in the frozen snapshot hash
AND the live agent session via a pre-turn session swap),
input.receive (composed text, both orchestrated and headless paths),
agent.before_start, context.prepare and provider.request (ephemeral —
request copy only, session messages never mutated), provider.response
(replacement persists as the visible transcript turn), tool.before
(re-parse replaced calls so policy/permission see what executes),
permission.decision (full-trust allow overrides host deny, audited and
surfaced), tool.after, compaction.prepare/complete, session
start/end/load/save/rotate, and frontend.event observation (strategy
may rewrite text, never the kind; malfunction never hangs the frontend
state machine). Every point is nil-safe: no extensions means
byte-identical behavior, golden baselines unregenerated.
- Close the externalization gap: intercept/event payloads over 64 KiB are
offloaded to the host content store with an ExternalizedField envelope,
and inbound replacements carrying content refs are paged back through
host/content/read with byte-count and SHA-256 verification (protocol
schema regenerated — additive optional fields only, hash
821c11cc... -> 41d3978a...).
Verification: go test ./internal/extension/... ./internal/agent
./internal/boot ./internal/control pass; full go test -race on
internal/agent and the extension packages pass; every point has
continue/block/replace/timeout coverage incl. permission override
triples, two-phase chain-then-strategy ordering, cache ephemerality
(run-2 request byte-equals the no-extension baseline), and snapshot
immutability across dispatch; gofmt/go vet clean; no new dependencies.
Problem:
Session event replay decoded an entire messages array before enforcing its count budget, and the ownership probe treated JSON field order as a persistence contract.
Root cause:
The replay decoder materialized []provider.Message in the outer event record, while the bounded probe required schema_version and type to be the first two keys.
Fix:
Keep message arrays as raw JSON until each element passes the aggregate message budget, and preserve order-independent header recognition for valid logs within the byte limit while retaining the bounded native fast path.
Verification:
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- focused agent and Desktop go test -race
- go vet ./internal/agent
- cd desktop && go vet .
- git diff --check
Problem: The Windows titlebar adjustment changed shared Creation topicbar geometry on macOS and Linux.
Root cause: The compact height, padding, and title offset were applied through platform-neutral .app--creation selectors.
Fix: Restore the shared Creation geometry and move the 40px alignment overrides behind Windows-specific selectors for both default and themed styles.
Verification: app-chrome-tabs.test.ts, CSS checks, TypeScript typecheck, and the production frontend build passed.
Integration:
- Sync usage statistics with the newly merged compaction-settings and diagnostics changes.
- Preserve both SettingsPanel subfeatures and their Wails/localization contracts.
Verification:
- go test -timeout=10m ./internal/stats ./internal/cli ./internal/config
- (cd desktop && go test -timeout=10m .)
- (cd desktop/frontend && pnpm typecheck && pnpm test:usage-stats && pnpm build)
Problem:
Native session event logs could be fully buffered and decoded during startup without byte, record, or message limits. Desktop also treated pinned-session load failures like a missing session and could open a blank replacement.
Root cause:
The ownership probe decoded the entire first event, replay trusted the complete log shape, and the pinned-session helper collapsed non-ENOENT load errors into a false result.
Fix:
Probe only the bounded native header, enforce replay budgets with a typed path-free error, and fail closed in Desktop while preserving session files and releasing partial runtime resources.
Verification:
- go test ./... -count=1
- cd desktop && go test ./... -count=1
- focused agent and Desktop go test -race
- go vet ./internal/agent
- cd desktop && go vet .
Problem:
Desktop update failures could leave users with only a retry action, while the update settings mixed recovery actions with low-frequency preferences and lacked a direct feedback path.
Root cause:
The frontend reduced updater failures to a manual-fallback boolean and did not distinguish recoverable pending transactions from transient failures or manual-only installation environments.
Fix:
Classify updater errors as retryable, recovery, or manual; prioritize the official signed installer for blocked flows; add official download, changelog, and GitHub feedback actions; and move privacy/update preferences into a collapsed footer section.
Verification:
- pnpm --dir desktop/frontend test:updater (33 passed)
- pnpm --dir desktop/frontend build
- in-app browser DOM and computed-style acceptance
Deleting a session silently swallowed non-2xx responses (the 409
'cannot delete active session' guard), so the click appeared to do
nothing. The delete button on the active session now shows a localized
notice ('无法删除当前会话' / 'Cannot delete the active session') without
calling the API, and any other failure response shows the server's
error text in a notice.
Integration:
- Sync the contributor branch with the latest main-v2 before final review.
- Preserve the reviewed usage-statistics changes while incorporating current Desktop, Remote, CLI, and provider fixes.
Verification:
- go test -timeout=20m ./...
- (cd desktop && go test -timeout=20m ./...)
- targeted Go race tests for provider, stats, agent, guardian, Remote, and Desktop
- (cd desktop/frontend && pnpm build && pnpm test:all)
Problem:
The Linux desktop CI build exceeded the initial raw JavaScript and CSS budget by 0.3 KiB after adding the compaction controls.
Solution:
Code-split the control-specific CSS with the lazy Settings panel, deduplicate preset copy, and simplify equivalent state handling and guidance without changing the interaction contract.
Verification:
- pnpm --dir desktop/frontend build
- pnpm --dir desktop/frontend test:typecheck
- 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
Problem:
Desktop exposes the automatic compaction threshold, but CLI users and scripts cannot inspect its effective source or update it safely.
Solution:
Add compact-ratio query, user-global writes, and project-local overrides with the same 65-85% guard rails and transactional config locking. Document the behavior in English and Chinese CLI/spec docs, localized help, and the website.
Verification:
- go test ./...
- go test ./internal/cli ./internal/config
- ./scripts/cache-guard.sh
- npm --prefix site test
- npm --prefix site run build
Problem:
The Linux desktop CI job failed its unused-code lint after the contextual WebView2 report builder replaced the old compatibility wrapper.
Root cause:
The wrapper no longer had production or test callers, but remained in wails_logger.go.
Fix:
Remove the dead wrapper and keep all call sites on the contextual report builder.
Verification:
golangci-lint v2.12.2 reports zero issues; go vet, all desktop Go tests, and Windows amd64 test-binary cross-compilation passed.
Problem:
The desktop CI go.mod tidy gate failed because the new Windows Runtime version helper imports go-webview2 directly while the module still classified it as indirect.
Root cause:
The implementation added the direct import without committing the dependency classification produced by go mod tidy.
Fix:
Move the existing go-webview2 requirement into the direct dependency block without changing its version, checksum, or local replacement.
Verification:
go mod tidy is stable; desktop Go tests, race tests, vet, and Windows amd64 test-binary cross-compilation passed.
Problem:
Usage statistics omitted HTTP attempts whenever a logical provider stream failed or ended without terminal token usage, so retries could appear as zero requests.
Root cause:
Providers owned the request counter, callers only emitted successful Usage records, and the statistics recorder rejected zero-token records.
Fix:
Reuse caller-owned attempt counters across built-in providers, synthesize request-only usage on failed or missing-usage streams, cover every direct model-call path plus the Remote broker, and persist those records without forwarding zero-token receipts to the UI. Aggregate request-only rows independently from model token distributions and preserve exact Desktop request totals.
Verification:
- go test -timeout=20m ./...
- go test -timeout=20m ./... in desktop
- go test -race on the affected root and Desktop packages
- go vet ./... in root and desktop
- pnpm typecheck
- pnpm test:usage-stats
Problem:
- The four-option segmented control truncated every threshold label at the settings width.
- Opening custom editing visually replaced the saved preset selection before any value was applied.
Root cause:
- Preset values and the custom-editor disclosure shared one selected-state control.
Fix:
- Render the three presets as equal-width two-line choices and keep their full accessible labels.
- Move custom editing to a separate disclosure beside an explicit saved-value summary.
- Preserve the saved preset selection while the editor is open and cover the state contract with regression tests.
Verification:
- pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- pnpm exec tsx src/__tests__/typography-overflow-contract.test.ts
- pnpm typecheck
- pnpm check:css
- pnpm lint:hooks
- ./scripts/cache-guard.sh
- In-app browser DOM, computed-size, overflow, and disclosure-state checks
Problem:
Recent hang telemetry mixed lifetime diagnostic counts with selected time windows, allowed synthetic versions to become the release baseline, and lacked enough context to distinguish WebView2 failures from UI measurement or sleep/resume artifacts.
Root cause:
Diagnostic group queries ignored the dashboard window, long code viewers fed virtual-row measurements back through React, the watchdog retained a pre-sleep heartbeat, and WebView2 failures were queued without per-session throttling or runtime attribution.
Fix:
Apply diagnostic window filters and stable release quorum rules, show deduplicated affected installs, use direct DOM virtualizer updates, reset the watchdog after sleep, tier heap reports, and add throttled WebView2 reports with runtime and occurrence context.
Verification:
Desktop Go tests, frontend focused tests and typecheck, crash-report worker tests and typecheck, and Windows amd64 cross-compilation passed.
Problem:
The custom compaction editor exposed a 65%–85% range without explaining why values outside it are unavailable.
Root cause:
The hint described only input mechanics and omitted the adjacent 60% tool-output cleanup and 90% forced-compaction watermarks.
Fix:
Explain both guard rails and their safety purpose in every supported locale, with regression coverage for the English contract.
Verification:
- pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- pnpm test:typecheck
- In-app browser DOM and wrapping checks
CleanupStaleRunning aborted startup on any undecodable .meta.json
(truncated write or killed process), reporting "cleanup stale
subagents: decode subagent metadata ...: unexpected end of JSON
input" and blocking the whole app. Skip JSON decode failures the same
way ListSubagentsByParent already does, while keeping genuine I/O
errors fatal.
Fixes#7191
Problem:
Usage accounting could block turn completion on file locks, undercount retried or continued provider calls, label turns as sessions, and render oversized custom ranges.
Root cause:
Recording performed disk I/O inline; request attempts were not carried through provider retry, reconnect, continuation, and recovery merges; the UI reused session wording; range work scaled with absent days and the full trend.
Fix:
Queue records through one bounded per-state-dir worker with settings and shutdown flush boundaries, attach HTTP attempt counts through provider contexts, preserve counts across recovery, rename completed turns, scan only existing stats files, and cap trend DOM to the latest 180 days with a notice.
Verification:
- go test -race ./internal/stats ./internal/provider ./internal/provider/openai ./internal/provider/anthropic ./internal/provider/responses
- go test ./...
- cd desktop && go test ./... && go vet ./...
- cd desktop/frontend && pnpm test:usage-stats && pnpm typecheck && pnpm build
Co-authored-by: SivanCola <[email protected]>
Problem:
The custom compaction threshold option rendered outside the fixed 320px settings control and was clipped by the segmented control overflow.
Root cause:
Segment buttons kept their intrinsic minimum width, so the fourth localized option could not shrink into the available column.
Fix:
Allow compaction options to share and shrink within the full control width, preserve truncated labels with titles, and add CSS contract coverage.
Verification:
- pnpm test
- pnpm test:typecheck
- pnpm build
- In-app browser boundary and interaction checks
submitHTTP rejects '!' shell commands (403) before the turn loop, so a
structured-output format attached to such input was parked in the
one-shot slot and leaked into the next real turn. isNonTurnHTTPInput now
returns true for '!' prefixes; the test assertion mirrors the actual
submitHTTP behavior.
The format field was forwarded to the controller without normalization:
arbitrary or whitespace-padded values would propagate into provider
requests and fail later with a vendor 400. Trim and validate up front —
only empty (default text) or json_object are accepted, anything else
returns 400 before the turn starts. Test covers rejection (xml -> 400,
runner never runs) and normalization (padded json_object -> 202).
SubmitHTTPFormat parked the requested format in a one-shot slot consumed
only by runGoalLoopWithRawDisplay. Slash commands (/compact /new /model
...), memory quick-adds, and /remember bypass the turn loop, so the slot
leaked into the next real turn — the next ordinary message would
unexpectedly request json_object output.
isNonTurnHTTPInput() now gates the slot: commands and memory shortcuts
never set it. Leak regression test covers both paths.
The field was carried over during the structured-output cherry-pick but
never referenced — cacheColdAfter() uses a package-level var
(controller.go:3397) with test override via reassignment. golangci-lint
v2 flags it as unused; removing it also aligns the struct with upstream
main-v2, which never had the field.
TestCompletedResponseDefaultsFinishReasonToStop and
TestAllZeroUsageCompletedIsSuppressed belong to a different response
behaviors change (FinishReason=stop synthesis, zero-usage suppression)
not included in this MiMo PR; their implementations live in the dev
branch's separate workstream. Removing them keeps the PR scoped to the
MiMo wire fixes while the remaining 25 tests (14 upstream + 11 MiMo)
all pass.
The stateful fast path compares conversationDigest(history) against the
digest recorded when the previous response completed; a mismatch forces a
full history replay instead of previous_response_id (server cache-hit
loss). The digest was built with hardcoded (vision=false, summary=false)
while the wire uses the client's real knobs, so vision-capable endpoints
and DashScope (summaryRequired) never matched.
Digest now uses the same vision/summary arguments as buildRequestBody.
Test asserts vision and dashscope-summary both change the digest.
BrokerProviderRequest gains ResponseFormat so a structured-output request
survives the Host->Desktop hop (schema regenerated: schema.generated.json,
schema_hash.generated.go, remoteProtocol.generated.ts). provider.ResponseFormat
Type gets its json tag ("type") so the DTO validates against the generated
schema.
Round-trip test extended to assert json_object survives encode/decode.
Phase 1 (wire): provider.Request gains an optional ResponseFormat; the
Responses provider serializes it as text.format (json_object) — the one
shape MiMo/DashScope/OpenAI all define. Nil omits the key entirely,
keeping the common path byte-stable for prompt caching.
Phase 2 (call path): serve /v1/chat accepts an optional "format"
field; Controller.SubmitHTTPFormat carries it one-shot into the turn
context (agent.WithResponseFormat, same pattern as runStepLimit) so the
agent's provider.Request picks it up without persistent state.
Tests: wire serialization + omission, context round-trip, and a live
MiMo check returning {"name":"Zhang San","age":28}.
Product: Keep presets on the default path and reveal exact threshold input only on request.
Interaction: Add focus-managed editing, explicit apply and cancel actions, keyboard recovery, and failure-atomic saves.
Budget: Raise the raw initial asset ceiling by 2 KiB for the measured feature cost while leaving gzip and per-chunk limits unchanged.
Verification: pnpm build; pnpm test; pnpm test:typecheck; focused settings snapshot coverage; in-app browser interaction check.
The wire schema's Reasoning item also carries an optional status
(in_progress | completed). Capture the final status from the streamed
output_item.done event and echo it back on the next turn's reasoning
item, completing the id/status/content round-trip.
Live MiMo check: turn 1 emits id rs_3d141808c8874636b3c8c308163c6c21
status completed; echoing {id,status,content} on turn 2 is accepted
(completed, 1.6s).
The OpenAI Responses schema marks Reasoning.id required on input items.
The streamed output_item.added event carries the provider-issued id
(rs_...); capture it into Message.ReasoningID and echo it back on the
next turn's reasoning item. Omitted when no id was captured, keeping
wire bytes stable for vendors that ignore it.
Live MiMo check: output items carry id rs_28d480b72c06448e8e2e67efbdf8025a
with type reasoning; round-tripping it matches the documented schema.
messagesToInput unconditionally serialized the DashScope-only `summary`
list on reasoning items for every vendor. MiMo's schema has no summary
field; its server folds the extra field back into the model context, so
each turn's chain-of-thought echoes both summary and content — doubling
reasoning output every tool round until max_output_tokens truncates the
turn mid-tool-call ("reply then stop" / stuck agent loops).
Vendor capability table gains summaryRequired (DashScope only). MiMo and
unknown endpoints now send content-only reasoning items, matching the
OpenAI base format. Live 6-round tool loop: reasoning stays flat
(236→230→140→144→144→151) vs the previous doubling; every round
completed.
MiMo's 32768 server default covers reasoning + visible output; its
thinking mode (low/medium/high all enable reasoning) can spend a large
chunk of that budget on reasoning before the visible answer, truncating
tool calls mid-JSON on long turns and breaking the agent loop. Add a
vendor capability defaultMaxOutputTokens (65536, within the documented
[1,131072] range) used only when the caller did not set MaxTokens.
Verified live: mimo-v2.5 high, max_output_tokens=65536 accepted,
status=completed.
MiMo's thinking is one uninterruptible segment per turn: the server
emits reasoning and the final answer atomically, and a new reasoning
segment only starts on a brand-new turn — never mid-turn after a tool
call (official docs: low/medium/high all enable reasoning with no
strength differentiation). DeepSeek, by contrast, can emit several
reasoning segments across a turn's tool loop.
Add vendorCapabilities.singleSegmentReasoning (mimo=true, others
false) so callers know not to expect a multi-segment chain-of-thought
from single-segment vendors. Verified live: mimo tool-call rounds pair
one reasoning segment with the call; no mid-turn re-thinking occurs.
Messages with attached Images on a vision-enabled provider serialize as
the documented InputItemList array form: [{type:input_text,text}] +
[{type:input_image,image_url}] per image, matching the MiMo/DashScope
multimodal example. Text-only turns keep the TextInput string shape —
images are the only trigger for the array form, so pure-text sessions
stay byte-stable.
- client.vision from Config.Extra["vision"]
- messagesToInput(messages, vision): user turns with Images -> array
parts; system/assistant and text-only user turns unchanged
- conversationDigest passes vision=false (images don't affect the
stateful prefix digest; image URLs are not stable across replays)
Tests: text-only stays string even with vision on; images embed as
input_image parts in order after input_text; vision-off ignores Images.
Verified live: mimo-v2.5 describes the official example image and
continues multi-turn context after the image round.
The responses provider did not implement WarnOnMissingToolCallReasoning,
so provider.WarnOnMissingToolCallReasoning fell back to
RequiresToolCallReasoning, which is true for MiMo — surfacing a
per-session warning when a MiMo tool-call turn arrived with empty
reasoning.
MiMo documents reasoning alongside tool calls but does not guarantee it
every round (observed: mimo-v2.5-pro tool-call turn with no reasoning),
so a missing chain-of-thought there is endpoint-conditional, not a
degradation worth warning. Gate the warning on deepseek non-flash
models, mirroring openai.go's expectsDeepSeekToolCallReasoning.
MiMo and other strict OpenAI-compatible backends reject a role=tool
message whose `name` key is absent (HTTP 400 "Param Incorrect, name is
not set"). A legacy empty-name tool result (#4727 shape, where backfill
has no source) previously vanished via omitempty, 400ing the whole
request.
Name becomes a *string: ordinary messages keep omitting the key
(byte-stable prompt prefix), while role=tool messages always serialize
it — empty string included.
Test: TestToolResultEmptyNameStillSerialized covers the both-names-empty
legacy shape; non-tool messages are asserted to stay name-free.
Single source of truth for Responses wire-level vendor differences,
replacing scattered string comparisons in responses.go.
- new internal/provider/responses/vendor.go: vendorCapabilities table
(stateless / sessionCacheHeader / toolCallReasoning / ignoresTemperature)
covering dashscope, deepseek, mimo, and the default OpenAI behavior
- responses.go: mode(), sessionCache, RequiresToolCallReasoning, and the
x-dashscope-session-cache header now consult the table
- mimo: skip temperature in the wire request (vendor forces 1.0/0.95 in
thinking mode per official docs)
- effort: MiMo exposes a binary thinking knob — /effort none|low|medium|
high accepted (none disables reasoning), plain OpenAI endpoints still
reject none
- cache_policy: detect api.xiaomimimo.com explicitly; TTL stays at the
24h unknown-vendor default pending a long-prefix (>8K tokens) TTL
measurement (short-prefix measured ~3.5-7.5min, vendor targets hours)
Tests: vendor capability table, temperature omission, MiMo effort none,
cache vendor detection. All pass; pre-existing migrate env test failures
(TestMigrateImportsLegacy*) are unrelated to this change.
MiMo's Responses API is stateless like DeepSeek's: previous_response_id
is not supported (planned phase two) and multi-turn tool calls must
retain historical reasoning in the input.
- DetectVendor: recognize api.xiaomimimo.com as vendor "mimo"
- mode(): default stateless for mimo (was stateful -> HTTP 400 on
previous_response_id)
- RequiresToolCallReasoning: preserve reasoning on tool-call turns for
mimo, matching its documented multi-turn requirement
- config: mimo-api preset users add responses_mode = "stateless"
Every provider 400 lands in one bucket, so the dashboard says 46 of them
happened last month and nothing about which. Triaging four such reports by hand
this week turned up four unrelated causes: a replayed image_url, a tool schema
missing `required`, a thinking parameter shaped as an object where a boolean was
expected, and reasoning content that had to be echoed back. Each needs a
different fix, and none is distinguishable from the bucket.
errorClass deliberately never uploads the message itself, because a provider
body can echo request content back. That constraint stays: every arm matches a
fixed substring and returns a fixed token, so nothing from the body reaches the
label. Unrecognized shapes stay plain http_400 rather than guessing.
The user-facing message is unchanged — control.apiErrorReason already appends
the provider's verbatim reason, which is why those four were diagnosable at all.
This closes the gap on the telemetry side, where the aggregate lives.
Cache-impact: none. desktop/metrics_app.go is not on the cache-sensitive path
list in scripts/check-cache-impact.sh; no prompt or tool bytes change.
Cache-guard: not applicable for the same reason.
Verified: go test ./desktop (full module), plus a test asserting that bodies
carrying paths, filenames and an API key produce labels containing none of them.
Integration: Bring the compaction threshold PR onto the current main-v2 baseline.
Resolution: Preserve the compaction presets while retaining the upstream GLM reasoning protocol option.
Verification: Conflict markers removed; focused feature verification follows in the next commit.
Stage 5 of the unified extension kernel / Extension Protocol v1 plan.
Problem: Reasonix had no process model for code-carrying extensions — no
stable sidecar protocol, no schema generator to keep it honest, and no
lifecycle for spawning, supervising, and retiring extension processes.
Fix:
- internal/extension/protocol: a new public JSON-RPC 2.0 protocol
(reasonix.extension.v1) with a frozen 16-method registry (initialize,
initialized, shutdown, intercept, event, resources/changed,
provider/catalog, provider/stream open|cancel|chunk|end, ui/action,
ui/submit, host/ui/publish, host/ui/request, host/content/read), strict
DTO decoders, frozen limits (8 MiB frames, 64 KiB externalization
threshold, 256 KiB content chunks, 8 MiB content objects), a frozen
error table, and a v1 stability contract (only optional fields, enum
values, and methods may be added within the major). Provider DTOs are
public copies of the provider shapes — internal types never cross the
wire, and credentials never appear in any DTO.
- internal/extension/protocolgen + cmd/extension-protocol-gen: generates
the canonical JSON Schema (draft 2020-12, sha256:821c11cc…), the schema
hash constant, and docs/EXTENSION_PROTOCOL.generated.md; -check enforces
drift-free artifacts and a determinism test double-generates byte-equal
output.
- internal/extension/sidecar: the only sidecar launch path. Authorization
invariant: runtimes start only from installed+enabled plugin state —
project configuration can never declare a sidecar. Exec-form spawn (no
shell, ${REASONIX_PLUGIN_ROOT} expanded, absolute command required) with
unfiltered inherited environment (the full-trust contract), 16 KiB
redacted stderr diagnostics, initialize-first handshake with capability
subset validation (subscriptions/replaces/providers/UI beyond the
manifest fail with capability_not_declared, version mismatch with
unsupported_version), bounded shutdown (shutdown RPC → stdin close →
750ms grace → process-tree kill → 5s reap), crash supervision that
cancels pending RPCs and fails later calls fast, a host content store
with externalization and chunked host/content/read, and per-point
intercept timeouts (5s input/tool/permission, 30s slow family, manifest
override clamped to 60s).
- internal/extension SessionExtensions: per-session private plugin state
at <session>.extensions.json, keyed by plugin ID, 1 MiB per-plugin cap,
atomic writes, corrupt-file tolerant, never touches the session JSONL.
- boot: BuildResult.Extensions carries the sidecar Manager; the RuntimeSet
is chained into controller cleanup (like LSP) so sidecars live exactly
as long as their controller generation and Rebuild retires only the old
set; required-runtime start failures and replacement-slot conflicts fail
the build, optional failures degrade to warnings. No runtime packages
installed means zero sidecars and a byte-identical golden surface.
Verification: go test ./internal/extension/... (sidecar also -race) and
go test ./internal/boot pass incl. golden baselines; extension protocol
generator -check green; re-exec fake-sidecar tests cover handshake
variants, capability violations, early-traffic poisoning, bounded
shutdown, mid-intercept crash, content-ref round trips, stderr redaction,
and boot end-to-end start/stop; gofmt/go vet clean; no new dependencies.
Problem:
Explicit help still returned status 2 for nested commands whose positional arity was checked before FlagSet parsing, while successful FlagSet help was written to stderr.
Root cause:
The shared reporter reused the FlagSet error output and could only see help after Parse; remote add, remote serve, and named subagent commands short-circuited before that boundary.
Fix:
Route successful help to stdout, recognize help within required positional prefixes before arity validation, and expand end-to-end coverage for help and error streams.
Verification:
- go test ./... -count=1
- go test -race ./internal/cli -count=1
- go vet ./...
- scripts/check-cache-impact.sh on all touched CLI files
- git diff --check
Problem:
Remote Workbench usage was written on the SSH target, Guardian reviews kept only the last model call, external child models could be misattributed, and stale frontend requests could restore obsolete data.
Root cause:
Accounting followed the remote controller filesystem instead of the local broker, review usage used a last-value slot, identity lookup ignored the authoritative resolver catalog, and incomplete or failed filters did not invalidate main-panel generations.
Fix:
Persist brokered remote usage and turn completions in Desktop state, aggregate every Guardian call including compaction, resolve child and capability-router identities through the resolver, and clear/invalidate stats on every filter transition.
Verification:
- go test ./internal/guardian ./internal/boot ./internal/remote/workbench/runtime ./internal/stats
- go test ./... -run focused remote statistics tests (desktop)
- pnpm test:usage-stats
- pnpm typecheck
Co-authored-by: SivanCola <[email protected]>
Problem:
Go's flag parser stops at the first positional argument, so the documented remote connect <name> [flags] form rejected flags placed after the host name.
Root cause:
remoteConnectCLI parsed the complete argument tail as one standard-library FlagSet and required exactly one leftover positional value.
Fix:
Extract a focused remote connect syntax parser, accept both name-first and flags-first forms, preserve the -open and remote open aliases, and carry deferred parse errors through the shared command flag reporting contract.
Verification:
- go test ./...
- go test -race ./internal/cli -run 'Test(ParseCommandFlags|ParseRemoteConnectSyntaxFlagOrder|RemoteConnectSyntaxUsesSharedFlagContract)' -count=1
- go vet ./...
- git diff --check
Co-authored-by: skyzhao1223 <[email protected]>
Problem:
Malformed pflag arguments could exit with status 2 without explaining the error, while subcommand help was treated as a failure.
Root cause:
Command entry points handled ContinueOnError results independently. pflag does not emit parse errors in this mode, and neither flag implementation's help sentinel was distinguished.
Fix:
Add a shared parser wrapper for standard flag and pflag, route all direct CLI FlagSet parsing through it, and preserve upgrade parser purity while returning help successfully.
Verification:
- go test ./...
- go test ./internal/cli ./cmd/reasonix -count=1
- go vet ./...
Cover the decision disclosure and conditional React Hook crash fixes that landed after the initial release-note review. Keep the stable record bilingual and bound to the newly reviewed Desktop behavior.
Problem:
ProviderEditor and WorkspacePanel could render fewer hooks behind early-return branches, leaving later state transitions vulnerable to React error #310.
Root cause:
Three provider memo hooks followed the built-in editor return, while workspace code-search hooks followed the closed-panel return. The frontend also had no automated rules-of-hooks gate.
Fix:
Keep every hook above the relevant early return, add built-in-to-custom and closed-to-open regression coverage, and run eslint-plugin-react-hooks during production builds.
Verification:
- pnpm lint:hooks
- pnpm exec tsx src/__tests__/provider-editor-model-picker.test.tsx
- pnpm exec tsx src/__tests__/workspace-changes-errors.test.tsx
- pnpm test:typecheck
- pnpm test
- pnpm build
- git diff --check
Problem:
Auxiliary model usage could be unattributed, empty stats responses exposed null arrays, torn JSONL tails consumed the next record, and top-provider ranking followed the top model.
Root cause:
The router and reviewer events omitted canonical model refs, the disabled query path returned nil slices, appends did not repair record boundaries, and provider ranking reused model ordering.
Fix:
Propagate canonical model refs through both auxiliary paths, centralize empty-array query responses, serialize appends under the existing cross-process file lock with torn-tail repair, and rank providers from aggregate totals. Add focused regression coverage for each contract.
Verification:
- go test -race ./internal/stats ./internal/capability ./internal/recovery
- go test ./...
- cd desktop && go test ./...
- go vet ./...
- cd desktop && go vet ./...
- cd desktop/frontend && npm test -- --run
- cd desktop/frontend && npm run build
Scope:
Bring the reviewed release-notes branch onto the latest main-v2 after the E2E runner repair, issue-label workflow fix, Token Rhythm provider integration, and CLI continuation shorthand repair.
Verification:
Release note validation and the exact-candidate CI will run again on the updated branch.
Scope:
Add the Token Rhythm provider and model-aware reasoning work from PR #7301, restore the CLI continuation shorthand fix from PR #7223, and credit the new contributor.
Review:
Refresh the bilingual title, summary, highlights, New section, and Fixed section without changing the reviewed Stable identity or migration guidance.
Verification:
node scripts/release-notes.mjs validate
node --test scripts/release-notes.test.mjs
node scripts/release-notes.mjs render --version 1.19.4 --output /tmp/reasonix-1.19.4-zh.md
git diff --check
Integration:
Merge main-v2 at f63bee144 after the Token Rhythm, unattended E2E, desktop icon repair, and remote runtime fixes landed.
Conflict resolution:
Keep the 2256 KiB raw initial-asset ceiling from the decision-disclosure branch; the combined production build measures 2255.3 KiB. Preserve both the decision disclosure strings and the new Token Rhythm and GLM locale entries.
Verification:
Passed the full frontend test suite and the production build with all bundle budgets on the integrated tree.
Stage 4 of the unified extension kernel / Extension Protocol v1 plan.
Problem: reasonix-plugin.json had no way to declare prompts, themes, or a
code runtime, no strictness against typos, and no path-safety beyond
lexical checks; install previews gave no signal that a code extension runs
with full trust.
Fix:
- Manifest v1 (apiVersion reasonix.io/plugin/v1): strict decoding with
DisallowUnknownFields at every level so typos fail with the field path;
unknown major versions rejected. v1 may mix legacy top-level fields with
contributes (skills/agents/commands/prompts/hooks/mcpServers/themes):
identical paths dedupe, same-key-different-definition is a manifest
error naming the key. Prompts are a new path-list resource exposed as
/<plugin>:<name> via the existing plugin-command semantics; themes are
root-confined globs matched against .reasonix-theme files. v1 path
safety rejects traversal, absolute paths, symlink escapes, and
non-regular theme files; missing paths are warnings. Manifests without
apiVersion parse byte-for-byte as before (legacy compat gates green).
- runtime declaration: exec-form command/args/env (never shell-interpreted)
with required/priority/intercepts/replaces/capabilities validated against
the extension kernel's interceptor points, replacement slots, priority
bounds, and {interceptors,strategies,providers,ui}. pluginpkg cannot
import internal/extension (extension->hook->pluginpkg cycle), so the
validator lists are duplicated with a sync test pinning them equal.
- Full-trust disclosure: install plans carry RiskHigh + a FULL TRUST
reason plus omitempty RuntimePlanInfo (command, intercepts, replaces,
capabilities) that old consumers ignore; CLI plugin show/doctor and
capdiag render prompts/themes counts and the FULL TRUST block; the
Desktop install preview renders a danger-styled FULL TRUST section with
the command line, interceptors, replacement slots, capabilities, and the
session/environment/permission risk statement. Install/update/replace/
--link continue to imply full trust with no second confirmation.
- Desktop plugin themes: plugin themes are discovered from enabled v1
packages, validated by the existing v2 theme validator, and shown
read-only (never copied into the user theme dir) with IDs
plugin:<plugin>:<theme>; the internal pack ID regex is unchanged.
Activating a plugin theme persists the full ID; when the plugin is
disabled or uninstalled, rendering falls back to the base style while
the ID is preserved byte-for-byte in desktop-theme-state.json so a
reinstall of the same plugin restores the theme (non-plugin unknown IDs
keep the old clear-and-save behavior; legacy state files load
unchanged). Theme assets serve through the existing content-addressed
route with digest re-verification.
Verification: go test ./internal/pluginpkg ./internal/installsource
./internal/capdiag ./internal/cli ./internal/extension pass (legacy
compat gates unmodified); cd desktop && go test . passes (plugin theme
discovery/fallback-preserve/restore/read-only/asset-route tests);
desktop frontend pnpm typecheck and pnpm test pass; docs/THEME_PACK.md
updated in both languages; gofmt/go vet clean; no new dependencies.
Problem:
Keyboard activation of long-description disclosures bubbled into global decision shortcuts, which could submit the current Ask answer, approve a tool call, or run the clear-context default. Decision cards also capped body and actions independently, so their combined height could still exceed the viewport.
Root cause:
The shared disclosure button did not own its activation key events, and PromptShelf had no single viewport-bounded scrolling owner for the complete decision content.
Fix:
Handle Enter and Space inside the shared disclosure control, wrap prompt content in one bounded internal scroller, keep confirmation footers outside that scroller, and remove nested body/action scroll regions.
Verification:
Passed frontend typecheck, CSS checks, focused Ask and decision-surface tests, the full frontend test suite, production build and bundle budgets, plus an in-app browser long-text and keyboard interaction check.
Subagent transcripts are first saved on completion, so BranchMeta.CreatedAt
was backfilled with the save moment. Seed the sidecar from run.Meta.CreatedAt
before Session.Save so history shows when the subagent started.
Problem:
The calibrated 400k-token cap was exhausted after three successful baseline tasks, so the final two scenarios were skipped and the release gate could never complete.
Root cause:
Current model and cache behavior uses roughly 425k tokens for the first three tasks while keeping provider cost low, but both the harness default and workflow override retained the older 400k limit.
Fix:
Raise the bounded fixed-suite budget to 800k in the harness and workflow, and add a regression contract for the five-task baseline. Failure or skipping still fails the workflow.
Verification:
go test ./cmd/e2ebench -count=5
go vet ./cmd/e2ebench
go run github.com/rhysd/actionlint/cmd/actionlint@latest .github/workflows/e2e-bot.yml
bash scripts/release-workflows.test.sh
Closes#2549.
The dropdown is a choice and "v2 — Go rewrite (1.x)" sits first, so reporters
running 0.x pick it. `Exact version` is pasted from `reasonix --version`, so
when it parses it is the better evidence: 0.x is the TypeScript line, 1.x the
Go rewrite. A value that does not parse — a commit sha, "main-v2" — leaves the
dropdown to decide, since a wrong guess is worse than the status quo.
Replaying both rules over all 1161 open issues: 11 are labelled differently,
every one of them v2 -> v1, and every one currently carries the wrong label.
Reported versions on those range from 0.3.0 to 0.53.2. Nothing moves the other
way, so the cross-check only fires where the evidence is unambiguous.
This matters beyond tidiness: filtering the tracker by `v2` is how the backlog
gets triaged, and those 11 are legacy reports sitting in that filter.
The workflow only fires on opened/edited, so existing mislabels are corrected
separately.
Problem:
Model bars and donut segments used a fixed 20-color palette, so theme and base-style changes did not update them and several colors had insufficient light-theme contrast. The PR's browser mock also pushed the initial bundle just beyond its raw-size budget.
Root cause:
The palette was stored as literal final hex colors in React while the rest of the statistics visuals used semantic CSS variables. The empty browser mock included provider aggregates the panel does not consume.
Fix:
Blend stable model seed hues with the active accent and foreground, including overflow colors beyond the primary palette. Add regression coverage for primary and overflow theme adaptation, and keep the browser mock limited to fields the panel reads.
Verification:
pnpm test:usage-stats
pnpm typecheck
pnpm check:css
pnpm build
Browser: Graphite dark/light and Aurora light; no console errors
Problem: Desktop settings normalized an explicit GLM reasoning protocol to auto and could erase it when saving a provider.\n\nRoot cause: The frontend protocol whitelist and localized selector labels were not extended when the backend GLM enum was introduced.\n\nFix: Add GLM to the settings whitelist, selector labels, locale contracts, and provider-view type documentation. Cover normalization and locale availability with focused regressions.\n\nVerification:\n- pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx\n- pnpm exec tsx src/__tests__/startup-settings-contract.test.ts\n- pnpm typecheck\n- pnpm build
Problem:
The real-provider benchmark ran headlessly without an automatic writer posture and without a usable Linux sandbox backend, so valid file edits were denied and the suite exhausted its token budget.
Root cause:
The harness launched reasonix run in its default Ask mode, while the workflow did not install bubblewrap on the runner.
Fix:
Launch fixed-suite tasks with --auto and install plus verify bubblewrap before running the benchmark. Explicit ask/deny rules and the sandbox boundary remain enforced.
Verification:
go test ./cmd/e2ebench -count=5
go vet ./cmd/e2ebench
go run github.com/rhysd/actionlint/cmd/actionlint@latest .github/workflows/e2e-bot.yml
bash scripts/release-workflows.test.sh
Problem:
The usage statistics subtab reused the generic model usage heading and appeared as 使用模型, which described model selection instead of statistics.
Root cause:
The subtab and the model configuration section shared settings.modelUsage even though they represent different concepts.
Fix:
Add a dedicated settings.modelTab.stats locale key for English, Simplified Chinese, and Traditional Chinese, and use it only for the statistics subtab.
Verification:
pnpm test:usage-stats
pnpm typecheck
Browser DOM verification confirmed the 用量统计 tab renders and activates with charts.
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:
Long decision descriptions could overlap adjacent options or grow approval surfaces beyond the viewport, while only Ask exposed a complete-text path.
Root cause:
Decision rows mixed fixed-density styling with surface-specific overflow behavior, and recovery approval removed the shared height limits.
Fix:
Move description overflow detection and disclosure into a shared PromptAction component, apply three-line summaries and full-text controls by decision risk, preserve dangerous runtime consequences, and bound recovery content with internal scrolling. Raise only the raw initial-asset budget by 6 KiB for the accessible disclosure behavior; gzip, CSS, and chunk budgets remain unchanged.
Verification:
pnpm typecheck
pnpm check:css
pnpm exec tsx src/__tests__/ask-card-layout.test.ts
pnpm exec tsx src/__tests__/decision-surface.test.tsx
pnpm test
pnpm build
Browser checks at 320, 768, 1024, and 1440px
Problem:
Usage stats accepted unbounded ranges, missed synchronous turns and hidden provider retries, and attributed several auxiliary model calls incorrectly. Source changes could also leave stale heatmap data visible.
Root cause:
Accounting was tied to UI TurnDone/Usage event shape, child runtimes propagated raw overrides instead of canonical identities, and stats rows had no request multiplicity. The lazy panel also kept locale resources in the eager bundle.
Fix:
Merge main-v2, add completion accounting capabilities, canonical model attribution, request multiplicity, bounded custom ranges, protocol-audit forwarding, and stale-result clearing. Keep usage panel locale/theme resources lazy and cover the current main sanitizer contract.
Verification:
go test -p 4 -timeout=5m ./...
go test -race -timeout=5m ./internal/stats ./internal/event ./internal/control ./internal/agent ./internal/guardian ./internal/serve
(cd desktop && go test ./...)
(cd desktop/frontend && pnpm test:usage-stats && pnpm typecheck && pnpm build)
Co-authored-by: HaoyueQin <[email protected]>
Summary:
Place both the e2e provider config and credential file under an explicit ephemeral REASONIX_HOME. Keep the checkout-level config only for diff mode.
Why:
The first credential-gate fix used the legacy XDG config directory. Current Linux Reasonix uses ~/.reasonix by default and strictly resolves provider secrets from <Reasonix home>/.env, so the fixed suite still reported missing credentials.
Testing:
- bash scripts/release-workflows.test.sh
- actionlint .github/workflows/e2e-bot.yml
- focused Reasonix home and credential path tests
- git diff --check
Privacy:
- Scanned the public diff for credential and private-path patterns.
Problem:
The startup repair could rewrite healthy Windows shortcuts and macOS aliases on every launch, overriding a custom Windows icon and causing unnecessary filesystem churn.
Root cause:
The repair validated ownership but did not distinguish stale version-scoped icon references or old alias targets from already healthy integrations.
Fix:
Repair Windows only when IconLocation points at the old versioned desktop executable, and leave healthy macOS aliases untouched when they already resolve to the running bundle.
Verification:
- go test ./...
- go test -race . -run TestRepairMacDesktopAliases|TestDesktopPackagesPreserveNativePlatformLaunchers
- go vet ./...
- Windows amd64 and arm64 cross-compilation
- git diff --check
Problem:
The startup repair rewrote every existing Reasonix shortcut and could discard user-customized targets, arguments, working directories, or descriptions.
Root cause:
Shortcut ownership was inferred from the filename alone, and the repair replaced all shortcut properties.
Fix:
Verify that the existing target belongs to the current Reasonix installation and update only IconLocation. Preserve unrelated shortcuts and every user-customizable property.
Verification:
- go test ./...
- go test -race . -run TestRepairMacDesktopAliases|TestDesktopPackagesPreserveNativePlatformLaunchers
- go vet ./...
- Windows amd64 and arm64 cross-compilation
- git diff --check
Problem:
Desktop shortcuts and Finder aliases could lose their icons after an update.
Root cause:
Windows shortcut icons referenced version-scoped executables removed by retention, macOS bundle replacement could leave Finder aliases pointing at an old bundle, and Linux desktop caches could remain stale after package upgrades.
Fix:
Repair existing Windows and macOS desktop integrations on startup, keep Windows shortcut icons on the stable launcher, and refresh Linux desktop caches from the package post-install script.
Verification:
- go test ./...
- go test -race . -run TestRepairMacDesktopAliases|TestDesktopPackagesPreserveNativePlatformLaunchers
- go vet ./...
- Windows amd64 and arm64 cross-compilation
- Wails macOS arm64 production build
- Linux nFPM post-install package smoke test
Summary:
Write the repository provider secret to the ephemeral global Reasonix credential file used by the candidate process. Fail fixed-suite e2e runs when any task fails or is skipped while still posting the report.
Why:
The e2e workflow injected the key only as a process variable, which the credential boundary intentionally ignores. The harness then reported 0/5 but returned success, creating a false-green release signal.
Testing:
- bash scripts/release-workflows.test.sh
- actionlint .github/workflows/e2e-bot.yml
- git diff --check
Privacy:
- Scanned the public diff for credentials, local user paths, and private temporary paths.
Closes#2419.
RunWithIsolatedUserState redirects HOME, USERPROFILE, XDG_* and AppData, which
covers every location addressed by a path. The OS keyring is not one of those:
it is per-user and has no environment variable in front of it, so legacy
credential migration walked straight past the sandbox into the real store.
On a machine with credentials saved, TestMigrateImportsLegacyStateHomeDotEnv-
Credentials fails by printing the developer's actual API key where its own
fixture was expected. On a clean CI runner the keyring is empty, so it passes —
the suite only breaks for people who use the tool they are working on, which is
why the failing package in #2419 stayed unexplained for two months.
Substituting the lookup once in TestMain makes isolation the default for the
package; the three cases that exercise the lookup already install their own.
Two things verify it, because they fail in different places:
scripts/credential-leak-check.mjs runs the suite and flags any credential
printed that appears nowhere in the repo's sources — a value the tests did not
write can only have come from the machine. It reported one leak before this
change and none after, and it is the only check that can observe the bug at
all, since it needs a populated keyring.
TestKeyringLookupStaysOutOfTheRealStore asserts the substitution itself, so it
holds on an empty CI keyring where the leak is invisible. Removing the
substitution turns it red.
Neither prints a credential; the tool reports a SHA-256 prefix and the guard
reports a byte count.
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:
Remote Workbench events carried the Host runtime epoch while the shared frontend surface could still retain its Local epoch, causing valid Remote events to be discarded.
Root cause:
Remote Meta and tab projections did not publish the Remote runtime identity, and initial pending-prompt replay could overtake the runtime:rebuilt transition after subscription.
Fix:
Project the authoritative runtime epoch through Meta and ListTabs, publish the initial Remote epoch inside the committed subscription fence, and restore the Local epoch on every switch or disconnect path. Add frontend and Desktop regressions for Local-to-Remote event admission and Local restoration.
Verification:
- cd desktop && go test ./...
- cd desktop && go vet ./...
- cd desktop && go test -race . -run Workbench -count=1
- go test -race ./internal/remote/workbench/client ./internal/remote/workbench/runtime -count=1
- cd desktop/frontend && pnpm typecheck && pnpm test:typecheck
- cd desktop/frontend && pnpm test:remote
- cd desktop/frontend && pnpm exec tsx src/__tests__/pending-prompt-stale-status.test.tsx
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:
The PR merge exceeded the desktop initial raw bundle budget by 0.2 KiB.
Root cause:
Each Creation hover menu kept separate enter and leave timer refs even though those timers are mutually exclusive.
Fix:
Reuse one timer ref per menu while preserving the existing hover delays and close guards.
Verification:
- frontend test suite
- frontend typecheck and test:typecheck
- CSS checks
- frontend production build
Co-authored-by: SivanCola <[email protected]>
Problem:
Late no-op profile responses and epoch-less Desktop event projections could cross a runtime replacement boundary and restore or mutate stale client state.
Root cause:
The Desktop client allowed multiple mutations in flight and copied unchanged profile results back into authority, while the Remote adapter discarded SessionEvent.RuntimeEpoch.
Fix:
Serialize client mutations and session selection, keep unchanged profile results side-effect free, preserve event runtime epochs, and add deterministic regression coverage.
Verification:
- go test ./...
- go test -race ./internal/remote/workbench/client ./internal/remote/workbench/runtime -count=1
- (cd desktop && go test ./...)
- (cd desktop && go test -race . -run RemoteWorkbench -count=1)
- (cd desktop/frontend && pnpm typecheck)
- Windows/ARM64 integration-tag test binary cross-compile
Adds a "Usage Statistics" subtab to the desktop Settings → Models page:
time-range presets (7/14/30/90 days or custom), token/session/request
cards, active-days + average cache hit-rate + top-model cards, a fixed
40-week activity heatmap with per-day cache hit rate, a stacked per-day
token trend overlaid with a smooth cache hit-rate curve, and a per-model
donut + list. All charts are hand-drawn SVG; component CSS loads on demand
to stay inside the initial bundle budget.
All frontends (desktop, cli, serve, bot, remote workbench) record usage
through one passthrough stats.Recorder mounted at the boot layer after
event.Sync. event.Usage carries a new ModelRef field so tokens attribute to
the exact model; event.TurnDone rows count completed turns. Records are
appended as daily JSONL under the user state root (<state root>/stats/), so
usage survives app upgrades and accumulates from the day the feature ships.
The desktop App.UsageStats binding serves range aggregates, and the panel
can split totals by entry point.
Why a dedicated recorder: usage accounting is observational — the stats
sink must never alter the event stream, and a bad disk state must never
interrupt a turn, so writes are best-effort (matching the telemetry append
idiom).
Problem:
The Windows release checks only exercised launcher path resolution in a unit test, so packaging regressions across Scoop installation, its current junction, and Start Menu shortcuts could still ship.
Root cause:
CI did not install a Reasonix fixture through the real Scoop CLI or launch the generated shortcut.
Fix:
Add an isolated Windows integration test that builds a versioned fixture, installs it with Scoop v0.5.3, verifies the current junction and shortcut contract, and launches the desktop marker through that shortcut. Run it on every Windows CI leg.
Verification:
- go test ./internal/desktoplauncher/... ./internal/installlayout/...
- go vet ./internal/desktoplauncher/... ./cmd/reasonix-launcher
- GOOS=windows GOARCH=amd64 go build ./cmd/reasonix-launcher
- actionlint .github/workflows/ci.yml
- git diff --check
Problem:
Brief pointer passes over the Creation task/profile pills could enter a closing-only state even though their menus never opened.
Root cause:
The hover-leave handlers cancelled the pending open timer but always scheduled a close timer, which later rendered the popover's closing phase and open trigger styling.
Fix:
After cancelling pending hover timers, schedule a close only when the corresponding menu is actually open or already closing. Add deterministic timer-driven coverage for both pills and preserve sustained-hover opening.
Verification:
- pnpm --dir desktop/frontend exec tsx src/__tests__/composer-run-strip.test.tsx
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend test:typecheck
- pnpm --dir desktop/frontend check:css
- pnpm --dir desktop/frontend test
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Stage 3 of the unified extension kernel / Extension Protocol v1 plan.
Problem: there was no shared runtime rebuild entry. Desktop settings
changes hand-rolled a fail-atomic controller rebuild; the CLI could only
hot-swap slash commands; ACP had no reload surface at all. The extension
protocol needs one build entry that returns Controller + Snapshot +
RuntimeSet, and one reload semantic every frontend shares.
Fix:
- boot.BuildRuntime returns BuildResult{Controller, Snapshot, RuntimeSet};
boot.Build keeps its exact signature as a compat wrapper. The snapshot is
frozen from the same in-hand objects boot assembled (never re-derived),
with a process-wide monotonic generation; assembly degrades to a warning
rather than failing a boot that would have succeeded before.
- extension.Builder gains ConflictCollect: legacy resources keep today's
tier-winner shadow behavior with conflicts recorded on
Snapshot.Diagnostics(); strict ConflictFail remains the default and is
what v1 extensions will use.
- boot.Rebuild builds a replacement runtime fail-atomically: on any build
or migration failure the new controller is released and the old one keeps
working untouched. It migrates history (same session file, fresh system
prompt spliced in), approval grants, tool-approval/plan mode, goal and
recovery state (new public control.CarryRecoveryFrom), and lifecycle; it
never closes the old controller — frontends own the swap.
- One reload semantic in all three frontends: busy (turn/rebuild in
flight) coalesces to exactly one queued reload drained when idle; idle
rebuilds, swaps atomically, and only then retires the old controller.
CLI gains /reload (TUI reuses its model-switch swap path); ACP gains the
vendor method _reasonix.io/session/reloadExtensions (the ACP v1
_<vendor>/ namespace is wire-required, so not the bare reasonix/... form)
advertised in initialize _meta; Desktop gains the bound ReloadRuntime
action reusing the existing mutation lock, deferred-rebuild queue, and
runtime:rebuilt fence, plus a command-palette entry (en/zh/zh-TW).
Verification: go test ./internal/boot ./internal/cli ./internal/acp
./internal/extension ./internal/control pass (extension also under -race);
TestGoldenBaselineNoExtensions green unmodified; cd desktop && go test .
passes; desktop frontend pnpm typecheck and pnpm test pass; gofmt/go vet
clean; no new dependencies.
With the rollup from #7279 populated, the 30-day view is the fast one: reading
it off metric_user_rollup measures 68ms against production, versus 5,667ms for
the live 7-day query it was defaulted to in #7271. Leaving the default at 7 days
now means defaulting everyone to the slower path and the less representative
window.
That also retires windowExplicit. It existed to stop a module default from
following the reader into other modules; with no module default left it is
always equivalent to windowDays === 7, so it is dead weight.
A precomputed window can go stale silently if the rollup cron stops, so the
panel heading now carries the oldest computed_at across the rollup — the cursor
refreshes a slice at a time, so that is how far behind the least recently
recomputed signal is.
Summary:
Describe the Scoop junction failure as install-root validation so the 1.19.4 notes match the launcher error boundary in #7275.
Verification:
Validated the release catalog, ran the release-note tests, rendered the reviewed bilingual notes, and scanned the public text for private data.
Closes#7272.
The preferences module's 30-day COUNT(DISTINCT install_id) spans ~28M rows of
metric_users and D1 abandons it mid-query, so #7271 pinned the module to the
7-day window it can still finish. This computes the 30-day answer ahead of time
instead.
It has to be computed, not derived: a cross-day COUNT(DISTINCT) cannot be summed
from per-day totals, because an install active on twelve days would count twelve
times. Benchmarks ruled out the alternatives — storing one JSON row per install
per day and expanding it with json_each took 103s at production shape, worse
than what we have, because parsing JSON per row costs more than the I/O it
saves.
What does work is the query split by signal: measured against production, one
signal over the 30-day window is ~1s. An hourly cron walks the signal list eight
at a time behind a cursor, so a run stays well inside its CPU budget and a full
pass takes about seven hours. Each signal's rows are replaced in a single batch,
so a reader never observes one half-written, and a signal whose query is
abandoned still advances the cursor rather than blocking every signal behind it.
The dashboard reads the rollup for the 30-day desktop window and keeps querying
live everywhere else. Until the first pass completes the panel says the
precompute has not reached every signal yet, rather than showing an empty one.
Both tables are created at runtime, matching how ingest_sentinel_state and the
cli_* tables bootstrap, so no manual migration is needed.
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: The latest main-v2 added failed-stream usage accounting in the same run-loop site wrapped by missing-reasoning recovery, leaving the PR conflicting.
Root cause: The recovery branch returned only an error when a replay was canceled, while the new base expects every failed stream to retain usage for truthful accounting.
Fix: Merge current main-v2 without rewriting branch history, preserve the DeepSeek recovery wrapper and base usage emitter, carry merged first-attempt and retry usage through cancellation, and add a deterministic cancellation regression.
Verification: focused recovery and cancellation tests passed 20 times; go test ./... passed; go test -race ./internal/agent -count=1 passed; two post-merge real DeepSeek transient and persistent recovery rounds passed.
Context:
Bring the published fix branch onto the current main-v2 without rewriting its public history.
Integration:
Preserve the Remote Workbench profile idempotence, runtime replacement, frontend deduplication, and Windows physical acceptance coverage while incorporating the latest agent and desktop fixes.
Verification:
The merged head will be regenerated and revalidated with protocol, desktop, frontend, race, and physical Windows Remote Workbench tests before opening the pull request.
# Conflicts:
# desktop/frontend/src/generated/remoteProtocol.generated.ts
# internal/remote/protocol/schema.generated.json
# internal/remote/protocol/schema_hash.generated.go
Problem: The credential-gated DeepSeek probe required every stripped reasoning response to produce an identical retry, so a valid visible-text fallback could fail the test even though the runtime stayed safe.
Root cause: The live assertion did not distinguish a retryable tool-only response from a response whose visible text intentionally suppresses retry to avoid duplicate output.
Fix: Validate single tool execution, zero user warnings, and the fallback outcome before treating no-retry responses as alternate provider shapes and continuing the bounded probe.
Verification: focused recovery regressions passed 20 times; the internal agent suite passed; the live-tag race build passed; three real DeepSeek transient and persistent recovery rounds passed.
Problem:
Windows CI may expose a temporary directory through an 8.3 short path while GetFinalPathNameByHandle returns the equivalent long path.
Root cause:
The regression test compared path strings, so two names for the same Windows directory produced a false failure.
Fix:
Compare the resolved and expected directories with os.SameFile so the assertion verifies filesystem identity across long and short path aliases.
Verification:
Ran the local desktoplauncher tests and cross-compiled the Windows test binary for amd64 and arm64.
Problem:
The Windows physical Remote Workbench fixture required the Broker user message to equal the bare prompt, but current prompt assembly prepends the response-language contract.
Fix:
Keep the semantic protection by requiring the final message to have the user role and contain the original acceptance prompt, while allowing stable runtime-owned wrappers.
Verification:
- GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go test -c -tags reasonix_remote_integration ./desktop
Problem:
The Desktop launcher fails when Scoop invokes it through the stable current directory junction because the install-root guard receives the unresolved alias.
Root cause:
filepath.EvalSymlinks does not reliably canonicalize Windows directory junctions, and the previous fallback silently retained the junction path.
Fix:
Resolve the running launcher through a Windows file handle with GetFinalPathNameByHandle, normalize DOS and UNC extended paths, preserve the existing non-Windows fallback, and cover the junction path in Windows CI.
Verification:
Ran root and Desktop Go test and vet suites, focused race tests, repeated the unrelated timing-sensitive agent test, cross-compiled Windows launcher tests and binaries for amd64 and arm64, and validated the CI workflow.
Problem:
The physical Windows Remote Workbench acceptance covered SSH, tools, history, and reconnects but did not exercise the profile feedback-loop regression from issue #7268.
Fix:
Apply the current collaboration, approval, and empty Goal profile twice over the live Windows OpenSSH connection. Require both calls to return unchanged, preserve the runtime epoch, and keep the subscribed snapshot authoritative before continuing the tool loop.
Verification:
- GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go test -c -tags reasonix_remote_integration ./desktop
- cd desktop && go test . -run profile/runtime focused suite
Problem:
The opt-in Windows Remote Workbench acceptance test no longer compiled after the Windows SSH factory constructor began returning the shared transport.Factory interface.
Root cause:
The fixture still called Windows-specific peer identity and process lifecycle helpers directly on the interface result.
Fix:
Assert the expected concrete Windows factory immediately after construction so the physical fixture can inspect authenticated peer identity and verify SSH process cleanup.
Verification:
- GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go test -c -tags reasonix_remote_integration ./desktop
- cd desktop && go test . -run profile/runtime focused suite
Problem:
Remote Workbench could enter an unbounded session/profile/set and session/resync_required loop, making the Windows UI unresponsive.
Root cause:
No-op profile writes still invalidated subscriptions, snapshot refreshes emitted synthetic runtime rebuilds, and frontend profile deduplication followed prompt churn instead of actual runtime identity.
Fix:
Return an explicit unchanged disposition for idempotent profile writes, emit runtime replacement events only for real controller identity changes, serialize profile and session rotations, and deduplicate frontend profile application by runtime identity and profile content. Apply the same no-op discipline to Goal mutations and session rotations.
Verification:
- go test ./internal/remote/protocol ./internal/remote/protocolgen ./internal/remote/workbench/client ./internal/remote/workbench/runtime
- go test ./...
- cd desktop && go test ./...
- cd desktop/frontend && pnpm test
- cd desktop/frontend && pnpm typecheck
- go test -race ./internal/remote/workbench/client ./internal/remote/workbench/runtime
- cd desktop && go test -race . -run profile/runtime focused suite
metric_users takes ~1M rows a day, so its 30-day COUNT(DISTINCT install_id)
now spans ~28M rows and D1 gives up on it — measured against production, the
query comes back as a CPU-limit reset. Dropping the window-scan index in
#7258 fixed the tables around it but could not fix this one: the 7-day window
completes in 5.7s over 6.9M rows, the 30-day one does not complete at all.
So the module now defaults to 7 days. An explicitly chosen 30d still runs,
and when it fails it says so: metricUserRows returns null for did-not-complete
rather than folding it into the empty array, which the dashboard was rendering
as its no-metrics-yet state — a timeout reading as nobody using these settings.
A module default must not leak, so windowExplicit tracks whether the reader
picked the window; only a chosen 7d is carried into the other modules' links.
This is a stopgap. The real fix is pre-aggregation, which needs its own
design: a cross-day COUNT(DISTINCT) cannot be summed from daily rollups.
Resolution:
Merge the latest main-v2 without rewriting the contributor branch. Resolve the frontend package conflict by retaining upstream test scripts, migrating Task Monitor tests to the repository's tsx/jsdom harness, and removing stale Vitest configuration.
Integration:
Lazy-load the Task Monitor panel so the updated base remains within production bundle budgets. Keep the Task Monitor component suite in the desktop CI gate.
Verification:
- go test ./...
- go vet ./...
- cd desktop && go test ./...
- cd desktop && go vet ./...
- go test -race ./internal/taskmonitor ./internal/cli ./internal/jobs
- desktop Task Monitor routing tests under race
- pnpm test:terminal
- pnpm test:task-monitor
- pnpm typecheck
- pnpm check:css
- pnpm build
Problem:
The client reasoning guard could return without canceling the provider stream, while automatic DeepSeek output limits also capped thinking-disabled requests and Responses vendor detection accepted substring matches.
Root cause:
Agent stream consumption did not own a child cancellation context, and provider defaults were selected before effective reasoning mode and strict hostname classification were applied.
Fix:
Cancel every provider stream when Agent.stream exits, scope the automatic 32K DeepSeek budget to reasoning-enabled requests while preserving explicit limits, and detect Responses vendors from parsed hostnames. Add focused lifecycle, serialization, and spoofed-host regressions.
Verification:
- go test ./internal/agent ./internal/provider/openai ./internal/provider/responses ./internal/provider/anthropic -count=1
- go test -race ./internal/agent ./internal/provider/openai ./internal/provider/responses -count=1
- go vet ./...
- REASONIX_RELEASE_CACHE_GUARD=1 go test ./internal/agent -run TestReleaseCacheHitGuard -count=1 -v
Co-authored-by: SivanCola <[email protected]>
Problem:
Malformed or legacy provider catalog payloads can contain null display fields, which makes model search, provider grouping, and settings rendering unsafe.
Root cause:
The frontend trusted non-null TypeScript bridge contracts at runtime and passed provider/model/name/baseUrl values directly to string operations.
Fix:
Normalize model and provider display metadata at their component ingestion boundaries, preserving the hook-order fix from #7195 while adapting the defensive null handling contributed in #7239.
Verification:
- pnpm test
- pnpm run typecheck
- pnpm exec tsx src/__tests__/provider-editor-model-picker.test.tsx
- pnpm exec tsx src/__tests__/model-switcher-refresh.test.tsx
- pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- git diff --check
Co-authored-by: Ardian Nurcahya <[email protected]>
Problem: one healthy tool-call turn immediately re-armed silent recovery, so an intermittently broken endpoint could flap into repeated extra requests.
Root cause: the v2 incident state had only missing and resolved watermarks, with no compatible way to retain consecutive healthy progress.
Fix: keep the v2 document version, add optional streak and healthy-watermark fields, require three consecutive healthy turns, and protect in-memory, cross-process, stale-event, duplicate-event, and I/O-failure boundaries.
Verification: focused regressions (20x), go test ./..., go test -race ./internal/agent, go vet ./..., golangci-lint, and the cache guard.
Co-authored-by: orz0219 <[email protected]>
Co-authored-by: Lxiny-zy <[email protected]>
Problem: Classic and Workbench rendered different right-dock tab geometry on Windows, while the independent terminal drawer also appeared as a fifth workspace tab.
Root cause: Workbench compressed its tab spacing separately, Classic reserved native window-control space on an already separated row, and the terminal drawer action was duplicated in the dock tab list.
Fix: Keep four shared workspace tabs, retain terminal access in the topic bar, separate Workbench window controls from the tab row, and share dividers and narrow-width spacing across Classic and Workbench.
Verification: app-chrome-tabs 120/120 passed; workspace-layout 40/40 passed; CSS syntax and z-index checks passed; production frontend build and bundle budgets passed; Windows browser preview showed matching complete tab geometry with no console warnings or errors.
Problem:
Task lifecycle state conflated persisted intent with live execution, while Resume only moved failed or stale tasks to queued without restarting work. Desktop control also reused mutable killer routing across sessions.
Root cause:
Snapshots had no runtime-liveness dimension, job IDs were session-local, and ControlService stored a shared killer. Recorder completion could also lose its terminal update after a concurrent CAS write.
Fix:
Add backward-compatible runtime_state evidence, rename Resume to Requeue, route killers by session per call, retry completion CAS conflicts, and expose both state dimensions in Desktop. Add Workbench access and run Task Monitor component tests in CI.
Verification:
- go test ./...
- go vet ./...
- go test -race ./internal/taskmonitor ./internal/cli ./internal/jobs
- cd desktop && go test ./...
- cd desktop && go test -race ./...
- pnpm test:task-monitor
- pnpm typecheck
- pnpm check:css
- pnpm build
Problem:
The custom provider model picker crashed with React error #310 after an asynchronous model fetch changed the candidate list from empty to populated.
Root cause:
ProviderEditorModelPicker returned before useDeferredValue for an empty candidate list, so consecutive renders called a different number of React hooks.
Fix:
Keep the hook order stable by calling useDeferredValue before the empty-list return, and add a focused regression test for the empty-to-loaded transition.
Verification:
- pnpm exec tsx src/__tests__/provider-editor-model-picker.test.tsx
- pnpm exec tsx src/__tests__/provider-model-refresh.test.ts
- pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- pnpm run typecheck
- git diff --check
Co-authored-by: SivanCola <[email protected]>
Contract: Providers without the DeepSeek tool-call reasoning policy must keep the ordinary tool loop when reasoning content is absent.
Coverage: Assert two provider calls, one tool dispatch, and zero protocol recovery audits.
Verification: go test ./...; focused agent and race tests.
Stage 2 of the unified extension kernel / Extension Protocol v1 plan.
Problem: runtime assembly (tools, skills, commands, hooks, MCP, providers)
is spread across boot with emergent last-writer-wins shadowing and no
single frozen view of what a turn actually uses. The extension protocol
needs one immutable snapshot per generation that turns, tool batches, and
compaction can pin, with explicit conflict and replacement rules.
Fix: introduce internal/extension, purely additive:
- ContributionSource/Contribution/Catalog: every resource (tool, skill,
command, prompt, hook, MCP server, provider, theme, UI action,
interceptor, strategy) is a contribution with a canonical ID, scope tier,
priority, and registration order; catalog iteration is fully sorted so
discovery order can never leak into outputs.
- Builder pipeline Discover -> Parse -> Validate -> Resolve -> Assemble ->
Freeze -> Activate with a documented Activator seam (sidecars arrive in a
later stage). Resolve keeps the existing tier-winner shadow behavior
across scopes but fails the build with a ConflictError naming both
sources when same-tier contributions from different sources collide on
one canonical ID; hooks and interceptors stay additive.
- Replacement slots (system_prompt, context, provider_request,
provider_response, compaction, session_policy, permission,
frontend_events, tool:<name>, provider:<ref>) with single-owner claims;
a second claimant fails with SlotConflictError.
- Interceptor points (17 events) with priority -1000..1000 and stable
ordering: priority asc, plugin ID, registration order.
- RuntimeSnapshot frozen with copy-returning accessors, canonical tool
schemas, interceptor chains, replacement owners, and a sha256 CacheHash
over {systemPrompt, toolSchemas}; RuntimeSet owns per-generation closable
resources with idempotent Close and a generation guard so stale cleanup
cannot close a newer runtime.
- Adapters wrap existing discovery (builtin tools, skill.Store,
command.LoadRoots, hook.Load, plugin.Spec, provider.Descriptor) without
changing any existing package.
Verification: go test ./internal/extension/ -count=1 and -race pass
(~35 tests incl. 100-seed permutation determinism over schemas, chains,
and hash; conflict/slot/ordering/immutability cases); go vet and gofmt
clean. Existing packages untouched; golden baselines still green.
#7258 dropped the three desktop indexes that cost the dashboard its window
prune, but missed their cli_ mirrors — and those are worse than a stale
migration: ensureCLITelemetrySchema issues the CREATE INDEX statements on
every cold start, so dropping them in D1 only lasts until the next CLI
request. Production still had all six.
The cli_ tables carry the same primary keys as their desktop counterparts,
so they lose the same way. Remove the CREATE INDEX statements from both the
runtime schema and migrate-client-surface.sql, and extend the drop migration
to cover all six.
The migration is now ordered against a deploy: it must run after a worker
that no longer recreates them.
Stage 1 of the unified extension kernel / Extension Protocol v1 plan.
Problem: the extension kernel rebuilds runtime assembly (tools, skills,
commands, hooks, MCP, providers) behind a Builder that freezes an immutable
RuntimeSnapshot. Before touching that assembly chain we need executable
proof of the current provider-visible surface, because any byte of drift in
the system prompt, tool schemas, or request serialization cold-starts the
provider prompt cache for every session.
Fix: add regression gates only; no production code changes.
- internal/boot TestGoldenBaselineNoExtensions: committed goldens for the
composed system prompt (machine-independent: environment probes disabled
in the fixture config, workspace root normalized to <ROOT>), the
provider-visible tool contract via the canonical schema path, a synthetic
provider request serialization, and the agent.PrefixShape cache hash.
Regenerable via REASONIX_UPDATE_GOLDEN=1 for reviewed, deliberate drift.
- internal/pluginpkg legacy manifest + plugin-packages.json round-trip
gates: manifests without apiVersion parse exactly as before (unknown
fields ignored, paths sorted, autoStart:false normalized to nil, hook
args presence bit preserved), and state v1 saves keep every non-empty
field while empty strings stay omitted (omitempty status quo).
- internal/agent legacy session JSONL round-trip: pre-event-log sessions
load and re-save with all seven messages intact.
- desktop theme state: v1 state migrates to v2 on save, user theme IDs
round-trip, base-style IDs are never persisted, garbage files fall back
to the default state.
Verification: go test ./internal/boot ./internal/pluginpkg ./internal/agent
and cd desktop && go test . -run TestLegacyThemeState pass;
./scripts/cache-guard.sh passes (all cases tail_avg >= 92, threshold 90).
Problem:
A byte-identical missing-reasoning retry can return a normal final answer instead of another tool call, but the recovery audit classified every non-missing retry as recovered reasoning. The live DeepSeek behavior also lacked a reusable credential-gated regression probe.
Root cause:
The recovery outcome only distinguished another missing tool-call response from all other retry shapes, so a replaced response inflated the recovered counter even though the discarded tool call correctly stayed unexecuted.
Fix:
Record retry-replaced responses separately, preserve whole-response adoption before tool execution, cover the shape change and usage merge deterministically, and add bounded transient/persistent live probes against the official API without logging credentials or content.
Verification:
- go test ./...
- go test -race ./internal/agent
- go test -race ./internal/event ./internal/cli ./internal/telemetry ./internal/notify
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
- env -u DEEPSEEK_API_KEY go test -tags live ./internal/agent -run TestLiveDeepSeekFlashMissingReasoningRecovery -count=1
Problem:
The reasoning byte guard was converted into one 32K provider token limit and sent to every backend, coupling a local safety fallback to unrelated provider wire contracts.
Root cause:
The agent owned both controls, so OpenAI-compatible, Responses, Anthropic, DeepSeek, and unknown gateways could not choose model-appropriate fields or defaults.
Fix:
Keep the client byte guard independent, add provider and per-model max_output_tokens configuration, default only official DeepSeek endpoints to 32K, and serialize each supported protocol's native field while leaving unknown gateways unchanged unless configured. Merge current main-v2 and retain both status-bar test suites.
Verification:
go test focused provider/agent/config/boot suites; go test -race focused suites; desktop go test; frontend test/build/typecheck; cache guard; go vet; diff/gofmt checks.
Co-authored-by: SivanCola <[email protected]>
Problem:
DeepSeek V4 Flash can intermittently return tool calls without replayable reasoning_content, exposing a provider-protocol warning and degrading later tool rounds.
Root cause:
The agent treated the malformed turn as a user-visible diagnostic and immediately committed it, while the persisted cooldown only rate-limited the notice instead of attempting recovery.
Fix:
Retry one byte-identical request before tool execution, keep speculative stream events invisible, reuse the configuration-scoped cooldown as a recovery circuit breaker, preserve the empty-key fallback, and record content-free recovery metrics.
Verification:
- go test ./...
- go test -race ./internal/agent
- go vet ./...
- golangci-lint run --timeout=5m
- ./scripts/cache-guard.sh
* perf(crash-worker): drop the window-scan indexes the dashboard loses to
Every dashboard aggregate is `WHERE date >= ... GROUP BY <other columns>`,
and each table's primary key already starts with `date`. Offering SQLite a
second index on the GROUP BY columns makes it prefer an ordered full-index
scan, which skips the window prune and reads every retained row back through
the table. The same query shape with a `date <` upper bound already picks the
primary key, which is why the previous-window delta was never slow.
Measured on seeded copies, 65 days retained against the 30-day window:
metric_users 5.3M rows 31.6s -> 3.3s
metrics 180k rows 807ms -> 88ms
pings 568k rows 672ms -> 459ms
No query filters these tables by the dropped columns, so this also takes
their maintenance cost off the ingest path.
* perf(site): bake contributor avatars as 128px webp
The prebuild wrote whatever GitHub handed back straight to `<login>.png`.
Of the 98 baked files, 58 were actually JPEG behind a .png name, 19 came
back at 420x420 despite the ?s= hint, and the heaviest 120x120 avatar was
76 KB. The homepage renders all 98 at 60 CSS px.
Re-encode through sharp at twice the largest rendered size instead of
trusting the response. Avatars 823 KB -> 252 KB; the homepage drops from
842 KB to 371 KB, still 103 requests but a little over half the bytes.
sharp was already resolved in the lockfile as an astro dependency; it is
promoted to a direct devDependency so the prebuild does not rely on hoisting.
Summary:
- validate both legacy flat and versioned-v1 portable layouts
- use the protected control-plane verifier during immutable tag recovery
- keep exact Authenticode and payload hash checks for all six executables
Verification:
- bash scripts/release-workflows.test.sh
- go -C desktop test . -run TestWindowsReleaseSignsPayloadBeforeRepackaging -count=1
- actionlint
- ./scripts/cache-guard.sh
- git diff --check
Product: Remove the redundant advanced context disclosure around the sole compaction setting.
Change: Render the threshold controls directly in Agent runtime, preserve the low-touch guidance in localized helper text, and cover the visible-without-expansion contract.
Verification: pnpm exec tsx src/__tests__/settings-refresh-snapshot.test.tsx; pnpm build; in-app browser visual and console checks.
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
Motivation:
- Let advanced users choose a threshold between the preset values without editing TOML.
Implementation:
- Add a 65% to 85% percentage draft with 0.1% steps and explicit save semantics.
- Keep preset buttons, validation, cache-risk guidance, and localized accessibility labels.
- Revert the draft to the persisted value when a guarded settings update fails.
Verification:
- pnpm --dir desktop/frontend build
- pnpm --dir desktop/frontend exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- ./scripts/cache-guard.sh
- git diff --check
Motivation:
- Let advanced users trade context headroom against prompt-cache reuse without editing TOML.
Implementation:
- Add guarded 70%, 80%, and 85% presets with effective token and scope guidance.
- Persist the validated global ratio through the existing failure-atomic settings rebuild path.
- Preserve older config and Wails compatibility with additive optional fields and frontend defaults.
Verification:
- go test ./...
- (cd desktop && go test ./...)
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend check:css
- pnpm --dir desktop/frontend exec tsx src/__tests__/settings-refresh-snapshot.test.tsx
- ./scripts/cache-guard.sh
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.
BoolVar(cont, "c", ...) registers "c" as a long flag name, so -c failed
with "unknown shorthand flag: 'c' in -c" — silently swallowed by the
return-2 error path — while --c accidentally worked. Route both flag
sets (runAgent and chatREPL) through a shared BoolP registration so
-c/--continue behave as documented, and add regression tests.
Fixes#7156, #7171
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.
Integration: Incorporate the Windows instruction-home isolation and CI environment coverage without overlapping the reasoning guard or usage telemetry paths.
Verification: Root suite, instruction tests at count 10, focused Agent race/provider/Remote suites, cache guard, formatting, and diff checks.
Integration: Incorporate the official release-channel migration and preview release notes while preserving the additive estimated-usage fields in the shared frontend types.
Verification: Root tests, frontend tests and production build, focused Agent race/provider/cache suites, Desktop telemetry/workbench tests, and isolated repetitions of the unrelated flaky Desktop file and git probes.
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: Runaway reasoning streams relied only on a client byte guard, reused the provider length reason, and exposed reconstructed usage as exact across public surfaces.
Root cause: The agent did not send a stable provider output budget, Usage had no estimate provenance, and the guard was named as a character limit even though strings.Builder.Len reports bytes.
Fix: Send a stable derived MaxTokens budget through every built-in protocol, use client_reasoning_limit for the local guard, rename the option to ReasoningByteLimit, and propagate Estimated through CLI, Desktop, ACP, event wire, doctor, and Remote Workbench contracts.
Verification: go test ./... in the root and Desktop modules; focused race tests; provider serialization tests; cache guard; Remote protocol generation tests; frontend tests and production build.
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.
Integration: preserve the latest persisted missing-reasoning warning state while retaining the PR reasoning stream guard.
Verification: focused runaway, interrupted-usage, and missing-reasoning warning tests passed.
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.
The legacy main-v2 threshold of 24h is correct for DeepSeek: its Context
Caching on Disk retains prefixes for 'several hours to days'. Shrinking
it would prune still-warm caches and burn the user's live cache (~4x
miss cost measured).
Only override vendors with a documented, much shorter TTL:
- DashScope Session cache: 5 minutes (GPU cache)
- Anthropic ephemeral cache: 5 minutes
- DeepSeek + unknown: keep 24h legacy default
This keeps the PR minimal and approval-friendly: no behavior change for
DeepSeek users; the fix targets only the DashScope 5-minute GPU cache
that the legacy 24h threshold previously missed.
DashScope occasionally emits a response.completed event whose usage
object exists but is all zeros (server-side reporting gap; tokens are
still billed). Emitting it as ChunkUsage corrupted cache-ratio and cost
accounting with a spurious zero-token turn.
Gate emission on TotalTokens > 0 or a non-stop abnormal finish reason,
so length/content_filter truncations still surface while zero records
are dropped. Add TestAllZeroUsageCompletedIsSuppressed.
Replace the hardcoded 24h cacheColdAfter constant with a vendor-aware
dynamic resolution: DashScope 5m, DeepSeek 60m, Anthropic 5m, unknown
10m. Users can override per-provider with cache_ttl_minutes in config.
DashScope Session cache TTL is documented at 5 minutes; the previous 24h
assumption meant cold-resume prune never triggered for DashScope users,
leaving stale tool results in the transcript after cache expiry.
Changes:
- Add CacheTTLMinutes field to ProviderEntry (config.toml)
- Add internal/config/cache_policy.go with DefaultCacheTTL() and
EffectiveCacheTTL() vendor detection
- Replace var cacheColdAfter with Controller.cacheColdAfter() method
- Adapt resume_prune_test.go to use testCacheColdAfter override
- Add cache_policy_test.go (4 tests)
Verified: go test ./internal/config/ ./internal/control/ ./internal/provider/...
go vet clean.
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
DashScope's Responses API (token-plan endpoint) rejects reasoning items
without a summary list: "Invalid 'summary': summary is required and must
be a list for reasoning." OpenAI's format only needs content, so this is
a vendor divergence. Include both summary (summary_text) and content
(reasoning_text) so multi-turn conversations with assistant reasoning
history work on both DashScope and OpenAI-compatible endpoints.
A completed response finished normally; without this the FinishReason
stayed empty and consumers (e.g. tools/baseline) reported 'unknown'.
response.incomplete keeps its length/content_filter/incomplete mapping.
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
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]>
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:
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]>
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]>
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.
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.
Addresses the two blocking issues from PR #6998 review:
1. FileStore.SaveTask ran its version CAS without a file lock (TOCTOU):
two processes could both pass the check and clobber each other.
SaveTask now holds the per-task task.lock (shared with
AppendAuditEvent) across read-CAS-write-rename, and a corrupt
snapshot fails loudly instead of bypassing the CAS gate. Desktop
task control actions reuse one process-wide ControlService.
2. Nothing wrote task lifecycle data into the task store, so
.reasonix/tasks stayed empty. jobs.Manager exposes a TaskRecorder
hook (WithTaskRecorder / SetTaskRecorder) invoked on Start and
terminal completion; controller.New installs a taskmonitor.TaskRecorder
backed by FileStore so CLI, Desktop, and scripts observe the same
task state/events. SessionID is now optional (jobs parentSession is
commonly empty before the first turn).
Tests: concurrent cross-instance CAS, recorder lifecycle/restart/
truncation, jobs hook coverage; go test -race ./internal/taskmonitor
./internal/jobs, go vet, desktop go test all pass.
fmt.Fprintf(&b,"\n<sub>Real provider run. Cache-hit %% is cached prompt tokens / total prompt tokens.</sub>\n")
fmt.Fprintf(&b,"\n<sub>Real provider run. Cache-hit %% is cached prompt tokens / total prompt tokens. Wall time is measured by the harness and includes process startup.</sub>\n")
fmt.Fprintf(os.Stderr,"error: repair plan preview changed since confirmation; re-preview and re-confirm (expected %s, got %s)\n",expectedPreviewID,actualPreviewID)
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 may mix two releases; refusing to start. Re-run reasonix-guard to retry recovery, or reinstall Reasonix.")
return1
}
forceSafeMode=true
}
}elseifresult.RolledBack{
fmt.Fprintf(os.Stderr,"Reasonix Guard restored %s before launch after an unfinished %s update.\n",result.FromVersion,result.ToVersion)
}elseifresult.Cleared{
fmt.Fprintf(os.Stderr,"Reasonix Guard cleared an abandoned %s update before launch.\n",result.ToVersion)
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()
prompt:"baoguanPutArchive needs a user-owned decision: fully align archive logic, or only repair the current compiler error?",
options:[
{label:"Full alignment",description:"Reuse the archive flow and keep behavior consistent."},
{
label:"Full alignment",
description:"Reuse the archive flow and keep behavior consistent across every constructor, dynamically computed path, runtime boundary, and release validation step.",
},
{label:"Minimal repair",description:"Touch only the failing path and keep the patch smaller."},
ok(customGroupIndex>=0&&builtinGroupIndex>customGroupIndex,"custom subagents should render before built-in subagents");
ok((subagentsSource.match(/className="subagents-profile-group"/g)??[]).length===2,"custom and built-in subagents should use separate sections");
ok(subagentsSource.includes('className="btn btn--small subagents-reset-override"'),"override status and reset should share one compact action");
ok(subagentsStyles.includes("repeat(2, minmax(200px, 1fr)) 152px"),"built-in subagent pickers should use equal columns and reserve one stable status column");
ok(subagentsStyles.includes("repeat(2, minmax(0, 1fr)) 152px"),"built-in subagent pickers should use equal shrinkable columns and reserve one stable status column");
ok(subagentsSource.includes('className="settings-model-picker subagents-effort-picker"'),"effort and model overrides should share the same picker interaction pattern");
ok(subagentsSource.includes("<SubagentInvocation name={skill.name}"),"every subagent card should show its chat invocation affordance");
ok(subagentsSource.includes("onUseInChat(command)"),"subagent cards should send their slash command to the chat composer");
if(!dangerousClearDetail)thrownewError("dangerous clear-context detail did not render");
eq(dangerousClearDetail.hidden,false,"truncated destructive consequence opens automatically after selection");
eq(document.querySelector(".prompt-shelf__footnote .prompt-action__description-toggle"),null,"auto-open destructive consequence has no redundant toggle");
ok(container.textContent?.includes("Weekly report")===true,"card renders the title");
ok(container.querySelector(".extension-card__plugin")?.textContent==="alpha","card badges the plugin id");
ok(container.querySelectorAll(".extension-card__field").length===2,"card renders the key-value grid");
ok(container.querySelector(".extension-card__progress")?.getAttribute("aria-valuenow")==="40","card renders progress as an accessible progressbar");
ok(container.querySelector(".extension-card__body")?.querySelector("strong")!==null||container.textContent?.includes("bold")===true,"card renders markdown through the shared renderer");
Loaded 100 of 1044 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.