mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
4fbbca6a99dfd80cca293edd9762c6206b1d93a5
80
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4fbbca6a99 |
refactor(go): adopt modern stdlib idioms and gate them (#7831)
* refactor(go): adopt modern stdlib idioms across kernel and desktop Mechanical sweep with golangci-lint's modernize/intrange/copyloopvar/ usestdlibvars fixers plus hand-fixes for what they could not rewrite: slices.Contains, min/max, slices.Backward, WaitGroup.Go, reflect.TypeFor, maps.Copy, strings.Cut/CutPrefix/SplitSeq, strings.Builder, range-over-int. Net -549 lines with no behavior change. Three loops in session.go stay index-based: slices.Backward yields element copies, so the fixer's rewrite silently dropped writes to DecisionReceipts and ToolCalls (caught by TestDelivery* and TestDependentSameBatch*). All 49 slices.Backward sites were audited for that shape; only these three write through the index, and they carry a nolint with the reason. CaptureAfter's scan loop had an empty body and never ran any logic, so the fixer could not express it. Drop it and the accessor it was the only caller of. omitzero is deliberately untouched: turning omitempty into omitzero changes the JSON written for nested structs, which is a wire-format change to session files and the extension protocol. * build(lint): gate the modern-idiom linters Nothing stopped the codebase from drifting back to pre-1.22 idioms. Both modules are now clean for modernize, intrange, copyloopvar, and usestdlibvars, so enable them rather than leaving the sweep as a one-off. omitzero stays disabled with the reason inline; usetesting, perfsprint, and errorlint still have residue and land separately. |
||
|
|
93243ab14b |
feat(agent): store-P2 write-authority probe for unleased session saves (#7532)
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. |
||
|
|
0f5a900c45 |
fix(session): drop leading @file refs from the session preview (#7464)
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. |
||
|
|
41cfc15fe2 |
Harden CLI recovery trash publication
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 ./... |
||
|
|
0d41d616b3 |
Fix CLI recovery session hygiene
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 |
||
|
|
1e4e8b5d94 |
Merge main-v2 into PR 6971 hardening branch
Problem: - PR 6971 had fallen behind main-v2 and conflicted with the new Kimi K3 preset helper. Root cause: - Both branches added provider override helpers at the same insertion point. Fix: - Merge current main-v2 and retain both the Kimi K3 and Qwen per-model overrides without rewriting contributor history. Verification: - go test ./internal/agent ./internal/cli ./internal/config - ./scripts/cache-guard.sh |
||
|
|
952200ac1d |
fix(session): harden PR 6971 recovery contracts
Problem: - Same-revision snapshot recovery could overwrite an interrupted foreign write. - Paste recovery mutated loaded history, reused labels after resume, and trusted assistant text. - Mixed Qwen catalogs inherited a provider-wide 1M context budget. Root cause: - Revision equality was treated as ownership without checking the persisted digest. - Recovery lived in the persistence layer while paste IDs were process-local. - Qwen context metadata was configured only at provider scope. Fix: - Require persisted-state ownership for non-prefix snapshots and preserve foreign bytes. - Keep loaded history byte-stable, recover labels only on explicit resubmission, and continue IDs from history. - Add per-model context overrides for smaller Qwen catalog models. Verification: - go test ./internal/agent ./internal/cli ./internal/config - go test -race ./internal/agent -run snapshot recovery cases - ./scripts/cache-guard.sh - full repository suite passed except an opt-in live probe using an expired DEEPSEEK_API_KEY; the package passed with that variable unset Co-authored-by: SivanCola <[email protected]> |
||
|
|
32721fc0e1 |
feat(context): introduce Context Engine v2 / 引入上下文引擎 v2 (#7001)
* feat(memory): separate standing instructions from scoped facts * fix(memory): preserve scoped memory compatibility Problem: Omitted-scope updates could move legacy global memories into the current project, and global preferences or feedback were unavailable on the first turn. Mixed-version clients could also route independently scoped facts through the wrong directory. Root cause: Save normalized an empty scope before consulting the active file, the stable prompt only contained the memory index, and previous releases treated metadata.type as the routing signal. Fix: Preserve the effective scope of same-name updates, snapshot global user and feedback bodies into deterministic lower-priority guidance, retain a previous-release routing type alongside authoritative fact_type metadata, and queue complete save/revocation notes for the current session. Verification: ./scripts/cache-guard.sh go vet ./... env -u DEEPSEEK_API_KEY go test ./... (cd desktop) go test ./... (cd desktop/frontend) pnpm test (cd desktop/frontend) pnpm build gofmt -l . git diff --check * refactor(context): separate raw and provider user input * refactor(instructions): resolve scoped context with provenance * refactor(memory): add versioned fact storage Context: Memory facts were addressed by mutable slugs, trusted a separately maintained index, and had no durable update history or conflict protection. Change: Add stable IDs, monotonic revisions, atomic writes, derived indexes, deterministic legacy migration, scoped identity handling, revision restore, and optimistic update checks. Surface the metadata through tools and Desktop while preserving legacy frontmatter routing and Safe Mode behavior. Compatibility: Existing files remain readable, gain deterministic runtime metadata before migration, and keep metadata.type/fact_type/scope values that previous releases understand. Added Desktop JSON fields are optional. Verification: go test ./internal/memory; go test -race ./internal/memory; go test ./internal/boot -run Test.*Memory; go test ./... with provider credentials isolated; desktop go test ./...; frontend pnpm test and typecheck; go vet ./...; ./scripts/cache-guard.sh. * feat(memory): recall relevant facts per user turn Problem: Saved facts were only visible through a stable index and required the model or user to invoke memory search manually. Design: Add conservative host-side BM25 recall for real user turns with project-over-global deduplication, freshness labels, explainable scoring, strict character limits, and local-home redaction. Keep the system prompt and tool schemas stable by appending recall only to the provider user-turn tail, and exclude synthetic turns and already-loaded global guidance. Compatibility: Preserve Store.List behavior and add ListAll for scope-aware consumers. No persisted memory or session fields change; display fallbacks strip the new transient suffix. Verification: go test -count=1 ./internal/memory ./internal/agent ./internal/control go test -race -count=1 ./internal/memory go test -race -count=1 ./internal/control with focused recall tests go test -count=1 ./internal/tool/builtin/ ./internal/boot/ go vet ./... ./scripts/cache-guard.sh * feat(memory): auto-save low-risk project facts Problem: Every remember call required a fresh approval, including bounded project facts that were new, non-sensitive, and low impact. This made durable context expensive enough that agents and users often skipped it. Design: Classify interactive remember requests against the scoped store. Automatically allow only explicit project/reference creates that are bounded, non-sensitive, and not semantic duplicates. Keep global, user/feedback, sensitive, and overwrite requests on the existing fresh-human path; keep headless and sub-agent mutation fail closed. Safety: Issue one-shot hashed grants and enforce create-only again inside the serialized Store write, preventing an approval-to-write race from becoming an overwrite. Clear abandoned grants at turn completion. Expose revision history and audited restore through the controller. Compatibility: The remember description and schema are unchanged. RequireCreate is a runtime-only SaveOptions field; persisted memory and session formats are unchanged. Verification: DEEPSEEK_API_KEY= go test -count=1 ./... go test -count=1 ./... in desktop go test -race -count=1 ./internal/memory ./internal/control with memory/approval tests go vet ./... ./scripts/cache-guard.sh * feat(memory): recover archived facts safely Problem: Archived facts remained inspectable on disk but users could not recover them through Reasonix, and ad hoc restoration could overwrite active memory or lose revision provenance. Fix: Add store-validated archive recovery with collision checks, exclusive active-file creation, monotonic revisions, revision snapshots, controller turn-tail refresh, and a Desktop restore action. Compatibility: Existing active and archived Markdown files remain readable; recovery writes the established v2 frontmatter and hidden revision layout, while older binaries continue to read the restored active fact. Verification: go test ./internal/memory ./internal/control; go test ./... in desktop; pnpm test; pnpm exec tsc --noEmit; ./scripts/cache-guard.sh. * feat(context): expose memory recall diagnostics Expose scoped fact freshness, project-over-global conflict decisions, and the last automatic recall trace through the desktop contract. Add revision history and restore APIs while preserving non-null collection responses and remote-workspace isolation. This gives Context Center enough provenance to explain what was available, selected, suppressed, or restored without changing the stable prompt prefix. * feat(context): add provenance center to desktop Make scoped memories explainable in the desktop settings surface. Show stable identity, freshness, revision history, project-over-global resolution, automatic recall traces, instruction precedence, imports, and diagnostics. The suggestions workflow remains compatible while the Context Center gains zero-impact read paths and explicit restore actions for user-controlled recovery. * feat(memory): make suggestions zero-config Open the suggestions surface with automatic local-history scanning by default, removing the hidden localStorage setup switch. Keep every candidate behind explicit confirmation. Deduplicate against all scoped active facts and resolved instruction bodies so project and global memories cannot produce repeated guidance. * feat(memory): add CLI diagnostics and recovery Context: Context Engine v2 exposed revision, recall, and instruction provenance in the backend and desktop, but the CLI only listed legacy merged facts and archives. Changes: - add shared /memory diagnostics for scoped facts, recall traces, instruction precedence, revisions, and archives - add explicit revision restore and archive recovery commands with structured completion across CLI, desktop, and remote workspaces - preserve same-name facts across scopes and keep narrow TUI output bounded Verification: - go test ./internal/control ./internal/cli ./internal/remote/workbench/runtime ./internal/memory -count=1 - go test -race ./internal/control ./internal/memory - cd desktop && go test . -count=1 - ./scripts/cache-guard.sh Cache-impact: management diagnostics only; provider-visible prompt and tool schemas are unchanged. * docs(context): document instructions and memory v2 Context: Context Engine v2 changed instruction precedence, automatic recall, safe memory writes, revision recovery, and desktop suggestions, while the user docs still described the legacy approval-only retrieval model. Changes: - rewrite the context and memory contract around standing instructions versus background facts - document recall freshness, project-over-global resolution, zero-configuration writes and suggestions, revision/archive recovery, cache stability, privacy, and remote fail-closed behavior - add a complete Chinese companion and update Guide, CLI, migration, spec, approval, and README navigation in both languages Verification: - validated relative Markdown links, code fences, and final newlines across 14 changed files - scanned for stale approval/suggestion claims - git diff --cached --check - scanned the staged diff for secrets and machine-local paths * fix(context): preserve safe headless memory writes Allow controller-scoped low-risk project/reference creates to pass the non-interactive approval gate while keeping deny rules and all higher-risk memory operations fail-closed. Reject archive recovery through symlinked files or archive directories, with end-to-end regressions for both boundaries. * fix(memory): reject non-directory migration roots * fix(context): make instruction and memory references safe Problem: Provider-visible instruction paths exposed machine-local roots, memory results exposed store paths, and listed scoped references could not be used directly for read, update, revision, or archive operations. Root cause: Instruction rendering used absolute source paths and memory lookup treated display names as unqualified slugs. Symlink reads also crossed boundaries through a path-resolution gap, while scope-aware listing deduplicated same-name facts. Fix: Render stable workspace/user provenance, confine instruction and import reads to their owning roots, reject external symlinks, and support project/<name>.md and global/<name>.md references end to end. Preserve unqualified compatibility, stable-ID renames, revision snapshots, exact-scope archive behavior, low-risk create-only approval boundaries, and bilingual documentation. Verification: Focused instruction/memory tests and race tests; go test -count=1 ./...; go test -race ./...; go vet ./...; desktop go test/race/vet; pnpm test:all; pnpm build; go run ./cmd/remote-protocol-gen --check; ./scripts/cache-guard.sh (8/8 pass, long tool loop tail average 95%); staged privacy scan clean. * docs(contributors): attribute adopted context work Attribution: Record the authors whose prior pull request mechanisms were adapted by the Context Engine v2 integration. - cyq1017: feedback injection from #5710 and Markdown input normalization from #5603 - salarkhannn: Markdown input normalization from #5604 Scope: This metadata-only commit adds repository-level co-author attribution without changing runtime behavior. Verification: Confirmed source PR ownership and both public GitHub noreply identities. Co-authored-by: cyq <[email protected]> Co-authored-by: Salar <[email protected]> * test(desktop): allow slow Windows git metadata probe Problem: The Windows Desktop CI job failed when MetaForTab's asynchronous first git probe did not finish within the test's one-second deadline. Root cause: The production path intentionally launches the cold git branch lookup in the background so tab metadata never blocks. Windows runner process startup can exceed one second under normal CI load, making the test timing assumption platform-fragile. Fix: Keep the non-blocking metadata implementation unchanged and extend the cross-platform test observation window to five seconds, with a comment documenting the Windows process-startup variance. Verification: - GOCACHE=/tmp/reasonix-desktop-go-cache go test -count=1 -run 'TestMetaForTabIncludesWorkspaceContext|TestWorkspaceGitBranch' ./desktop - git diff --check --------- Co-authored-by: cyq <[email protected]> Co-authored-by: Salar <[email protected]> |
||
|
|
7ee229e0d8 |
fix(agent): repair orphaned paste labels on session load
When a session is resumed or its history replayed, orphaned [Pasted text #N] labels that were never expanded (because pastedBlocks was lost during a prior reload) are stored raw in the transcript. The model receives an opaque token it cannot interpret. repairOrphanedPasteLabels runs during loadSessionUnlocked: 1. Scans the same transcript for prior expansions (--- Begin/End ---) 2. Inlines recovered content into orphaned labels 3. Replaces unrecoverable labels with [pasted content unavailable] This cleans historical pollution at load time, complementing the TUI-layer fix (chat_tui_paste.go) that prevents new orphaned labels on submit. Ref: #6948 |
||
|
|
9f801723d9 |
fix(agent): skip byte-level prefix comparison when revision CAS proves same-runtime ownership
When baseState.revision == currentRevision the revision ledger proves no external writer has touched the transcript. In that case the byte-level messagesHavePrefix comparison in checkSnapshotWrite is redundant — the revision CAS already vouches for consistency — and its false positives (normalisation / JSON round-trip / local-only metadata) drive spurious diverged conflicts that cascade into recovery-branch chains and force-saves that break the prefix-cache. Future-tense (Delta): anchor on the revision CAS match, not the byte-level disk snapshot (Gamma). Same insight as Agda PR #8611 (makeTau's nTarget = nOld + nctel - 1). Ref: #6027 Phase 3, #6948 |
||
|
|
a6e1459621 |
Fix Desktop session runtime ownership / 修复桌面会话运行时所有权 (#6897)
* fix(desktop): canonicalize runtime process identity Problem: executable-based single-instance IDs let stable, canary, and portable builds concurrently write the same Reasonix home, while path aliases could split session lease identity. Root cause: process ownership was keyed by binary location and fresh paths resolved only their immediate parent. Fix: hash the canonical REASONIX_HOME and resolve every missing path component through the nearest existing symlink or junction ancestor, preserving isolated homes and dev-mode parallelism. Verification: Go agent tests, Desktop tests, Windows cross-compilation, and canonical-path race tests pass. * fix(desktop): unify writable session runtime ownership Problem: Goal and Delivery mode rebuilds, duplicate tab restore, background reattachment, and saved-session navigation could build a second controller for one session or tear down the live source before a target was proven usable. Root cause: controller startup, lease ownership, deferred retries, detached-session attachment, and path switching were coordinated per WorkspaceTab without a canonical process-wide admission record or one atomic controller/lease/path commit. Fix: add a SessionRuntime registry keyed by runtime ID and canonical session key; attach or wait on existing local runtimes; safely reclaim only orphaned current-process leases; and make saved-session switches build, restore, validate, and lease a candidate before atomically publishing it. Target failures preserve the source controller, lease, path, mode profile, and epoch. Verification: full Desktop and repository tests, Desktop/Agent/Controller/lease race suites, all 18 collaboration-token-mode orderings, deterministic A/B/A navigation fencing, frontend tests/build, Wails bindings, and a production Desktop build pass. Co-authored-by: erphenheimer <[email protected]> * fix(frontend): fence sends and events by runtime state Problem: the composer could append an optimistic user bubble while a workspace was starting or lease-blocked, and late events from a replaced controller could mutate the new transcript. Root cause: frontend readiness used only legacy booleans and controller events had no process-runtime generation fence. Fix: expose structured runtime phase and issue metadata, gate every submit path before optimistic dispatch, seed and advance per-tab runtime epochs, and ignore events from obsolete epochs while retaining legacy event compatibility. Verification: the full frontend suite, focused send-failure tests, TypeScript checking, and the production Vite build pass. * docs: record desktop session ownership fix Problem: the user-visible impact and compatibility boundary of the Desktop self-lock fix were not documented. Fix: describe single-runtime session ownership, epoch-fenced events, readiness-gated sends, and REASONIX_HOME-scoped single-instance behavior in the Unreleased changelog. Verification: Markdown diff inspection and repository privacy scan pass. * test(desktop): align CI fixtures with runtime ownership Problem: CI found an unused private runtime helper and a Linux timing path where a Heartbeat test injected Ctrl plus Ready without publishing the matching SessionRuntime state. Fix: remove the unused helper and advance the injected test runtime exactly as the production controller publication path does; production readiness checks remain strict. Verification: the Heartbeat submission and pending-prompt tests pass for 20 repetitions and for 5 race-enabled repetitions, and go vet passes. * fix(desktop): wait for starting session runtimes Problem: two persisted tabs restoring the same session could attach the first tab's starting placeholder before its controller was published, retire both candidate builds, and leave the visible session permanently starting. Root cause: startup called the general attach path before claimSessionRuntime classified the registered phase, and applyRuntimeTab could create a second registry record when adopting a legacy runtime into a target that already owned a placeholder. Fix: make claimSessionRuntime the startup admission entry, refuse attachments from non-ready runtime owners, reuse the target placeholder when adopting compatible visible or detached runtimes, and cover the starting-to-ready duplicate-tab handoff. Verification: focused runtime tests passed for 10 repetitions; the runtime/mode/navigation race matrix passed for 3 repetitions; Desktop go test ./..., go test -race ., go vet ./..., go build ./..., and git diff --check passed. --------- Co-authored-by: erphenheimer <[email protected]> |
||
|
|
5480019c4f |
feat(agent): evolve Auto mode with Auto Guard / 以 Auto Guard 强化自动模式 (#6732)
* feat(agent): add Auto-mode failure recovery checkpoint / 为自动模式增加失败恢复检查点 Problem: In Auto mode, after a tool or verification failure the agent could silently change strategy, expand write scope, or raise risk without a user decision. Root cause: There was only the Ask/Auto/YOLO permission axis and plan confirmation. No host-side checkpoint blocked unconfirmed recovery mutations after failures. Fix: - Add a shared per-task recovery state machine (idle/diagnosing/awaiting/ approved_once) consulted before permission and write-lock acquisition. - Deterministic rules auto-continue same-strategy verification retries and force confirm for high-risk, scope, strategy, or consecutive failures. - Independent recovery reviewer with isolated policy/session; fail closed. - Fresh recovery approval cards (continue/revise/stop); never session-drain. - Config/session defaults: new sessions on, pre-upgrade sessions off. - Desktop composer toggle + settings default; Bot structured recovery UX; content-free recovery metrics; reviewer/gate sidecars for cold restart. - Docs: English + Chinese tool-approval modes. Verification: - go test ./internal/recovery/ ./internal/control/ ./internal/bot/ -run 'TestRecovery|TestCache|Approval' -count=1 - Cache-shape test: success-path system/tools hashes unchanged with gate on - desktop package compiles (go test -c) Cache-impact: low — success path keeps stable system prompt and tool schemas byte-stable; only failure-path dynamic user-tail and an isolated reviewer session add tokens. Cache-guard: internal/recovery TestRecoveryWiringPreservesSuccessPathCacheShape System-prompt-review: N/A for main agent (reviewer uses a separate fixed policy) * fix(agent): harden auto recovery checkpoints Problem: Auto recovery could start a concurrent Agent.Run, wait forever in headless mode, duplicate revise feedback, and persist incomplete or orphaned recovery state. Root cause: Failure guidance was routed through Controller.Steer, headless detection relied on approval timeout, reviewer transcripts were treated as session sidecars, and recovery state lacked ordered atomic persistence and task-scoped replay semantics. Fix: Deliver guidance to the exact Agent.Run, use explicit headless mode, isolate root and subagent recovery identities, make recovery state atomic and session-owned, support cold-start decisions, and complete the Wails/mock bridge contract. Verification: go test ./...; go test -race ./...; root and desktop vet/lint; desktop tests; frontend typecheck/tests/build; real-browser Continue/Revise/Stop checks. * style: format recovery checkpoint fields Problem: CI stopped before compilation because four recovery-related Go files from the original PR were not gofmt-clean. Root cause: Aligned struct fields were added without running the repository-wide formatter gate. Fix: Apply gofmt to the affected root and desktop files without changing behavior. Verification: The exact root and desktop gofmt checks from CI now produce no output. * test(recovery): respect Windows file modes Problem: The Windows CI leg failed even though the recovery snapshot was written atomically and remained valid JSON. Root cause: The test expected POSIX 0600 permission bits from FileMode.Perm on Windows, where NTFS permissions are represented by inherited ACLs and Go reports synthetic 0666 bits. Fix: Keep the atomic persistence assertions on every platform and limit the numeric 0600 assertion to platforms where POSIX mode bits are meaningful. Verification: Focused recovery tests pass, the Windows test package cross-compiles, and the exact root gofmt gate is clean. * fix: harden recovery checkpoint session isolation Problem: Recovery state could cross session boundaries, delayed snapshots could target a new session, destructive delete tools could bypass human confirmation, synchronous approval replies could be lost, and legacy sessions could resume enabled. Root cause: The gate resolved persistence paths asynchronously, session rotations did not consistently replace gate state, approval IDs were bound after event emission, delete tools were absent from deterministic risk rules, and resume ignored branch metadata. Fix: Capture and drain persistence per session, reset or carry recovery state explicitly across every rotation, bind approval IDs before emission, classify delete tools as high risk, and restore the persisted per-session preference with a legacy-off default. Verification: go test ./... (cd desktop && go test ./...) go test -race ./internal/recovery ./internal/control -run 'Recovery|FreshSessionRotations|SetSessionPath' go vet ./... ./scripts/cache-guard.sh * fix(recovery): preserve defaults across session resets Problem: Recovery settings were omitted from rendered configuration, and fresh sessions could inherit a resumed session preference instead of the configured default. Root cause: The serializers did not emit the new fields, while the controller kept one mutable recovery flag for both current-session state and future-session defaults. Desktop metadata snapshots also did not consistently mirror the effective preference. Fix: Render and round-trip the recovery settings, separate current and default controller state, route known-new paths through an explicit fresh-session binding, and synchronize Desktop tabs and branch metadata after rotation. Verification: go test ./... (cd desktop && go test .) go test -race ./internal/control -run recovery rotation cases (cd desktop && go test -race . -run recovery session cases) * feat(agent): evolve recovery checkpoint into Auto Guard Problem: Auto only applied the recovery checkpoint after failures, duplicated UI and config controls, and escalated every uncertain recovery directly to a human. Root cause: High-risk classification was scoped to active failure state, while persisted one-shot grants and recovery-specific Stop and replay branches made the flow stateful and fragmented. Fix: Add a deterministic pre-action Auto Guard, return reviewer denials to agents before three-strike human escalation, remove stale grant replay and recovery Stop, consolidate config and UI, and preserve legacy wire and config decoding. Verification: - go test ./... - go test -race ./internal/recovery - (cd desktop && go test ./...) - (cd desktop/frontend && pnpm test:typecheck && pnpm test && pnpm build) * test(recovery): synchronize prompt resolution Problem: Linux CI could schedule prompt-resolution goroutines before the gate re-keyed its provisional waiter, leaving two new tests blocked until the package timeout. Root cause: The tests resolved real approval IDs without first binding them, so they depended on scheduler timing even though production prompt emitters use BindApprovalID. Fix: Bind each approval ID synchronously inside the prompt callback before resolving the decision. Verification: - go test ./internal/recovery -run TestHighRiskMutationPromptsBeforeAnyFailure\|TestReviewerBlockReturnsReasonThenEscalates -count=100 - go test -race ./internal/recovery -run TestHighRiskMutationPromptsBeforeAnyFailure\|TestReviewerBlockReturnsReasonThenEscalates -count=100 * ci: restart stuck Windows runner Problem: The Windows test job stayed in progress well past its configured 10-minute step timeout and ignored both normal and force-cancel requests. Root cause: The hosted runner stopped reporting progress after entering the test step, preventing the workflow from reaching a rerunnable conclusion. Fix: Advance the PR head without file changes so the workflow concurrency policy replaces the stuck run with a fresh runner. Verification: The same head already passed Ubuntu, macOS, race, lint, coverage, CodeQL, cache-impact, and desktop Linux checks. * fix(desktop): keep Yolo visible in composer Problem: The Auto Guard refinement hid Yolo from the composer unless it was already active, which made a supported approval mode less discoverable and left the three-column control partially empty. Root cause: The composer rendered the Yolo button conditionally and narrowed the approval control for a two-option default surface. Fix: Always render Ask, Auto, and Yolo, restore the three-option width, and align English/Chinese documentation and regression coverage with the direct selection flow. Verification: - pnpm exec tsx src/__tests__/composer-run-strip.test.tsx - pnpm test:typecheck - pnpm check:css - pnpm test - pnpm build - Browser DOM/click verification for all three visible options * fix(recovery): close Auto Guard evidence gaps Problem: Auto Guard could miss dependency/config deletes, moves, and common install or publish commands, while the recovery reviewer never received the read-only evidence gathered after a failure. Root cause: High-risk classification covered only a subset of file mutation tools and broad command names, and successful diagnostic observations were discarded before populating the task-local diagnosis state. Fix: Classify every built-in file mutation path and risky package-manager subcommand, keep ordinary verification commands on the fast path, and persist bounded, deduplicated, task-local diagnostic excerpts for the isolated reviewer. Mark dynamic reviewer evidence as untrusted. Verification: - go test -race ./internal/recovery -count=1 - env -u DEEPSEEK_API_KEY go test ./... -count=1 - go vet ./... - cd desktop && go test ./... -count=1 - go test ./internal/recovery -run TestRecoveryWiringPreservesSuccessPathCacheShape -count=1 - ./scripts/cache-guard.sh * fix(recovery): remove obsolete path helper Problem: The PR lint job rejected an unused pathFromArgs helper left behind after high-risk classification moved to multi-path extraction. Root cause: The compatibility wrapper had no remaining callers, while go vet does not report unused package-level functions. Fix: Remove the obsolete wrapper and retain pathsFromArgs as the single path extraction implementation. Verification: - go test ./internal/recovery -count=1 - go test -race ./internal/recovery -count=1 - go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m * test(recovery): freeze Auto Guard behavior matrix Problem: Auto Guard routing is embedded in the Gate coordinator, so later refactors risk changing product semantics without a focused oracle. Fix: Add a pure Decision engine skeleton and table-driven golden coverage for the product matrix: ordinary Auto paths never call the reviewer, high risk and scope/strategy/second-failure ask immediately, ambiguous recovery reviews once, reject budget escalates on the third block, and reviewer errors fail closed without burning reject credits. Verification: go test ./internal/recovery/ * refactor(recovery): pure decision core, collapsed state, provider reviewer Problem: Auto Guard mixed classification, routing, agent-based review, and multi-source runtime state in one Gate, making later product simplification risky. Root cause: Phase/ConsecutiveFails/SafeRetryLeft/Pending lived as parallel truth, and the reviewer spun a full agent.Agent with tools and compaction. Fix: - Route through a pure Decide engine (Classifier facts → allow/review/ask). - Keep one taskRuntime + activeFailure; derive Phase and wire compatibility fields on Snapshot; Restore still drops Pending/ApprovalID grants. - Replace the agent reviewer with a bounded provider.Stream call (temp 0, max 256 tokens, 8 KiB total, 4 KiB output cap, recovery-reviewer usage source). Verification: go test ./internal/recovery/ ./internal/control/ -run 'Recovery|AutoGuard|recovery' * refactor(desktop): one-click Auto recovery and remove Guard toggles Problem: Recovery cards still used select-then-confirm, and unreleased per-tab / Settings Auto Guard toggles competed with Ask/Auto/YOLO as a fourth control. Fix: - Recovery card is one click: Continue this step / Try another approach, optional guidance, details collapsed, localized reason by change_kind. - Empty revise feedback is accepted; host injects a fixed agent guidance. - Remove desktop Settings and Wails per-tab Auto Guard toggles; kill switch remains [agent].auto_recovery_checkpoint only. - Stop persisting RecoveryCheckpointEnabled on BranchMeta. - Document guarded Auto semantics in EN/ZH mode docs. Verification: go test ./internal/recovery ./internal/control -run Recovery desktop go test focused recovery/session cases pnpm exec tsx src/__tests__/decision-surface.test.tsx * fix(desktop): keep legacy Auto Guard Wails no-ops Problem: Removing the Wails methods broke frontend typecheck against generated bindings that still list the old surfaces. Fix: Restore thin no-op / config-read Wails methods so older frontends and generated bindings keep compiling, without reviving per-tab or Settings Auto Guard toggles as product controls. Verification: pnpm typecheck go test . -run AutoRecovery|RecoveryCheckpoint|NewSessionUsesFresh (desktop) * fix(recovery): unblock legacy Approve, honor project kill switch, keep evidence JSON valid Problem: 1. Pre-action high-risk recovery cards parked a live waiter without taskRuntime, so Approve looked them up via Snapshot and never called ResolveRecovery — old clients could hang until context cancel. 2. Desktop re-applied Auto Guard from user-only config after boot, ignoring project [agent].auto_recovery_checkpoint = "off". 3. Reviewer evidence over 6 KiB was clipped mid-JSON, producing invalid payload. Root cause: Live waiter state was inferred from a persistence-shaped snapshot; desktop overwrote the controller kill switch; field budgeting happened after marshal. Fix: - Gate.HasApproval(id) inspects the live waiter table; Approve routes through it. - Remove desktop secondary SetRecoveryCheckpointEnabled / DefaultEnabled overrides. - Budget optional evidence fields, then marshal; assert json.Valid and size. - UTF-8-safe feedback clip at the 4 KiB host cap. Verification: go test -race ./internal/recovery ./internal/control go test . -run AutoRecovery|NewSessionUsesFresh (desktop) * fix(ci): clear Auto Guard lint failures and freeze project kill switch Problem: CI golangci-lint v2.12.2 failed on three issues introduced by the Auto Guard refactor: an empty if (SA9003), a redundant explicit type (ST1023), and an unused desktop helper (U1000). Fix: - Assert the real recovery approval id is cleared after legacy Approve. - Use r := reviewer in the decision matrix helper. - Remove unused currentTabRecoveryCheckpointEnabled. - Add boot regression: global on + project off disables Auto Guard at Build and after SetFreshSessionPath. Verification: go test ./internal/recovery ./internal/control ./internal/boot -run LegacyApprove|BehaviorMatrix|BuildHonorsProject|HasApproval staticcheck -checks SA9003,ST1023,U1000 on touched packages * test(desktop): freeze project Auto Guard kill switch Problem: The existing regression only exercised boot.Build and did not pass through the desktop runtime wiring that previously reapplied the user-global Auto Guard setting. Root cause: The test used a legacy-compatible config path and treated a same-controller session rotation as proof of desktop rebuild behavior, so the secondary desktop overrides were outside its coverage. Fix: Use the primary user config path and add a desktop integration regression that builds a project tab with global on/project off, then verifies initial configuration and a fresh-session rotation both remain disabled. Verification: go test ./... go test -race ./internal/recovery ./internal/control ./internal/boot (cd desktop && go test ./...) golangci-lint v2.12.2 in root and desktop ./scripts/cache-guard.sh * refactor(recovery): make Auto Guard intrinsic to Auto Problem: The short-lived auto_recovery_checkpoint kill switch duplicated Auto mode state across config, recovery decisions, controller sessions, and desktop tabs, increasing user and maintenance cost. Root cause: Auto Guard was introduced as an optional feature even though its decision matrix is part of Auto mode's product contract. Fix: Remove the config key and the full runtime enablement chain. Ignore retired TOML and session metadata fields, clean them on the next save, and retain only no-op Wails compatibility methods for older generated frontends. Update docs and regressions, and format files that blocked the prior CI run. Verification: - go test -race ./internal/recovery ./internal/control - go test ./internal/agent ./internal/config ./internal/boot - cd desktop && go test ./... - cd desktop/frontend && pnpm typecheck && pnpm test:typecheck - decision-surface and composer-run-strip tests - ./scripts/cache-guard.sh * test(boot): isolate Windows config directories Problem: The Windows root-module CI repeatedly loaded a test provider from an earlier case, retried example.invalid for 92 seconds, and then timed out. Root cause: isolateConfigHome redirected HOME and XDG_CONFIG_HOME but not the AppData and LocalAppData directories used by os.UserConfigDir and os.UserCacheDir on Windows. Fix: Redirect USERPROFILE, AppData, and LocalAppData into the same per-test temporary directory. Verification: - go test ./internal/boot - repeated the affected Boot tests three times - confirmed the Go standard library reads AppData and LocalAppData on Windows * fix(desktop): prevent approval control overlap Problem: The three-option approval bar overlapped the adjacent model selector at compact composer widths. Root cause: The 760px container rule shrank the approval wrapper to 164-178px while its child mode bar retained a fixed 200-232px width. Fix: Allow the approval mode bar to shrink with its compact parent and add a CSS contract regression for the responsive geometry. Verification: - pnpm test - pnpm build - pnpm typecheck - pnpm test:typecheck - pnpm check:css - ./scripts/cache-guard.sh - in-app browser geometry at 1280px and 1182px viewport widths * fix(recovery): close Auto Guard review gaps Problem: Auto Guard missed wrapped dependency and configuration mutations, while desktop telemetry replayed cumulative recovery counters on every completed turn. Root cause: The high-risk classifier recognized only narrow command and path shapes, and the desktop sampled controller totals rather than per-turn deltas. Fix: Harden wrapper and configuration mutation classification, add atomic recovery metric draining, and cover both regressions with focused tests. Verification: - go test ./... - go test -race ./internal/recovery ./internal/control - cd desktop && go test ./... - go vet ./... - ./scripts/cache-guard.sh * refactor(recovery): make Auto interruption-light Problem: Auto asked users to understand recovery internals and interrupted reversible workspace work, while Plan required select-then-confirm. Root cause: The recovery classifier treated config and dependency edits, strategy changes, and the second failure as human boundaries, and the desktop surface exposed internal guard concepts. Fix: - reserve human prompts for deterministic hard boundaries or three failed attempts - keep low-risk recovery moving when optional review is unavailable - simplify Plan and recovery decisions to two direct, accessible buttons - align desktop, bot, docs, and localized copy Verification: - go test ./... - pnpm --dir desktop/frontend test:all - pnpm --dir desktop/frontend build - browser verification for Auto, Plan + Auto, Goal + Auto, Plan confirmation, and recovery confirmation * fix(recovery): align Auto boundaries and reviewer Problem: Auto could silently allow destructive or external commands, while the isolated reviewer still rejected bounded project-local dependency, config, strategy, and scope changes after failures. Root cause: The deterministic shell classifier omitted destructive Git variants, global package-manager forms, and external mutation CLIs. The fixed reviewer contract only allowed same_strategy verdicts after Auto policy moved bounded workspace recovery to the automatic path. Fix: - Classify destructive Git state changes, global package operations, HTTP/GitHub API mutations, and remote command families as hard boundaries while preserving proven read-only fast paths. - Allow reviewer continue verdicts for bounded, task-aligned strategy and scope changes while keeping risk and uncertainty fail closed. - Align the three-failure rationale and add policy and classifier regression coverage. Verification: - go test ./... -count=1 - go test -race ./internal/recovery ./internal/control ./internal/agent -count=1 - go vet ./... - golangci-lint v2.12.2 ./internal/recovery/... (0 issues) - ./scripts/cache-guard.sh * fix(auto): scope reusable recovery approvals safely Problem: Auto Guard repeatedly interrupted bounded external operations, while several unlisted shell, PowerShell, Git checkout, and attached GitHub CLI flag forms could bypass the intended hard boundary. Root cause: Recovery confirmations were one-shot only and the shell classifier relied on an incomplete deny list without a host-owned semantic authorization scope. Fix: Add task-scoped semantic grants for explicitly bounded operations, expose opt-in actions across Desktop, CLI, and Bot, fail closed for unknown mutations, and cover destructive sibling forms and lifecycle boundaries. Verification: go test ./... go test -race ./internal/recovery ./internal/control ./internal/agent pnpm test:typecheck && pnpm test pnpm run build ./scripts/cache-guard.sh * refactor(auto): simplify recovery confirmation UI Problem: - Auto Guard confirmations exposed recovery diagnostics at equal visual priority, making simple decisions noisy. Root cause: - Raw recovery fields reused warning cards and appeared before the user's actual choices. Fix: - Show one concise action summary, recommend the safer alternative first, move optional task grants after the decisions, and flatten technical diagnostics behind disclosure. Verification: - pnpm test - pnpm typecheck - pnpm check:css - In-app browser checks for default, expanded, and 620px layouts with no console errors. * feat(auto): add optional recovery guidance Problem: Auto Guard offered only a generic retry or one-shot continue, so users could not steer a safer alternative without adding another heavyweight decision. Root cause: the simplified recovery surface removed free-text feedback entirely instead of keeping it behind progressive disclosure. Fix: add a quiet guidance entry that expands an inline editor, routes text only through revise, clears unrelated task grants, restores keyboard focus, and localizes the flow. Verification: pnpm test; pnpm typecheck; pnpm check:css; interactive in-app browser review of collapsed and expanded states. * fix(auto): bind task grants to exact targets Problem: Task-scoped recovery grants for git pushes matched every ref on the same remote, stale grants remained attached to reused task IDs, and Desktop described a checked task grant as a one-shot continue. Root cause: The semantic grant key omitted the destination ref, runtime state did not retire grants when the host task scope changed, and recovery approval events exposed only a boolean instead of the host-classified scope. Fix: Scope reusable pushes to an explicit remote and destination, reject ambiguous or behavior-changing variants, retire grants on task-scope changes, expose a transient display scope to every frontend, and group explicit consent with the Continue action. Verification: go test ./... go vet ./... go test -race ./internal/recovery ./internal/eventwire ./internal/control ./internal/agent -count=1 pnpm test:all && pnpm build (cd desktop && go test ./... -count=1) ./scripts/cache-guard.sh * test(auto): cover legacy task-grant events Problem: Older backends do not send the optional task_grant_scope field. Root cause: The new exact-scope rendering tests only exercised events produced by the updated backend. Fix: Keep one grantable recovery fixture on the legacy wire shape and assert that Desktop renders the generic bounded-scope explanation. Verification: pnpm test:typecheck pnpm exec tsx src/__tests__/decision-surface.test.tsx * fix(auto): drain recovery writes before new session Problem: The macOS PR job intermittently failed while TempDir removed a session directory because /new could return with an old-session recovery snapshot still writing asynchronously. Root cause: ClearSession drained path-bound recovery persistence before deleting artifacts, but NewSession rotated the session path without the equivalent lifecycle barrier. Fix: Drain pending recovery writes for the old path before the final session snapshot and path rotation. Add a deterministic blocking persistence regression test that proves NewSession waits for the drain. Verification: - go test ./... -count=1 - go test -race ./internal/control -run '^(TestFreshSessionRotationsClearRecoveryState|TestNewSessionWaitsForPendingRecoveryPersistence)$' -count=10 - go test ./internal/control -run '^(TestFreshSessionRotationsClearRecoveryState|TestNewSessionWaitsForPendingRecoveryPersistence)$' -count=50 - go vet ./internal/control ./internal/recovery - (cd desktop && go test ./... -count=1) - ./scripts/cache-guard.sh |
||
|
|
1e1ba41d40 |
agent/save: make session filenames Windows-safe / 修复 Windows 会话文件名兼容性 (#6684)
* agent/save: fix Windows crash when model name contains ':' Fixes #6683 NewSessionPath() only sanitized '/' and '\' from model names, missing ':' and other Windows-illegal filename characters. On Windows, ':' is the NTFS alternate data stream (ADS) marker, causing: - System freeze / crash when file system scanners index the session file - CLI permanently stuck: lease.lock path with ':' triggers 'session is in use' error that can never clear - All sidecar files (.ckpt, .lock, .lease.*, .events.jsonl) corrupted Add all Windows-illegal chars to the replacer: :<>"|?* * test(agent): cover Windows-safe session filenames Problem: The Windows filename fix had no regression covering a real colon-bearing model label or persistence through the generated path. Fix: Add a focused test for the curated nemotron-3-nano:30b model shape, cover the newly sanitized punctuation, and save plus stat a session through the real persistence path. Compatibility: The test exercises future filename derivation only. Existing session files and safe model labels remain unchanged. Verification: - go test ./internal/agent -run TestNewSessionPath -count=1 - go test ./internal/agent ./internal/control ./internal/cli ./internal/boot - cd desktop && go test ./... - git diff --check Co-authored-by: SivanCola <[email protected]> --------- Co-authored-by: SivanCola <[email protected]> |
||
|
|
dd929bf2df |
Bound session save lock waits / 限制会话保存锁等待时间 (#6667)
Problem: A competing process holding the compatibility session save lock wedged desktop navigation and true-quit shutdown indefinitely, kept the session lease and WebView alive, and a timed-out final snapshot could discard transcript changes that existed only in memory (#6637, Bug 3). Root cause: flock/LockFileEx used blocking acquisition with no deadline; the shutdown path treated a held lock like any other snapshot failure; and during the two-phase recovery handoff a concurrent tab-layout save could overwrite the recovery anchor with the controller's stale path. Fix: Retry non-blocking lock acquisition for a bounded five-second window on Unix and Windows and keep the in-memory transcript on failure; on a true shutdown timeout persist the transcript to a process-specific recovery branch that never re-acquires the stalled original lock, reusing the standard session/event-log/branch-meta formats and recovery handoff; make the lease-backed tab path authoritative during the handoff via a lock-free lease-key mirror so layout saves never wait on lease file operations. Verification: go test -race ./internal/agent ./internal/control; cd desktop && go test -race ./...; go vet ./... (root and desktop modules); trial merge with origin/main-v2 clean; scripts/check-cache-impact.sh reports no cache-sensitive files changed; full CI green including race, desktop-windows, cache-impact, and CodeQL. |
||
|
|
dae65e250b |
Fix long-run freeze on stalled error bodies and session-switch lag/content loss / 修复重试卡死、切换会话卡顿与内容丢失 (#6613)
* fix(provider): bound non-OK response body reads in the retry loop A 502/524 gateway under load can send headers and then stall the body on a half-open connection. http.Client deliberately has no Timeout (streams), and ResponseHeaderTimeout no longer applies once headers arrive, so the retry loop's error-body io.ReadAll/io.Copy could block indefinitely with no retry notification and no user-visible progress — the turn looked frozen until the process was killed (#6607, #6346, #5760). readErrorBody arms a hard deadline that closes the body to unblock the read; context cancellation already aborts it via the transport. The regression test drives a stalled 502 body through SendWithRetry and fails via watchdog if the loop wedges (mutation-verified). * fix(agent): salvage event-log tail bytes before repair truncates them repairSessionEventLogTail's os.Truncate was the only place that permanently discards persisted transcript bytes. A torn tail is usually one partial record, but replay also stops at a buried undecodable or out-of-order record (the dual-writer interleave shape) — then every intact turn past it was silently lost, surfacing as 'reopened session lost messages' (#6607, #5315). The discarded range is now appended to a .events.jsonl.damaged salvage sidecar (one-line JSON incident header + raw bytes) before truncation. The sidecar name must not end in .jsonl: older binaries scanning a shared session directory would classify it as a primary transcript. It is registered in SessionSidecarFiles so every delete surface removes it. Preservation is best-effort — a full disk both tears tails and fails salvage — and never blocks the repair itself. * perf(agent): skip snapshot saves when the transcript is provably unchanged Desktop switch/close/prune paths snapshot defensively on every navigation. Each snapshot paid a full transcript serialize + digest + disk probe just to discover there was nothing to write — on large sessions that is the seconds of UI freeze reported as switch lag (#6607, #5337, #5185; single-surface activation pays it once per hidden tab, twice per pruned runtime). save() now short-circuits snapshot-mode saves when in-memory bookkeeping proves a no-op: same path, same version, same rewriteVersion, ledger revision known, no pending load-time repair or log damage. The check runs under the save locks, not before them, so a saver that waited on a concurrent writer still re-evaluates against the state it must persist (locked in by TestSaveSnapshotCapturesContentUnderSaveLock). Only save-verified baselines arm the fast path: a baseline adopted at load pairs the disk transcript with whatever the meta sidecar said, which lags the transcript after an interrupted save — the first save after a load must run in full and heal a stale ledger (TestSameContentSaveHealsStaleLedgerDigest). * fix(desktop): drop stale topic activations instead of applying them activateTopic applied its result unconditionally after awaiting the backend: when a newer navigation (another topic click via a different path, a tab switch, session restore) completed first, the stale completion flipped the visible tab away from the user's last click AND ran the single-surface prune that deletes every other tab's cached state — blanking the transcript the user was looking at (#6607, #5337 'switch shows an empty session'). The App-level navigation queue serializes queued topic clicks, but its latest() guard only covers App-level effects; the setActiveTabId + state prune inside activateTopic were unguarded. Anchor each activation to the existing activeNavigationSeqRef epoch (the same mechanism rewind/fork already use) and return without applying when a newer navigation superseded it. Mutation-verified: disabling the guard fails both stale-apply assertions. * fix(privacy): scrub and trash the damaged-salvage sidecar with its session Review of #6613 found two lifecycle gaps for the new .events.jsonl.damaged salvage sidecar, both privacy-relevant since it preserves raw transcript bytes: - doctor redact did not recognize the suffix: RedactSessions reported success while secrets in the salvaged tail survived. The bytes are undecodable by definition, so no format-aware masking can prove them clean; following the existing torn-tail precedent (compacted away regardless of content), the scrub now deletes the sidecar outright, honors dry-run, and skips leased sessions like every other artifact. - the desktop trash flow uses its own explicit artifact list, so deleting a session left the sidecar in the live directory and purging the trash never removed it. The sidecar now moves with the other transcript artifacts through delete, restore, and purge. * fix(agent): re-arm the snapshot fast path after a repair is persisted Review of #6613 found that normalizedDirty and eventLogDamaged were never cleared on the live session after a successful save persisted the repair, so a session that was repaired once kept paying the full serialize + digest on every defensive snapshot until restart — the switch-lag fix was incomplete for exactly the sessions that needed it most. Clear the flags (and the obsolete rawMessages capture) when a save-verified baseline lands: the write included the normalization repair and healed the on-disk log. Nothing reads the live session's copies after a save — checkSnapshotWrite re-loads the on-disk state and consults that object's flags, not these. * fix(desktop): invalidate in-flight activations when a navigation is queued Review of #6613 found the last-click-wins guard did not cover the production navigation queue: App.enqueueNavigation serializes clicks, so a click made while another request runs only becomes a pending queue entry — it does not run activateTopic and therefore never advances the controller's navigation epoch. The running stale activation then passed the guard, flipped the visible tab, and pruned the newer surface's cached state. useController now exposes noteNavigationIntent (the same epoch bump the guard checks) and App.enqueueNavigation calls it at enqueue time, so any queued click immediately invalidates the running activation's destructive apply. The regression test drives the REAL enqueueNavigationRequest queue, holds the first activation mid-flight, queues a second, and asserts the stale completion neither flips the tab nor deletes cached state; a source assertion locks the App.tsx wiring itself. Mutation-verified both ways. * fix(doctor): include the damaged-salvage sidecar in session bundles Proactive sweep after the #6613 review round: session bundles exist to debug persistence incidents, and the .events.jsonl.damaged sidecar is the primary evidence for truncation incidents (torn tails, dual-writer interleaves) — but sessionBundleArtifacts, another explicit artifact list, did not include it. Bundles copy artifact contents verbatim already (only paths/refs are redacted), so shipping the sidecar adds no new sensitivity class. * fix(agent): migrate the damaged-salvage sidecar on overlong-name renames Review follow-up on #6613: migrateSessionSidecars is yet another explicit sidecar list, and it did not include SessionEventLogDamaged. When reconcileOverlongSessionFilenames renames a transcript to a bounded stem, the salvage sidecar stayed under the retired name — an orphan file holding raw session bytes that no delete, trash, redact, or bundle flow could reach (they all derive paths from the live transcript name). Regression: TestReconcileOverlongMigratesDiagnosticSidecars now seeds the sidecar and asserts it migrates with the rename and leaves nothing under the retired stem (mutation-verified). |
||
|
|
0afcea408d |
Restore historical message timestamps and simplify history navigation / 恢复历史消息时间戳并简化历史入口 (#6586)
* fix(desktop): remove redundant history navigation * fix(desktop): restore historical message timestamps |
||
|
|
6826411dd1 |
Restore v0.53 secret handling semantics (#6583)
* Restore v0.53 secret handling * Harden verbatim artifact storage --------- Co-authored-by: open <[email protected]> |
||
|
|
fdef0f5b41 |
fix(delivery): harden review_report protocol, read-only gating, and streaming liveness
Delivery-mode hardening from a full-workflow field trace (16-min screen
recording + persisted event log of the same session):
- review_report protocol: append an explicit submission contract to review
subagent task prompts, nudge a finished-but-reportless review subagent on
the same session (evidence preserved so the report can cite earlier reads)
up to 2 times before failing, dump the failed transcript for diagnosis,
and correct the error text that told the parent to call a tool it does
not have.
- delivery intent classification: classify mutation/work expectations from
the user task text only — host framing (subagent/workspace context blocks,
delivery-runtime marker) contained incidental verbs ("resolve") that
classified every wrapped subagent prompt as a mutation request; read-only
registries can never satisfy a state-change expectation, so it is no
longer armed for them (this deadlocked read_only_task reviews).
- final readiness budget: extend nudges past the base cap (3, up to 6) only
while the model keeps earning new host-observable receipts, so a
converging turn (fix -> verify -> re-review) is not cut off mid-recovery
while a stalled turn still fails fast.
- title/preview hygiene: strip the trailing <delivery-runtime> marker in
StripTransientUserBlocks, share one synthetic-user filter
(agent.SyntheticUserPrefixes / IsUserAuthoredTurn) across session preview,
meta turn counts, and desktop topic auto-titles so host-injected readiness
nudges can no longer become titles ("你是谁? <delivery-run…") or inflate
user-turn counts.
- streaming liveness: providers emit ChunkToolCallArgsDelta progress (per
2KB) while tool-call arguments stream; the agent re-emits throttled
partial ToolDispatch events with ArgChars; the desktop upserts a live
"receiving arguments" tool card instead of dropping partial dispatches,
and the composer pill folds streamed arg chars into its token estimate —
a 30KB write_file body no longer looks like a hang for 80 seconds.
- right-panel freshness: a source-agnostic usageSeq keeps 会话指标/用量分析
refreshing during sub-agent runs; the context gauge no longer collapses
to 0/1M when a mid-turn snapshot races a rebuilt executor.
Verification: go test ./... (root and desktop modules), scripts/
cache-guard.sh (tail_avg 92/95/95 >= 90), desktop/frontend pnpm typecheck +
check:css + test:all, plus new regression tests covering every item above.
|
||
|
|
5078a1987f | Fix Windows encoded config reads | ||
|
|
eb592775a1 | Serialize LoadSession with in-process saves | ||
|
|
99c1539142 |
fix(security): gate env/path protections behind [secrets] config, fix PWD and clone bugs
Split the credential-protection layers by blast radius: - Durable surfaces (session transcripts, job artifacts) always redact. - Live tool output redaction stays default-on but honors [secrets] redact_tool_output for fixture-heavy repos. - Subprocess env filtering and the sensitive-path read denylist become explicit opt-ins (filter_subprocess_env, protect_sensitive_files, both default off): stripping GH_TOKEN/NPM_TOKEN broke gh, HTTPS git push, and npm publish; hiding .env broke legitimate edit workflows. [secrets] is user-global: project reasonix.toml values are discarded on load so a cloned repo cannot disable redaction or flip on the workflow-breaking protections. The captured global section deep-copies its *bool so the project-file TOML decode cannot write through it. Fix the PWD false positive: bare "pwd" now requires a leading [_-] separator (DB_PWD masks, POSIX PWD/OLDPWD pass through), keeping every subprocess's working-directory context intact. Fix RedactMessage writing through shared ToolCalls/MemoryCitations backing arrays: the save path hands in live session messages, and in-place masking would silently rewrite model-visible history mid-conversation and churn the prompt cache. Clone before masking. Fix SaveRecoveryBranch bypassing redaction: recovery branches are durable transcripts, and comparing a raw snapshot against the already-redacted on-disk content would misread coverage as divergence and fork a bogus recovery branch. Document the digest-consistency argument (Redact is deterministic and idempotent, so load/save cycles are byte-stable) and pin it with a regression test. |
||
|
|
344895e496 | fix(security): redact secret-bearing tool output | ||
|
|
1c17637267 |
Merge pull request #6126 from SivanCola/fix/sidecar-reconcile-and-doctor-out
Migrate diagnostic sidecars through overlong-name reconciliation / 超长名整理时迁移诊断 sidecar |
||
|
|
a7b5a0487e |
Keep diagnostic sidecars out of overlong-name reconciliation and migrate them
The overlong-name pass filtered by a bare .jsonl suffix, so an .events.jsonl or .conflicts.jsonl whose name crossed the 224-byte bound (owner stem between 209 and 224 bytes) was renamed into a fake session with fabricated meta, while the real transcript's rename orphaned both logs under the retired stem. Filter with store.IsSessionTranscriptName and carry the event log, event index, and conflict log through migrateSessionSidecars like the other owned sidecars. Also accept --out=PATH (equals form) in reasonix doctor session. |
||
|
|
d2d37c47ed |
Move the persisted-rewrite baseline into agent.Session
The controller kept savedRewriteVersion as external bookkeeping about the executor's session object, so every SetSession had to be paired with a manual baseline reset and snapshot() had to guess whether recovery had swapped the object — the guess that broke depth-cap force-saves (#6120). Store persistedRewriteVersion on the Session itself: save paths advance it with the rewriteVersion captured in the same lock window as the message snapshot (a compaction landing mid-save stays unpersisted, and a slower save cannot roll it backwards), clones carry it, and swapped sessions take their baseline with them. snapshotRewriteDecision and both mark helpers plus their seven call-site pairings collapse into Session.NeedsRewriteSave. Behavior fix folded in: installing a clone that still holds an unpersisted rewrite no longer stamps it persisted-by-construction; the next snapshot correctly runs as an owned rewrite. |
||
|
|
a1fecd3aa0 |
fix(agent): keep dirty-resume saves off the append shortcut, raw-check recovery need
Two more sites judged the normalized load as if it were the disk truth: - checkSnapshotWrite let a session resumed from a repaired transcript take the append-only shortcut with appendFrom measured against the normalized view. The event log replays the raw transcript, so the append event's index broke the replay chain and the whole appended turn silently vanished from disk on the next load (until a later save healed the log). A pending repair now falls through to the full rewrite, which persists the repair and the new turn together. - SaveRecoveryBranch decided "recovery not needed" only against the normalized view, so a mid-history repair (e.g. an empty tool-call name backfilled on load) made a fully-covered snapshot look uncovered and forked a pointless recovery branch. Coverage now falls back to the raw transcript, mirroring checkSnapshotWrite. |
||
|
|
09ce7df436 |
fix(agent): judge snapshot appends against the raw on-disk transcript
A mid-turn autosave can legitimately cut an assistant tool call from its still-running result, leaving a dangling call at the tail of the saved transcript. checkSnapshotWrite compared the next snapshot against the LoadSession view of disk, where normalization has already fabricated a placeholder answer for that call. The live session's real tool result collides with the placeholder, so a pure append misread as diverged and forked a "snapshot conflict" recovery branch 30 seconds after the first mid-turn save of any tool-heavy turn. Keep the pre-repair transcript on loaded sessions and fall back to it in checkSnapshotWrite when the normalized prefix checks fail: append shape, the owned-rewrite digest, and conflict-kind classification are now judged against the bytes actually on disk. Appending the real results also lands the transcript well-formed, so no repair is left pending in that path. |
||
|
|
82a9d866d6 |
fix(agent): cap recovery chain depth and force-save at the cap
A save-conflict loop used to fork recovery branches from recovery branches without bound — #5993 reached 8 nested levels, each fork polluting the session list. The conflict triggers behind that loop are fixed, but nothing prevents a future regression from spawning chains again: BranchMeta.Recovered was display-only and SaveRecoveryBranch never looked at the parent's recovery state. - Stamp BranchMeta.RecoveryDepth on every recovery fork (parent depth + 1; legacy recovery metas count as depth 1). - SaveRecoveryBranch refuses to fork a parent already at SessionRecoveryMaxDepth (3) with ErrSessionRecoveryDepthExceeded. - At the cap the controller force-saves the transcript onto the current branch instead: this runtime is the only writer of recovery branches it created, so the force write keeps the data, re-anchors the persistence baseline (no immediate re-conflict), and stops the file proliferation. A warn log plus a distinct notice make the condition visible. |
||
|
|
f4591d7c1c |
fix(agent): let a same-content foreign stamp keep rewrite ownership
A mirror runtime healing the revision ledger over identical content (same-content save recording a missed revision) advanced the revision under its own writer id. The next owned rewrite on this side — a cancelled turn's truncation or an auto-compaction — then read as a stale-runtime conflict and forked a recovery branch, even though the on-disk transcript was byte-for-byte what this session last persisted. ownsPersistedState now consults the ledger's recorded content digest: when the baseline digest, the on-disk transcript digest, and the ledger digest all describe the same bytes, a foreign revision stamp is bookkeeping over content this session owns, and rewriting it destroys nothing of the stamper's. At worst the conflict relocates to the stamper's next divergent save, where its own in-memory history forks a recovery branch as usual — no content-loss path opens. A stamp whose digest disagrees with the on-disk transcript still revokes ownership: that is the aftermath of a save whose bytes landed but whose record failed, the bytes cannot be attributed to a writer (event writer ids are per-process, not per-session), and only the conservative conflict path preserves them. |
||
|
|
895972d792 |
fix(agent): anchor stale-baseline session saves to the persisted baseline
Replace the exact-append CAS exemption with a reachability guard: locate the persisted baseline digest among the pending snapshot's incremental prefix digests (re-anchored on the disk system message when a resume swapped the prompt) and require the on-disk transcript to still reach that depth. This extends the stale-revision exemption to compatible-system appends (resume swapped the leading system prompt, then a routine turn append forked a spurious recovery branch), while closing the gap the exact-append exemption opened: an append over a transcript another runtime deliberately rewound below the baseline now conflicts again instead of silently resurrecting the removed suffix. ownsPersistedState no longer lets an absent revision ledger revoke rewrite ownership: recorded revisions start at 1, so a disk revision of 0 means the meta sidecar was deleted or rebuilt by a listing-only writer after this session's save — the digest+version match already proves the transcript is ours. Cancel-turn truncations and compaction rewrites therefore survive a swept sidecar. A foreign recorded revision still revokes ownership, preserving the guard against stale controllers force-rewinding a ledger another runtime has stamped. |
||
|
|
7f9491966c | fix(agent): allow safe session append from stale revision baseline | ||
|
|
075b961254 |
Merge pull request #6030 from SivanCola/fix/lease-experience-cleanup
Polish lease UX: self-heal metadata damage, sanitize errors, retire lock sidecars / 打磨租约体验:元数据损坏自愈、错误脱敏、锁侧车清理 |
||
|
|
8ea1c4905b |
Merge pull request #6023 from SivanCola/fix/desktop-lease-key-canonicalization
Fix Windows desktop session lease self-lock on model switch / 修复 Windows 桌面端切换模型会话租约自锁 |
||
|
|
c33c372a2f |
fix(desktop): self-heal lease metadata damage, sanitize surfaced errors, retire lock sidecars
Follow-up polish on the lease/CAS stack after the #6023/#6025 hotfixes, closing the remaining ways a healthy single-window session could still present as busy, leak holder details, or litter the session directory. - Reclaim now arbitrates on the OS lock alone: a missing or unreadable lease.json (user cleanup, AV quarantine, crash tear) with a free lock is a leftover, not a holder. Previously an orphaned in-process entry whose info file disappeared could never be reclaimed - the fallback acquire re-hit the orphaned entry forever and every rebuild stayed busy. The desktop gate mirrors this: nil lease info allows the reclaim attempt, foreign holders are still refused. - Raw lease errors (session path + host-pid-writer id) can no longer reach the UI: startup bind failures (StartupErr feeds the topbar banner and tab metas), ClearSession's bridge returns, and the recovery-lease callback (chat notices via snapshot errors) all render the user-facing busy message; raw details stay in slog. The busy message drops its dangling 'before changing this setting' clause when no setting applies. - SessionLease.Release retires the .lock/.lease.lock sidecars through the existing atomic take-then-remove helpers (non-blocking, no-op against any live holder or in-flight save), so ordinary use stops accumulating lock files that previously waited for the next boot reconcile (#6014). - Autosave failures warn once per burst and at most once per 5 minutes per tab: retries are slog-only, the user notice fires when the burst gives up. Explicit action saves keep immediate feedback. Every failure is still logged. - SaveRecoveryBranch tolerates event-index write failures like the other save paths: the recovery transcript and meta are already durable, so a failed listing accelerator no longer discards a successful recovery. - Lease test suite now feeds user-shape (mixed-case) paths to the APIs under test instead of pre-canonicalized ones - the pattern that let the Windows case-fold mismatch (#5999) escape the suite - plus regressions for orphan-without-info, corrupt-info, foreign-info-refused, sidecar retirement on release, StartupErr sanitization, and the autosave warning debounce. Refs #6014 #5999 #6027. |
||
|
|
728d4af4ce |
fix(session): heal stale meta ledger on same-content saves
A save that lands its transcript bytes and then fails to record the revision (fail-closed recordSessionContentRevision, or a crash between the two writes) leaves the sidecar describing older content. The same-content retry then took the up-to-date shortcut and never touched the ledger again, so revision/content_digest stayed stale until the next content-bearing save — contradicting the "later save can bump the revision" recovery the fail-closed path promises. Idle sessions never healed at all. checkSnapshotWrite now surfaces the ledger digest it already reads, and the up-to-date path records the deferred revision when that digest no longer matches the on-disk transcript, reproducing the exact state the interrupted save would have left. A missing or digest-less legacy sidecar stays untouched: stamping those would bump revisions other runtimes still hold as baselines. |
||
|
|
e6d574587a |
fix(session): harden the CAS meta ledger against torn reads and lost updates
The transcript CAS keyed off the .jsonl.meta sidecar, but the ledger was maintained unprotected. On Windows this manufactured conflicts in normal single-window use: recovery branches forked on every app start, recovery files accumulated, and 'adopted the newer transcript' notices looped. - Retry transient sidecar reads (I/O and JSON-decode failures, 20/50/100ms) before giving up: ReplaceFile's non-atomic copyOnto fallback tears concurrent readers, and AV/indexer handles fail reads transiently. - Stop swallowing unreadable-ledger errors into revision 0: a persistently unreadable ledger now fails the save (transcript bytes are already durable; the autosave retry heals it) instead of desyncing the baseline and misreading the next honest save as a foreign write. - LoadSession with an unreadable sidecar still opens the session but marks the baseline revision-unknown: revision CAS is disarmed (digest/prefix checks still catch real divergence) and owned rewrites anchor on digest+version, so compaction cannot become a permanent conflict. A missing sidecar remains a known revision-0 baseline. - Hold the per-path meta lock across the whole read-modify-write in RenameSession, topic-title propagation, legacy topic migration, and restoreSessionTopicIndex, so a concurrent save's revision bump can no longer be rolled back by a stale read-back. - Downgrade event-index write failures to a warning at both save() sites: the index is a listing accelerator and must not strand the baseline behind a transcript+revision that already landed. - Capture save() snapshots under the save locks so concurrent in-process savers can no longer land out of order and misread the newer transcript as a stale-prefix conflict. Regression tests cover fail-closed saves on a corrupt ledger, unknown- revision baselines (load, append, owned rewrite), missing-meta behavior, index-failure tolerance, a concurrent writers hammer asserting revision monotonicity, and deterministic under-lock capture. Fixes #6014. Fixes #5998. Fixes #5996. Refs #6003. |
||
|
|
aa503bca85 |
fix(desktop): unify session runtime keys with lease canonicalization
The lease registry keys sessions through agent's canonical form (clean, absolute, lowercased on Windows), while desktop sessionRuntimeKey kept the raw path case. On Windows every tab path contains uppercase segments (C:\Users\...), so the tab's own lease never matched its session key: ensureSessionLease re-acquired against the process's own registry entry, reclaim then hit the tab's live OS lock handle, and every model / effort / token-mode / provider-key rebuild failed with the "already open in another Reasonix window" error on a single window — including fresh installs. - Export agent.CanonicalSessionPath and layer it into sessionRuntimeKey so desktop identity checks agree with the lease registry byte-for-byte. - Normalize the canonicalTabSessionPath fallback (project-scope sessions) with clean+abs so shape variants cannot split one file into two keys. - Keep case-folded keys out of SessionPath: detach/attach paths now carry the display path separately from the runtime key. - Release only the build's own lease on startup-build failure, and skip claiming a lease the build merely reused (a concurrent switch owns it). - Supersede in-flight startup builds when a model/effort/token switch installs its controller, so a slow blank-session build can no longer overwrite the new runtime or strip its lease. - Regression tests: lease/runtime key agreement, idempotence, Windows case folding, project-scope fallback normalization, fast-path lease reuse. Fixes #5999. Fixes #6006. Fixes #5989. Fixes #5990. Refs #5996. |
||
|
|
7bb7290c2f |
Hold session locks across trash/delete and fix lint
Review follow-ups on the event-log hardening: - Close the probe-then-delete lease window: desktop trash, reconcile, and permanent delete now take a SessionRemovalGuard that holds the session's save lock and lease lock across the whole operation (built on #5977's delete-while-holding-flock primitive), so another process can no longer acquire the lease between a busy probe and the sidecar removal and then lose its freshly locked lease file. Live owners surface as a sanitized busy error; the lock files are deleted atomically with the release. - Run overlong-named sessions (pre-bounded recovery cascade names) in checkpoint-only mode: their event log/index sidecars would exceed the filesystem name limit, so the probe treats them as non-native instead of failing saves with ENAMETOOLONG. - Fix staticcheck QF1003: tagged switch for the save-reason mapping. |
||
|
|
b942018011 | Merge remote-tracking branch 'origin/main-v2' into fix/session-recovery-event-log-cleanup | ||
|
|
f0221714c9 |
Harden session event log persistence and recovery cleanup
- Replay the event log tolerantly: torn tails are truncated by the next save instead of bricking LoadSession; foreign/legacy .events.jsonl files squatting the native log path are read-ignored and never written; logs from a newer schema fail loudly instead of being silently truncated. - Bound log growth: past a size threshold the log compacts to a single replace event, and every rewrite/compaction refreshes the .jsonl checkpoint so direct readers and older binaries stay bounded-stale. - Keep force saves (subagents, guardian, migrations, forks) single-file: they never bootstrap an event log and fold an existing one instead. - Sweep the event log/index from every deletion surface through store.SessionSidecarFiles — /clear, serve delete, ACP delete, subagent delete, desktop trash — so deleted conversations cannot leak or be resurrected; guardian sidecars are excluded from transcript discovery. - Guard desktop trash/delete with an external-lease busy check so removing lease files cannot break cross-process mutual exclusion. - Migrate frozen-checkpoint readers to event-aware APIs: prompt history, serve session list, auto topic titles, duplicate-trash comparison, history search recency, and doctor byte totals. - Count recovery-copy activity in topic summaries, hide recovery-only topics only when unpinned with no live runtime session, and mark conflict copies in the session tree and History drawer. |
||
|
|
5311e0c9eb |
fix: open lock handle with DELETE access and commit overlong renames atomically
The Windows delete-disposition path only works when the handle carries DELETE access, but os.OpenFile requests just GENERIC_READ|GENERIC_WRITE, so RemoveAndUnlock always fell through to the post-close os.Remove fallback — reopening the exact cleanup-vs-saver window the unlink-under-lock ordering was meant to close (invisible to single-threaded CI). Open the lock file with CreateFile(GENERIC_READ|GENERIC_WRITE|DELETE) and full share mode so the disposition call runs on the locked handle; a Windows-only test pins that the fallback counter never advances. A plain open of a lock file now reads as held (sharing violation), matching the flock-held contract. renameOverlongSession committed the transcript rename and then folded any sidecar-migration error into a single return that dropped newID, so the caller skipped re-parenting even though the old name was already gone — orphaning child branches with no way to recover the mapping on a later run. Split the rename (retryable: on failure nothing moved) from post-rename migration (best-effort: newID is returned with joined warnings) so children always re-parent once the transcript has moved. |
||
|
|
963b43c625 |
fix: delete held lock files via handle disposition on Windows
Windows refuses a path-based os.Remove of a file this process still holds open, so the unlink-under-lock cleanup introduced for lock/lease sidecars failed with a sharing violation on every removal (windows-latest CI). Replace the closure-based try-lock with a sessionLockFile handle whose RemoveAndUnlock deletes the file atomically with the release: Unix unlinks while the flock is still held, Windows marks the delete disposition on the held handle so the name dies with it. Lease info sidecars keep the plain remove-under-lease-lock path — nothing ever holds them open. The overlong rename pass retires the old .lock through the same primitive. |
||
|
|
e5494594c2 |
fix: close rewrite-persistence races and migrate overlong session names
Capture the rewrite version when the snapshot decision is made and record that captured version monotonically after a successful write, instead of re-reading it afterwards: a compaction landing mid-save could be marked persisted without ever reaching disk, turning the next autosave into a spurious conflict and a recovery branch. A stale no-rewrite decision now retries once as an owned rewrite, and disk-adoption resets the baseline to the adopted session instead of inheriting the replaced session's counter. Unlink stale lock/lease sidecars while still holding their flocks so a concurrent saver cannot acquire an inode that is being deleted under it. Rename transcripts whose basenames outgrew the sidecar name limit (the leftover shape of the unbounded recovery cascade, #5923): conversation bytes move verbatim to a bounded name, branch meta migrates with its ID rewritten, children are re-parented, and disposable lock/lease files are retired. Sessions past the limit could never be saved again because their lock sidecars were uncreatable. |
||
|
|
0f7f43e744 | fix: prevent cascading session recovery | ||
|
|
564afe1981 | Fix session recovery persistence cleanup | ||
|
|
96ff4b6503 |
Merge remote-tracking branch 'origin/main-v2' into fix/desktop-runtime-locks
# Conflicts: # internal/agent/session.go |
||
|
|
1e4f38271c |
fix(control): close session data races on rewind/compact/turn and sidecar meta
Rewind truncated the live message slice in place, racing History/Save readers on other goroutines; use Session.Snapshot/Replace and a lock-guarded Len(). Compact lacked the running-turn guard Rewind/Branch have, letting a manual compact rewrite the log under the run loop. Run's turn++ was unlocked. The branch-meta sidecar RMW helpers (UpdateSessionMeta, in-flight markers, model/touch) now hold the per-path save lock so concurrent writers can't drop each other's fields. Document the promptMu/sink.Emit contract. |
||
|
|
565216c28c |
Skip revision bump on no-op session snapshot
checkSnapshotWrite now reports when the on-disk transcript already matches the pending write byte-for-byte. save() skips the rewrite and revision bump in that case instead of unconditionally recording a new revision. Without this, a routine no-op autosave (e.g. an idle tab's shutdown snapshot) advanced the on-disk revision under any other runtime that had cloned this file's persistence baseline via CloneWithMessages(If Compatible). That runtime's next legitimate append then read as a stale-runtime conflict and forked a spurious recovery branch — the same symptom this PR fixes, reintroduced through a different trigger. A load-time repair (LoadSession normalizing dangling tool calls, empty names, truncated args) still forces a real write even when digests match, so the repair actually reaches disk instead of being skipped as a no-op forever. |
||
|
|
fd9f8027f4 | Guard session baseline cloning | ||
|
|
62cbf89624 | Fix session rewrite recovery loops |