Commit Graph
19 Commits
Author SHA1 Message Date
SivanCola 7e45e66110 Preserve provider failure diagnostics across cleanup and restart / 保留收尾与重启后的模型请求失败诊断 (#11152)
* fix: preserve provider failure diagnostics across cleanup and restart

Problem: Failed requests could reopen without their failure identity or the
transport evidence needed to investigate them.

Root cause: Cleanup replaced terminal recovery metadata, HTTP/2 protocol
errors fell through classification, and request observations were process-local.

Fix: Preserve failure metadata at the shared cleanup boundary, recognize
HTTP/2 transport codes without enabling retries, and persist bounded redacted
request evidence atomically with turn completion. Localize failure summaries
and document additive persistence compatibility.

Verification: Full root Go suite, owning-package and focused race tests,
desktop history/export regressions, frontend tests and typecheck, Chromium
interaction checks, Windows provider cross-compilation, and repolint passed.

* fix: align diagnostic contracts and isolate lifecycle test fixtures

Problem: CI rejected stale desktop contracts and an error type assertion;
Windows lifecycle tests also timed out around filesystem finalization.

Root cause: The additive diagnostic field was not regenerated into the host
contract. Transport classification used a direct error assertion. Functional
checkpoint and cancellation tests unnecessarily coupled live state assertions
to multiple durable writes under short completion deadlines.

Fix: Regenerate the contract, reject wrapped API errors using errors.As, and
use existing in-memory checkpoint/controller fixtures for live semantics.
Retain all assertions and dedicated durable maintenance, checkpoint reload,
and cold-export coverage without changing production timeouts or CI gates.

Verification: Pinned golangci-lint 2.12.2, repolint, focused provider/control
tests, three race-test repetitions, checkpoint persistence reload, frontend
typecheck, and diff checks passed locally.

* fix: structure transport diagnostics and bound failed-turn evidence

Problem: Transport classification depended on error wording, UI projections
dropped structured diagnostics, successful requests persisted peer addresses,
and bounded request history could silently appear complete after eviction.

Root cause: Diagnostic identity was converted back to prose across transport
and presentation boundaries, while lifetime-wide request eviction lacked
bounded per-turn accounting and unsuccessful-turn persistence gating.

Fix: Classify exported HTTP/2 types and exact stdlib type/code identities;
carry diagnostic fields through all notice/history paths; persist only failed,
interrupted, or recovery-required turn evidence; and record per-turn dropped
counts and truncation, marking unavailable accounting incomplete.

Verification: Typed and real TLS connection/stream/GOAWAY tests, provider/control
race checks, cold-export and success-persistence regressions, per-turn loss
tests, frontend projection tests, Chromium three-locale interactions, production
frontend build, and bundle budgets passed. Preserve model bytes and retry policy.

* fix: keep optional diagnostics from blocking terminal commits

Problem: Encoding optional provider evidence could prevent required turn-end
events from committing. Persisted free-text transport errors duplicated the
structured evidence, and generated detail text duplicated HTTP/2 codes.

Root cause: Optional diagnostic errors escaped the shared terminal-event
builder, while transport prose was retained at multiple presentation boundaries.

Fix: Warn and omit failed diagnostic records while preserving required terminal
events. Remove transportError and its byte truncation. Leave transport-code
rendering to the structured frontend presenter. Update export documentation.

Verification: Real invalid-timestamp regression covers failed/interrupted/
recovery-required closures; synchronous and Desktop-send failures retain durable
turn/end and cold-export identity. Structured-evidence, no-prose, presentation,
and model-context tests pass, as do frontend typecheck and affected suites.
2026-09-28 19:01:57 +08:00
esengine 2dfa1ca44c fix(transcript): stream live deltas for sessions on the legacy projection
A session opened from a legacy topic path follows its transcript through
the legacy projection (storageBackend "legacy"). There every turn event,
text and reasoning deltas included, takes the next ledger sequence, so
each follow change advanced commitSeq by one but carried no firstSeq.
The desktop follow client treats a change without firstSeq as a display
frame that must ride the current cut, rejected the very first change of
the turn as "transcript frame cut mismatch", dropped the subscription
and re-baselined once a second. No delta reached the reducer, so neither
thinking nor answer streamed and the reply appeared whole at turn end.

The stream_attempt commit on that path also asked for a reset, because
the legacy path never recorded a result sequence. Recording the message
event's sequence covers rounds with text or reasoning, but a tool-call-
only round and an interrupted round publish no message event at all.

Projection.Apply now publishes the sequence each envelope occupies as
the change's firstSeq. A commit settles on the recorded message sequence
or, when the round had no message event, on its own sequence: the agent
emits the commit only after the round's record is durably appended, so
that sequence is already covered. The entry is dropped at the commit so
the map stays bounded. ApplyFrame (the exclusive runtime path) is
unchanged.

Test: TestLegacyTopicTranscriptFollowStreamsDeltasTheRendererAccepts
opens a legacy topic through StartTopicActivation on a real App with a
scripted streaming provider, follows through TranscriptFollowForTab and
applies the renderer's acceptance rules (mirroring
transcriptFollowClient.ts validate: coverage, sampling, resultKind,
records-based result identity with its 192 cap). Subtests run a
streamed reply, a tool-call-only round and a cancelled round, each
followed by a streamed reply.
- main-v2: all three fail with "frame_cut_mismatch at revision 3".
- message-sequence fix only: tool-call-only and cancelled rounds fail
  with "revision_gap" at the commit.
- this change: all pass, every delta delivered with no re-baseline.
Replaying captured follow responses through the real
TranscriptFollowClient accepted 0 deltas before and all of them after.

Fixes #10968
2026-09-26 21:32:08 -07:00
esengine 50f3d33a50 fix(transcript): an interrupted turn keeps its provider error visible
A turn_done with status interrupted or recovery_required took the generic
"turn interrupted" branch in both renderers (the transcript projection and
the desktop reducer), which ran before the err branch and dropped err,
detail and diagnostic. Turns persisted by 1.38.7 as recovery_required with
an HTTP 402 therefore replay as an unexplained interruption.

The interrupted branch now also emits the provider_request_failed notice
when the turn carries an error whose diagnostic is not a cancellation. The
cancellation test reads the diagnostic kind, now named
provider.FailureKindCancelled, rather than the error text; a user stop still
shows only the interrupted notice.

Tests: TestInterruptedTurnKeepsProviderFailure (internal/transcript) and
interrupted-turn-provider-error.test.ts both fail on the base and pass here.
2026-09-25 07:12:24 -07:00
SivanCola 6448d8c985 fix(transcript): reconcile queued guidance by message ID / 修复队列引导重复记录 (#10723)
* fix(transcript): reconcile queued guidance by message identity

Problem: A queued guidance insertion could appear twice when its live event and durable history row reached the Desktop in different orders. Unapplied guidance warnings had the same duplicate-row failure.

Root cause: Live receipts and canonical records used unrelated display identities, while some legacy history rows omitted the owning message ID.

Fix: Assign one message ID to each guidance insertion, retain legacy record addresses, normalize older history identities, and merge event and canonical rows by identity. Preserve inbox consumption receipts independently of display and cover applied and unapplied guidance in local, remote, and browser paths.

Verification: Focused Go and frontend regressions, root Go suite, frontend transcript suite, Chromium DOM transcript workload, typecheck, and repolint passed.

* test(frontend): typecheck steer transcript regressions

Problem: The new transcript regressions executed successfully but their fixture types failed the repository test-typecheck gate.

Root cause: Inferred literal state types were narrower than reducer results, while broad item and history interfaces hid the notice-specific fields required by assertions.

Fix: Type the reducer state at its public boundary and narrow notice items explicitly without changing the behavioral assertions.

Verification: pnpm test:typecheck, canonical user display test, and steer transcript test passed.
2026-09-24 12:08:32 +08:00
SivanCola 3476556ee8 Defer historical session loading and add bounded native paging / 延迟历史会话加载并加入有界原生分页 (#10664)
* Improve historical session startup and lazy loading

* Fix history preparation retirement across readers

Problem: source replacement and immediate reopening could expose a closed SQLite cache to retained history readers.

Root cause: reader cancellation did not follow preparation retirement, the last release removed cache ownership before close, and compatibility paging opened a separate owner.

Fix: share preparation across both APIs, propagate source cancellation, retain and drain cache-close barriers, and retire unbound borrowers during shutdown.

Verification: deterministic shared-reference, replacement, immediate reopen, compatibility cursor and shutdown tests passed with the related history paging race suite.

* Index history read fences and extend lazy catalog pages

Problem: paging repeatedly cloned the registry and scanned every source mapping, while existing groups, time filters and pinned shells bypassed lazy catalog reads.

Root cause: read invalidation lacked immutable reverse indexes and source fences could mix a captured WAL view with live metadata.

Fix: share exact-publication lifecycle tokens and one verification per validation; reuse lazy pages for all-session views, frozen time filters and automatic pinned shells; capture source fences from their listing transaction. Document remaining full-materialization and recovery limits.

Verification: registry, history lifecycle and catalog paging race suites; deterministic WAL-capture replacement regression; transcript tests; full root Go suite. Desktop full partition sweep remains in progress.

* Validate fresh macOS images and join history batch workers

* Page native history outlines and resolve direct read anchors

* Read schema-one history through cancellable native projections

* Handle unused history roots and verify packaged native readers

* Page grouped history without materializing every session

Problem: group filters expanded all legacy rows and loaded unrelated canonical
headers. Disappeared history roots could also be omitted from completeness.

Root cause: group membership forced the full snapshot adapter, and listing
availability independently skipped absent roots instead of using discovery.

Fix: retain immutable catalog pages with source-key membership predicates,
admit canonical group and pin members before Stat, and use metadata discovery
state for absent roots. Keep source hashes and persisted schemas unchanged.

Verification: catalog and Desktop pagination/organization regressions, related
race tests, sidebar browser interactions, and stronger packaged legacy reader
checks. Root-wide validation and the new packaged group check continue separately.

* Share native history preparation with compatibility content readers

Problem: Unbound history field requests could enter compatibility repair after
native paging had already prepared a read-only view of the same source.
Root cause: Paging and content RPCs borrowed different history preparation
owners and did not share cancellation and source validation boundaries.
Fix: Centralize native pager borrowing and use it for compatibility field
reads, including explicit targets, while retaining unsupported-format readers.
Verification: Focused paging/content regressions and race tests pass, including
Unicode chunk reads, unchanged authoritative bytes, and peer cancellation.
The native package harness now exercises unbound content reads as well.

* Bound queued history iterators and resolve original compatibility sources

Problem: Rotating discovery retained one descriptor per historical root, and
cold compatibility RPCs rejected global files after workspace restoration.
Root cause: Scan admission had no retained-iterator budget; read validation
mistook the destination workspace for the captured historical source root.
Fix: Limit queued discovery to eight iterators with a visible-workspace
reserve, retaining admitted progress. Share known-root source resolution
across cold paging, field reads, and legacy previews outside App.mu.
Verification: Catalog full race suite and Desktop focused race tests pass.
Tests cover retained progress, priority switching, eventual completion, old
global source reads, full Unicode content, and unknown/symlink root rejection.
The packaged smoke caught the original directory failure; retain compact
page diagnostics so an absent field ref identifies its actual read error.

* Filter historical metadata pages without materializing the library

Problem: Sidebar text queries bypassed lazy paging and built every legacy
session row before returning a small matching page.
Root cause: The bounded catalog adapter excluded text requests because SQL
case conversion could not preserve display-layer Unicode/title semantics.
Fix: Filter bounded catalog batches through the existing Go matching rules
inside the retained WAL snapshot. Read captured source identities instead
of resolving candidate files and account for retained query text.
Verification: Catalog and Desktop focused race tests cover sparse matches,
Unicode, cancellation, fixed-cut continuation, source keys and localized
titles. Sidebar browser interaction tests pass. Native smoke now also checks
text filtering within a group; sparse queries can still scan all metadata.

* fix(desktop): admit explicit group targets without scanning history

Problem: ordinary group edits enumerated all legacy source pages before
applying a workspace organization mutation.
Root cause: group commands shared manual reordering's full-source admission.
Fix: admit only resolved group targets inside the revision CAS, normalize
physical source keys, retain explicit ungrouping fences, and recheck source
adoption and lifecycle ownership inside the registry transaction.
Verification: organization and group regressions pass with the race detector,
including deterministic adoption-before-commit rejection and late preference
import. Document the remaining full-source manual and legacy preference paths.

* fix(desktop): decouple catalog admission from metadata refresh

Problem: catalog startup and filesystem event delivery waited for whole
project/topic metadata transactions and restored-path index writes.
Root cause: startup and the watch loop synchronously owned these writes,
while user edits started independent unjoined metadata goroutines.
Fix: register watches before restored identities settle, enqueue restored
paths, and coalesce all metadata refresh triggers into one cancellable worker
joined by the catalog lifecycle. Publish admission without waiting on writes.
Verification: catalog/sidebar regressions and race tests pass. Deterministic
coverage holds a real SQLite writer during admission, blocks refreshes to
verify coalescing, and checks cancellation and lifecycle wakeup ownership.
Whole-projection metadata reconciliation and synchronous database integrity
validation remain explicitly documented acceptance gaps.

* refactor(history): separate projection readers and preparation stages

Split native pager source validation, DAG projection, compatibility windows, catalog paging, and controller usage accounting into focused owners. Remove the unreachable continuation rebind in the empty-key topic branch. Preserve source validation, transaction boundaries, and snapshot ownership without increasing lint baselines.

Verification: repository lint; agent, sessioncatalog and session package suites; focused Desktop history/catalog/snapshot/navigation tests; frontend typecheck and transcript suite.

* fix(history): serialize discovery progress with catalog writers

Problem: the 100k native discovery fixture stalled with SQLITE_BUSY. Root cause: progress publication bypassed the process-local mutation lock while other catalog writers used it. Fix: route progress through the same boundary and preserve its generation predicate and cancellation behavior. Add a production-package scale harness and record the incomplete history.12 result without relaxing its gate.

Verification: focused metadata/reconcile/catalog race tests, repository lint, script syntax check; native scale harness smoke and history.12 measurement exposed the failure. A fresh package run is still required.

* fix(history): move metadata integrity audits behind startup admission

Problem: cached catalog opening still scanned every database page before returning a sidebar page. Fix: open advisory metadata separately from the full background audit while keeping content projection validation synchronous. Corruption revokes the generation, stops its work, closes all read leases, and reopens through the existing validated quarantine path with a monotonic revision floor. Old cursors return stale_cursor; canceled or transient audits cannot quarantine data, and retries remain bounded.

Verification: root Go suite; projectiondb and sessioncatalog race suites; Desktop catalog/history/snapshot race regressions; real CHECK-constraint corruption, source preservation, lease closure, late publication, cancellation and retry tests; repository lint. Native package performance acceptance remains pending.

* fix(history): avoid redundant discovery and source-wide metadata writes

Problem: Large legacy catalogs could finish discovery then immediately restart,
while registry refresh rewrote unrelated discovered topics.

Root cause: Startup recovery used the full-root invalidation notification
alongside watcher-owned discovery; metadata membership reset and orphan cleanup
scanned all topics.

Fix: Publish only registry metadata after reservation recovery, and use the
existing metadata_present flag with an optional partial index to constrain
membership refresh. Source mutation retains topic cleanup ownership. Record
incomplete history.13 scale evidence and warmup progress.

Verification: sessioncatalog suite, focused catalog race tests, Desktop
startup/catalog/history race tests, repolint, JavaScript syntax check, and diff
whitespace checks passed. New package scale acceptance remains required.

* fix(history): bound directory watching and merge undispatched invalidations

Problem: Large catalogs continued to restart discovery after the initial scan.

Root cause: Catalog monitoring bypassed the platform directory backend and used
macOS kqueue registration that enumerates children and opens per-file handles.
Unwatched roots were also invalidated on every metadata tick, and queued jobs
retained pre-dispatch changes as unnecessary follow-up scans.

Fix: Reuse native directory watching, preserve canonical event/access routing,
separate watch retries from the rotating audit, and merge invalidations before
scan dispatch while retaining genuine in-flight source changes.

Verification: Focused catalog queue and Desktop catalog/watcher race tests pass,
including 512-file native registration under a 256-descriptor process limit,
canonical events, recovery gaps, durable journals and late file discovery.
Repolint and diff checks pass. Record the incomplete history.14 scale evidence;
the new production package still requires measurement.

* fix(history): preserve cold reads when execution recovery fails

Problem: lease-blocked native navigation could leave a blank transcript, and cache failures could silently start full compatibility replay.

Root cause: passive readiness refresh switched startup-error tabs to controller-dependent Follow; activation failure settled independent history state. Native preparation did not distinguish ordinary errors from explicit unsupported formats, and root invalidation retained native watcher registrations.

Fix: coalesce matching cold reads, fence rebuild/reconnect paths, and let only read failures settle history errors. Restrict compatibility fallback to explicit unsupported results, positively identify ancient event envelopes, and remove replaced native watches.

Verification: deterministic activation regressions (59 assertions), frontend typecheck/lint, transcript suite, loading browser tests, focused agent/Desktop race tests and Go lint pass. Root full suite previously passed; Desktop default package timeout remains under investigation with exhaustive deterministic shards. Production history.15 failure is documented; new-package acceptance remains pending.

* refactor(history): share runtime rebuild subscription handling

Problem: the cold-read fence duplicated rebuild handling and exceeded the existing initial bundle budget by 380 bytes.

Root cause: single-tab and settings-wide rebuilds maintained equivalent subscription ownership logic in separate branches.

Fix: iterate the affected identities through one stop/invalidate/rebuild path, retaining the cold-read and activation guards without changing the asset budget.

Verification: activation regressions (59 assertions), prompt lifecycle tests and TypeScript checks pass; the complete production build will verify the unchanged bundle gate.

* fix(history): coalesce startup watches before resuming discovery

* fix(history): keep startup locale sync out of discovery

* feat(history): add shared on-demand native full-text search

* feat(history): add bounded discovery source diagnostics

* desktop: ignore noisy root watcher writes

* desktop: drain native watch backlog before initial history discovery

* refactor(history): remove retired startup migration workers

* fix(history): coalesce watcher overflow before journal writes

* fix(history): replace invalid scans at slice boundaries

Problem: large metadata discovery finished obsolete generations before replaying queued root changes, while unchanged rows rewrote topic projections.

Root cause: invalidations were consumed only at EOF and discovery always published full upserts.

Fix: supersede invalid iterators between slices with bounded coalescing; preserve incomplete journals and recheck unchanged rows transactionally before updating scan presence only.

Verification: sessioncatalog/historywork race suites, deterministic restart and projection regressions, Desktop watcher/startup race tests, and sessioncatalog lint pass. Native scale acceptance remains pending.

* perf(history): project registry metadata in bounded slices

Problem: registry refresh held the catalog writer for the entire registry and rewrote unchanged metadata.

Root cause: metadata-only discovery reused the atomic full-projection refresh and a whole-task timeout restarted large observations.

Fix: serialize cancellable observations, share background slice admission, compare persisted rows, defer paged retirement until input completes, and bound each writer transaction. Preserve atomic refresh for other catalog modes.

Verification: root Go suite, catalog/historywork race suites, Desktop catalog race coverage, frontend typecheck/transcript and loading/sidebar/navigation/scroll browser gates pass. Retain the preceding history.24 package's 90-run native scale evidence separately.

* feat(history): resume interrupted checkpoint index preparation

* test(history): verify checkpoint preparation across process exit

* feat(history): resume streamed legacy display index imports

* refactor(history): separate maintenance admission and publication steps

* feat(history): resume native event preparation across process exit

* fix(history): preserve cold reading windows during passive readiness refresh

* test(history): record production scale results and exercise native cache paging

* perf(history): load cold refresh proof on demand

* perf(history): defer session restoration policy with its reader proof

* perf(history): keep restoration policy outside the eager module graph

* feat(history): resume DAG preparation across all durable phases

* fix(history): include protected windows in resident cache capacity

* docs(history): document DAG resume and native cache findings

* fix(history): release evicted read bindings without affecting replacements

* test(history): qualify native cache resources and startup scale

* docs(history): record history.29 cache qualification

* test: align history migration fixtures with current schemas

Keep the junction migration regression focused on the last pre-invalidation schema and derive the projection schema assertion from the migration ledger. Complete the history hydration Meta fixture so frontend test type checking covers the current contract.

* fix: align startup bundle gate with measured producer output

Problem: the history startup PR exceeded the raw frontend bundle gate by 275 bytes on the Linux producer and the migration regression comment exceeded repolint's essay budget.

Root cause: the earlier budget ceiling was based on a pre-rebase producer measurement, while the test fixture note was four lines at a three-line position limit.

Fix: set the raw initial gate to the smallest measured 2056.6 KiB ceiling, keep all compressed and chunk budgets unchanged, and condense the migration note without removing coverage.

Verification: node scripts/check-bundle-budget.mjs; go run ./tools/repolint; focused sessioncatalog migration tests; git diff --check.

* fix: format desktop catalog runtime

Problem: the desktop producer gate rejected one unformatted composite literal in session catalog runtime code.

Root cause: the latest history loading changes left the pinned-only field alignment outside gofmt output.

Fix: apply the repository formatter without changing behavior.

Verification: gofmt -l ., git diff --check, and go run ./tools/repolint.

* fix: use passing producer bundle ceiling

Problem: the Desktop producer still exceeded the raw startup bundle gate by 112 bytes after the previous measured adjustment.

Root cause: the Linux CI build measured 2106070 bytes while the 2056.6 KiB ceiling allowed only 2105958 bytes.

Fix: set the raw initial budget to the smallest 2056.8 KiB ceiling that contains the producer result; compressed, chunk, CSS, locale, and residency budgets remain unchanged.

Verification: node scripts/check-bundle-budget.mjs, go run ./tools/repolint, gofmt -l ., and git diff --check.

* fix: allow Windows history sources to be replaced during paging

Problem: Windows pager fencing tests failed when a source or event log was replaced while a display index was being built.\n\nRoot cause: os.Open uses a Windows sharing mode that denies delete and rename, so the deterministic source-change interleaving failed before the pager could validate the new pathname identity.\n\nFix: add a platform-owned replaceable read helper. Windows opens sources with read, write, and delete sharing; Unix keeps the ordinary descriptor. All legacy pager builders, event-kind probing, and paged event/DAG reads use the shared helper.\n\nVerification: go test ./internal/agent -count=1; GOOS=windows GOARCH=amd64 go test -c ./internal/agent; go run ./tools/repolint; go test ./...; git diff --check.

* fix: match Windows replaceable read handle flags

Problem: the first shared-delete handle still left the DAG source replacement interleaving failing on Windows.\n\nRoot cause: the native source handle did not use the same reparse-aware backup semantics as the repository's proven replaceable regular-file reader.\n\nFix: align the fileops helper flags with the existing Windows repair reader while retaining read, write, and delete sharing.\n\nVerification: GOOS=windows GOARCH=amd64 go test -c ./internal/agent; focused pager tests pass on the host.

* fix: grant Windows pager handles replace access

Problem: atomic replacement of a DAG event log was still denied on Windows despite delete sharing.\n\nRoot cause: the replacement operation also needs delete access on the existing native source handle.\n\nFix: request DELETE together with GENERIC_READ while retaining read, write, and delete sharing.\n\nVerification: GOOS=windows GOARCH=amd64 go test -c ./internal/agent.

* fix: make Windows source replacement regression portable

Problem: the pager source-fencing regression used os.Rename(temp, existing) while the target was open, which Windows rejects; granting DELETE access then broke ordinary concurrent rewrites.\n\nRoot cause: the test encoded Unix replacement semantics that are unavailable for an open Windows target, while the production reader must remain compatible with normal writers.\n\nFix: keep the shared-delete read handle at read access and model replacement by moving the old pathname aside before installing the new file. The test still verifies a changed native identity is rejected before publication.\n\nVerification: focused pager source-change tests, GOOS=windows GOARCH=amd64 go test -c ./internal/agent, go run ./tools/repolint, and git diff --check.

* fix: join startup cold history on runtime ready

Problem
A runtime-ready event could start ordinary session hydration while startup cold history was still in flight, issuing a duplicate history read for the same session.

Root cause
The ready reconciliation only shared the normal session-load owner. Cold startup reads were tracked separately, so the readiness path did not join that owner.

Fix
Join matching cold-history promises before starting ordinary hydration. After the certified window settles, install the live follower and continue ancillary hydration with the cached window. Keep the failure path on the existing reader and record the measured 163-byte startup bundle increase with the smallest producer-safe budget update.

Verification
- pnpm exec tsx src/__tests__/ready-event-meta-sync.test.tsx
- pnpm typecheck
- pnpm test:typecheck
- pnpm lint:hooks
- pnpm test:transcript
- pnpm build
- go run ./tools/repolint
- git diff --check

* Fix protected history windows and catalog context races

Problem
Navigation could install a transcript before the active tab was pinned, allowing the resident cache budget to evict the just-opened window. Catalog callbacks could also reread the mutable App context while the lifecycle test replaced it, producing a data race.

Root cause
Active-tab pinning was deferred to a post-paint effect, and the restored catalog watcher used the App context accessor instead of its owning worker context.

Fix
Publish the active pin at the dispatch boundary before the first history page commits. Let the catalog watcher pass its existing context into historical discovery while retaining the default accessor for synchronous callers.

Verification
- tab-switch-hydration: 101 passed
- desktop TestConcurrentRebuildSessionCatalogCallersShareFailure with -race: 3 runs passed
- desktop TestHistorySliceSourceField/cold_index_hit: 100 runs passed
- pnpm typecheck and pnpm test:typecheck passed
- pnpm lint:hooks and pnpm test:transcript passed
- go run ./tools/repolint passed
- git diff --check passed

* Fix Windows history upgrade acceptance and test cleanup

Problem: Windows CI rejected visible upgraded history and could not remove a test source directory.

Root cause: the UI acceptance helper required an activation button even after lazy history had rendered, while the test closed its historical service after TempDir cleanup.

Fix: accept a healthy visible transcript without activation and close the test app services before removing the source directory. Keep pending and recovery-state rejection covered.

Verification: focused desktop Go test, go vet, repolint, golangci-lint, and diff checks passed. Windows PowerShell and installer checks remain for CI.

* Align history CI checks with lazy legacy loading

Problem: the Desktop CI matrix failed startup worker, cold index, and packaged upgrade checks after history loading changed.

Root cause: those checks still assumed startup discovery took the old coordinator lock, equal-timestamp sidecars were trustworthy, and opening a legacy tab automatically migrated it without rewriting JSONL. The current shutdown path snapshots an active legacy tab.

Fix: deterministically block the actual startup worker, establish sidecar ordering for the index-hit fixture, and verify legacy path, authored content, registry non-import, restart, and backups in the packaged fixture.

Verification: focused desktop tests with repeated and race runs, upgradefixture tests, go vet, repolint, golangci-lint, and diff checks passed. Windows packaged acceptance remains the terminal CI gate.
2026-09-23 11:54:15 +08:00
SivanCola a785237e60 fix(transcript): keep the user row ahead of its live output / 修复用户消息被本轮输出插到前面 (#10639)
* fix(transcript): keep a user row ahead of its live output

Problem:
Streamed reasoning, answer text, and tool calls could render above the
user message that started the turn. Later segments of the same turn
then continued below the bubble.

Root cause:
acceptBusiness copied a business record into the follower change before
filling an empty TurnID. Export and reload read the buffer, which had
the turn identity; the live change did not. Desktop attaches
already-mounted output to that turn only when the durable user row
carries the same turnId, so the output stayed on the previous formal
anchor or ahead of the prompt.

Fix:
Publish the assigned TurnID with the change record. Place live rows
after the submission anchor captured at send time when that user row
has no turnId, and move same-turn formal output back behind its user.
Keep a resident turnId when a projection update omits it, and stamp the
active turn onto a live answer that arrived before the identity did.

Verification:
go test ./internal/transcript/ -count=1 -run TestAcceptBusinessPublishesTheTurnIDItAssigns
desktop/frontend src/__tests__/submission-handoff.test.ts

* Preserve sampling order and the offscreen reader during transcript settlement

Problem: assigning turn identities could move later live answers ahead of
earlier samples and tools. Batched deltas could also clear the newer-history
flag while the reader remained on an older page.

Root cause: the user anchor overrode every existing formal predecessor, and
predecessor lookup ignored newly settled rows while retaining reclaimed ones.
The offscreen batch path leaked a temporary history flag into visible state.

Fix: preserve surviving and newly formal predecessors within the owning user
segment, and keep the reader's history flag while updating its offscreen tail.
Document both invariants and add six deterministic regressions. Five fail on
the previous PR head; all pass after the fix, including the ordinary-event
sibling of the batched stream path.

The production raw bundle measures 2102574 B versus 2100795 B on the base
(+1779 B, 0.085%). Set only its existing ratchet to 2053.5 KiB, preserving the
established 186 B Linux producer difference. Other budgets are unchanged.

Verification: transcript Go package; frontend test:typecheck; submission
handoff and output-order suites; test:transcript; submission and transcript
Chromium suites; production build and check:bundle; repolint; diff check.
2026-09-22 11:13:11 +08:00
SivanCola 355fa9fc12 Fix tool result identity, state handoff and historical projection (#10590)
Sparse completion events and later history hydration could erase durable tool identity, append duplicate result rows after the final answer, and downgrade completed state. Preserve canonical metadata, repair uniquely matchable legacy checkpoint identities, and reconcile bounded resident records through one conflict-aware projection with stable tool keys and anchored local notices.

Keep distinct conflicting formal results independently readable and prevent old session requests from updating a new binding. Preserve pagination, content-on-demand and existing resident-memory limits without changing provider payloads, prompts, session exports or the persistent message format.

Harden CI at the observed failure boundaries: carry frontend producer attempts into artifact consumers, attribute actual bounded Markdown Worker listeners, synchronize reconnect assertions with retained lifecycle publications, and use a fresh bounded functional wait for packaged upgrade history. Retain native geometry, visible history, restart integrity and independent startup gates.

Verification includes transcript/store/follower/reducer regressions, 240- and 1,000-turn browser scenarios, native Electron layout, backend and race suites, packaged Windows first-import/restart acceptance, and all three full memory-screening shards. Real remote-host validation remains unavailable; binding tests do not replace it. No release is published by this change.
2026-09-21 05:30:07 +08:00
SivanCola 65067d2bbd Fix compaction ownership, cancellation, and durable operation recovery
Problem: manual compaction could appear stuck, ignore Stop, lose its outcome after reload, or overlap other foreground work. Automatic recovery could install a projection after cancellation.

Root cause: maintenance bypassed shared runtime ownership and turn-independent persistence; client history and snapshots lacked consistent operation ordering.

Fix: centralize maintenance admission, cancellation, commit fencing, durable lifecycle records, and queue handoff. Reconcile versioned operation cards across desktop, remote, and CLI; classify empty history safely and preserve recovery details.

Verification: root and Desktop Go suites, deterministic maintenance regressions, focused Go race tests, frontend typechecks, transcript tests, production build, contract checks, and Chromium interaction/scroll benchmarks passed. Updated English and Chinese behavior documentation.
2026-09-21 02:44:15 +08:00
SivanCola 4756bc56ce Merge pull request #10550 from SivanCola/fix/transcript-snapshot-record-identity
fix(transcript): make snapshot record identity total / 修复快照记录身份缺失
2026-09-20 02:07:15 +08:00
SivanCola 6e1a45bd3d test(transcript): satisfy integer range lint
Problem:
The CI lint job rejected a fixed-count checkpoint regression loop under the Go 1.22 intrange rule.

Root cause:
The test used the pre-Go-1.22 three-clause form for a constant integer range.

Fix:
Use the native integer range syntax.

Verification:
- go test ./internal/transcript -run 'Test.*Checkpoint' -count=1
- golangci-lint run --timeout=5m
- git diff --check
2026-09-20 01:31:36 +08:00
SivanCola 7c91c2e88f fix(transcript): make snapshot record identity total
Problem:
Interrupted and outside-turn display frames could reach transcript snapshots without a record identity. Desktop then rejected the snapshot during hydration, and persisted checkpoints could reproduce the failure on every reopen.

Root cause:
The projection only derived identities from tool calls, message IDs, or positive business sequences. Zero-sequence display frames had no fallback identity, snapshot publication did not validate uniqueness, and Desktop mutated resident transcript state before all validation completed.

Fix:
Assign projection-scoped identities at the transcript owner, validate every frozen snapshot, repair legacy checkpoint rows, and normalize compatible old-peer snapshots. Stage Desktop store replacement until the reducer accepts the cut while preserving in-flight content ownership.

Verification:
- go test ./... -count=1
- go vet ./internal/transcript ./internal/control
- go run ./tools/repolint
- pnpm test:transcript
- pnpm build
- TypeScript production and test typechecks
2026-09-20 01:12:51 +08:00
SivanCola e7a5b149e3 feat(attachments): admit images at the session boundary
Problem: Composer and CLI image references could lose their bytes before the
provider request, leaving path text to fall through to tools and Bash.

Root cause: Image preparation was split across focus-sensitive Desktop RPCs,
temporary draft credentials, controller state, and request-time model routing
instead of one durable session admission boundary.

Fix: Admit ordered image batches before creating the user turn, freeze accepted
sources into session-owned image inputs, bind Desktop targets to session
identity, and preserve legacy provider serialization and CLI path behavior.

Verification: Focused attachment, control, provider, boot, serve, tool, Desktop,
and frontend suites passed on the latest main-v2 base. Frontend typecheck,
repolint, and git diff checks also passed.
2026-09-19 22:56:34 +08:00
SivanCola 2797455a1d Fix transcript initialization crash during migration
Problem: malformed legacy transcript baselines could crash the Desktop service while startup migration restored a nil projection.

Root cause: session runtime construction discarded NewProjection errors and continued into RestoreRuntime without a valid projection.

Fix: propagate typed initialization failures, release session ownership on every failed path, keep healthy migrations running, add content-free local diagnostics, and queue a deduplicated telemetry-gated exception for the crash dashboard.

Verification: session and transcript package suites, focused Desktop crash/migration tests, targeted race tests, crash worker tests, and worker typecheck pass.
2026-09-18 16:01:36 +08:00
SivanCola b1a4e016b3 Complete fixed-snapshot session exports / 完善固定快照会话导出 (#10471)
* Complete fixed-snapshot session exports

Problem:
Session exports could silently contain only the resident history window, omit tool payloads, and show unloaded tool results as stopped after workspace switches.

Root cause:
Export commands read renderer-owned items instead of an authoritative fixed session boundary, while tool execution state and content availability were inferred from the same paginated projection.

Fix:
Add fixed export snapshots, watermark-bounded persistence, complete forward history traversal, host-owned local and remote export operations, streamed visual output, reliable publication, cross-page tool observations, and expanded lifecycle diagnostics.

Verification:
Run root and Desktop test suites, focused race tests, repository and Go linters, frontend type checks and transcript suites, browser visual export probes, and native macOS save/cancel smoke tests.

* test(desktop): align remote export contract

Problem:
The desktop frontend CI still asserted that remote exports serialize the visible resident transcript.

Root cause:
The source-contract test predated fixed host-owned export snapshots and contradicted the new export boundary.

Fix:
Assert that remote exports pass an explicit session selector through runSessionExport to BeginSessionExportForTarget, and guard against restoring the resident-transcript Markdown path.

Verification:
- pnpm exec tsx src/__tests__/remote-project-tree.test.tsx
- pnpm test:typecheck
- pnpm test:transcript

* test(desktop): update export behavior contracts

Problem:
The complete frontend CI still encoded two pre-snapshot export assumptions: direct renderer imports from the command hook and missing tool results being treated as stopped.

Root cause:
The source and status contracts were not updated when export orchestration moved behind a lazy host-snapshot operation and tool execution state was separated from content availability.

Fix:
Verify the two-stage lazy export import path and require missing tool results to remain unknown without authoritative termination evidence.

Verification:
- pnpm exec tsx src/__tests__/bundle-contract.test.ts
- pnpm exec tsx src/__tests__/history-tool-status.test.ts
- pnpm test:typecheck
- REASONIX_TEST_CONCURRENCY=2 node frontend/scripts/run-ci-tests.mjs (452/452)
2026-09-18 12:00:00 +08:00
SivanCola 738e9b956e Unify transcript recovery and completion with Follow v2 / 统一会话同步与完成机制 (#10385)
* Unify transcript recovery and completion through runtime-owned v2

Problem: completed or reconnecting chats could lose visible output, infer incorrect run state, or display an intermediate duration.

Root cause: independent history, event-ledger and runtime snapshots described different cuts, and sampling settlement could precede its business commit.

Fix: publish canonical batches and independently versioned sampling frames from the session runtime; use one mandatory Follow v2 consumer for local and remote chat, persistence barriers, stable identities and bounded canonical paging. Preserve old session logs and provider serialization.

Verification: root and Desktop Go suites, transcript/session/control race tests, 371 frontend suites, typechecks, production build, Chromium and 42 macOS Electron layout scenarios; Windows cross-compilation. Native Windows verification follows on this branch.

* Keep contributor prompt instructions outside transcript migration

Problem: changing contributor AGENTS text affects repository instruction inputs. Root cause: protocol documentation had also been copied into the contributor instruction file. Fix: retain the existing instructions and keep migration details in the bilingual user documentation. Verification: cache and provider serialization guards passed; no runtime code changed.

* Separate transcript bookkeeping and honor native viewport geometry

Problem: transcript additions exceeded existing complexity and file-size budgets, and the Windows layout fixture assumed odd native widths were exact CSS widths. Root cause: runtime reduction, settlement publication and store contracts were embedded in larger owners; 200% Windows scaling rounded a requested 1009px surface to 1010px. Fix: extract cohesive helpers/types without changing publication order or provider bytes; test representable sizes on both sides of the launcher threshold and measure actual CSS geometry. Verification: repolint, transcript/type/hooks checks and focused Go tests pass; Windows native tests and race checks exercise the extracted boundaries.

* Migrate remote regression fixtures to transcript Follow

Route mock remote output through the mandatory Follow stream and keep metadata separate from snapshot cuts. Assert prefix preservation and ordered completion instead of legacy history rebases. Use unique mock sampling identities across turns and wait for pane transitions in native layout tests.

* Use reverse slice iteration for transcript retractions

* Give explicit transcript retries a fresh snapshot

A retry during ancillary hydration could join an already-installed old snapshot. Mark explicit retry reads as fresh so they supersede the previous load while ordinary startup reads still coalesce. Force a pending ancillary read in the regression and verify its late completion cannot overwrite the replacement.
2026-09-16 09:20:23 +08:00
SivanCola 658fe6eb06 feat(desktop): index the complete conversation in the turn navigator
Problem:
The turn navigator derived its entries from the mounted transcript window. A
long tool-heavy conversation fills the first body page with execution records,
so a window holding fewer than two user questions hid the rail entirely until
the reader pressed "load earlier messages" repeatedly. Switching away and back
rebuilt the window and could hide it again, loaded markers were renumbered from
1 because the ordinal came from the window, and a turn outside it could not be
reached at all.

Root cause:
Navigation had no source of truth for "which turns exist". The only index was
the loaded prefix, so completeness, identity and ordering were all coupled to
how much body a client had paged in.

Fix:
- internal/transcript publishes a bounded turn outline beside the body pages,
  bound to the same immutable snapshot, revision and event coverage. Entries
  carry the user record's stable RecordID, optional MessageID, the absolute
  turn ordinal, the record's order inside that snapshot, and two previews built
  from display bodies only: 50 grapheme clusters of the prompt and 120 of the
  last non-empty assistant body in that turn's group. Reasoning, tool output,
  submitted text and injected context never enter an entry.
- The index is built once per frozen cut, so repeated reads reuse one pass, body
  paging never shrinks it, and its previews count against the existing snapshot
  cache budget and lifetime.
- GET /transcript/outline serves it, advertised to remotes as
  transcript-outline-v1. TranscriptOutlineAPI is an optional capability beside
  TranscriptProjectionAPI, so an existing controller keeps compiling and an
  unsupported route answers 404/405/501 rather than an empty page.
- The frontend keeps one shared outline store for local and remote sessions,
  fenced by tab generation and snapshot identity. A recycled cut is reported as
  stale; the body is only replaced when the reader explicitly retries.
- The rail merges the complete outline with loaded turns, matching by MessageID
  first and stable RecordID second, so numbering stays absolute and loading an
  earlier page neither renumbers nor drops a mark.
- Selecting an unloaded turn starts a jump transaction that reuses the ordinary
  older-history paging one page at a time, waits for the progressive mount to
  advance, and writes the viewport only once the target node is really mounted.
  Reader intent, an explicit cancel, a newer target, or a session/snapshot
  replacement ends the pending transaction without taking scroll control back.
- Manual paging and navigation jumps share one queue: a page already in flight
  is awaited rather than submitted twice.

Verification:
- go test ./internal/transcript/ ./internal/control/ ./internal/serve/
- go test -race ./internal/transcript/
- cd desktop && go test ./...
- cd desktop/frontend && pnpm test:transcript && pnpm test:remote &&
  pnpm test:typecheck && pnpm build
- CHAT_BROWSER=chromium node bench/chat-transcript.mjs (240 and 1000 turns,
  zero errors; input P95 137.6ms, switch P95 38.2ms, longest task 218ms, heap
  growth 0.33MiB)

Compatibility:
Additive. No persisted session format, provider message, tool schema,
permission or prompt-cache byte changes.
2026-09-14 17:45:43 +08:00
SivanCola 4daa815be8 Refactor chat, permissions, and Windows sandbox around Harness / 以 Harness 重构聊天、权限与 Windows 沙箱 (#10209)
* feat: align chat and permissions with Harness

Replace the virtualized transcript and split approval/sandbox policy with a
natural-flow chat surface, stable node subscriptions, unified permission
presets, precise session grants, and platform sandbox enforcement.

Port the Harness-style process rows, turn footer, navigator, file presentation,
and full-access confirmation while preserving Reasonix history and provider
contracts. Add the Windows restricted-token sandbox and fail-closed capability
reporting, together with migration, remote compatibility, documentation, and
acceptance evidence.

* chore: refresh repolint baseline for one-shot refactor

Record the deliberate carry-forward from the transcript, remote presentation,
permission, and Windows sandbox changes. The refreshed baseline also captures
size and complexity reductions produced by the same extraction work.

* fix: align permission refactor with CI contracts

* fix: stabilize cross-platform permission CI

Problem:
The permission refactor left headless Windows tests on the workspace sandbox, exposed Windows-only lint findings, and let the transcript browser resolve Playwright assets from the wrong directory.

Root cause:
Tests used the interactive mode setter for a headless controller, direct Bash fixtures did not stamp their intended unrestricted preset, the transcript benchmark imported Playwright before normalizing its browser path, and Windows-tagged files had not been linted locally.

Fix:
Use the headless permission entry point, make Bash fixture intent explicit, normalize the transcript benchmark root before importing Playwright, and apply the Windows lint corrections without changing sandbox behavior.

Verification:
- go test ./...
- cross-platform golangci-lint for linux, darwin, and windows
- Windows amd64 cross-compilation for boot, builtin, and winsandbox tests
- transcript Chromium browser replay for 240 and 1,000 turns
- go run ./tools/repolint

* test: harden native transcript and shell contracts

Problem:
The Linux native-scrollbar regression clicked the bottom arrow instead of the scrollbar thumb, and direct shell-contract tests inherited the new workspace-write default on Windows hosts without a sandbox backend.

Root cause:
The browser test used a fixed bottom-edge coordinate, while shell fixtures invoked Bash outside the controller path and therefore had no host-stamped permission preset.

Fix:
Compute the native thumb center from scrollbar geometry, retain CI screenshots as artifacts, and mark direct shell execution fixtures with an explicit full-access test context while preserving restricted-sandbox tests.

Verification:
- go test ./internal/tool/builtin -count=1
- Windows amd64 cross-compilation for internal/tool/builtin
- GOOS=windows golangci-lint run --timeout=5m ./...
- Chromium transcript replay for 240 and 1,000 turns
- go run ./tools/repolint

* perf: bound cumulative transcript mounting

Problem: The 1,000-turn Linux browser replay exceeded the 500 ms long-task budget while cumulative history pages were mounted.

Root cause: Transcript subtree mutations repeatedly rescanned all loaded rows, and each 60-turn history page committed its full natural-flow DOM in one frame.

Fix: Observe direct chat-node additions only, retain per-row resize observation for internal content changes, enumerate direct row children, and reveal prepended history across bounded adjacent-frame commits without unmounting existing rows.

Verification: frontend typecheck; chat natural-flow and scroll-controller tests; Chromium transcript replay (1,000 turns: 122.7 ms input P95, 161 ms longest task, 0 px anchor drift); repolint clean.

* fix: keep transcript hydration synchronous

Problem: The progressive history mount delayed initial and replacement transcript content by one frame, breaking remote hydration visibility contracts.

Root cause: The bounded reveal path treated every order change like a history prepend, including an empty initial order and authoritative session replacement.

Fix: Restrict progressive rendering to a contiguous non-empty suffix with newly added leading nodes; hydrate, replace, and grow the tail synchronously.

Verification: remote-session-surface 101/101; frontend typecheck; chat natural-flow and scroll-controller tests; Chromium transcript replay within input and long-task budgets.

* fix: stabilize transcript and Windows CI

Problem:
Linux transcript hydration remapped every mounted node on each history page and exceeded the 500ms long-task budget. The Windows project-session migration test also raced asynchronous catalog startup.

Root cause:
The order subscriber owned the full React child map even while progressive hydration intentionally retained the previous order reference. The catalog test requested reconciliation before the catalog had been published.

Fix:
Memoize the node-seat mapping by rendered-order identity so only committed history chunks reconcile. Wait for catalog publication before requesting the production reconcile path, and move shared catalog polling helpers out of the oversized test file.

Verification:
- 1,000-turn Chromium transcript benchmark: max long task about 249ms
- frontend typecheck and hooks lint
- project CLI migration tests, 10 repetitions
- repolint and git diff checks

* perf: bound cumulative transcript layout

Problem:
Linux 1000-turn transcript replay still exceeded 500ms after seat memoization because each history page reconciled existing seats and the turn rail forced layout of every loaded mark.

Root cause:
Progressive prepend kept one flat React child list, unchanged turns were reprojected, and the navigator read scroll geometry and rendered all marks after every page.

Fix:
Keep immutable 24-node seat chunks, cache unchanged turn projections, isolate row layout, window rail marks while retaining full loaded-turn geometry, and make benchmark waits target exact mounted anchors without weakening thresholds.

Verification:
- Chromium 1000-turn: max 196ms, input P95 119ms, 2407ms full mount
- Full transcript test suite
- Frontend build, typecheck, lint and bundle checks
- Go migration tests 10x
- Repository lint and diff checks

* Serialize permission changes with approval commits

Problem: permission revisions could change while an older approval was still recording and installing its session grant, exposing a mixed snapshot and allowing cross-version authorization.

Root cause: mode changes, grant revocation, approval resolution, and permission snapshot reads used separate synchronization boundaries.

Fix: establish permissionMu -> promptResolveMu -> permissionStateMu ordering, publish preset/revision/grants atomically, cancel the captured old turn outside the state mutation, and cover both legal concurrent orderings with a deterministic barrier test.

Verification: go test -race ./internal/control (including five repeated targeted runs), Windows amd64 cross-compilation, and repository lint all pass.

* Preserve chat nodes across history prepends

Problem: loading older history repartitioned existing messages under different chunk parents, remounting their DOM and losing selection, focus, and local disclosure state.

Root cause: the rendered order was grouped into position-derived SeatChunk components and regrouped whenever a prefix crossed a 24-node boundary.

Fix: render every business node under one stable parent, progressively reveal only the new flat prefix, and publish the committed mounted order so navigation never advertises a missing target.

Verification: transcript unit tests cover 1/5/23/24/25/48/49-node boundaries and 60 resident DOM identities; Chromium, WebKit, and Electron layout and 240/1000-turn production replays pass.

* Clarify full-access host execution semantics

Problem: product copy implied that Reasonix filesystem and network sandbox guarantees continued to apply after selecting full access.

Root cause: permission labels were updated without consistently documenting Harness-style unsandboxed execution across the desktop, CLI, bot, Windows, and site surfaces.

Fix: state that full access runs with the current OS account, retains explicit pre-launch host denies, and does not constrain behavior inside the launched process.

Verification: desktop locale typecheck/build, documentation tests, site tests, and full-text consistency checks pass.

* Stabilize virtual rail navigation coverage

Problem: the Windows Electron layout check sometimes reached the test after the active turn had already scrolled the Harness-style virtual rail to its tail, leaving the early turn mark unmounted.

Fix: explicitly scroll the rail to its loaded start before the early keyboard jump, then to its loaded tail before the final jump.

Verification: Chromium reports 39 passing geometry scenarios and Electron reports 42.

* Make permission ordering test platform-neutral

Problem: the Windows control runner correctly reports restricted presets unavailable before its native sandbox is initialized, so the new concurrency test failed before reaching the transaction it was meant to exercise.

Fix: drive the same serialized Controller mode-change path directly; platform capability rejection remains covered separately at the public preset boundary.

Verification: the ordering suite passes ten times under the race detector and internal/control cross-compiles for Windows amd64.
2026-09-13 13:41:48 +08:00
SivanCola e36ef93a2f Simplify execution and completion semantics / 精简执行与完成语义 (#10198)
* Simplify execution and completion semantics

Problem:
Host-derived quality contracts, readiness gates, ordinary todo continuation,
and a separate Goal evaluator could prolong or block turns after the model had
finished, while mixing model claims with observed execution facts.

Root cause:
Execution safety, model completion judgment, and result presentation shared a
single quality-policy pipeline. Legacy delivery mode also propagated through
session, CLI, ACP, worktree, and recovery paths.

Fix:
Retire delivery mode and host quality obligations, keep Plan preapproval and
execution safety boundaries, drive Goal continuation from structured model
reports without an evaluator, and render facts separately from declarations.
Preserve legacy reads, explicit check recovery, historical evidence, and old
remote state without reactivating retired policy.

Verification:
- root and desktop Go suites
- focused root and desktop race suites
- frontend test:all and production build
- lint, generated contracts, cache guard, browser and native Electron checks

Cache impact:
One-time provider prefix change from retired tool discovery and updated tool
descriptions. Serialization and ordering remain stable within the version.

* Fix transcript history formatting

Problem:
The pull request's Linux and macOS test jobs stopped at the repository-wide
gofmt gate for internal/transcript/history.go.

Root cause:
A composite literal added during historical-check projection was not aligned
by gofmt before the initial push.

Fix:
Apply gofmt to the affected transcript history source.

Verification:
- gofmt -l . returns no files
- go test ./internal/transcript passes
- git diff --check passes

* Normalize subagent receipt paths for display

Problem:
The Windows isolated agent job rendered changed paths with backslashes while
the stable host-receipt contract and tests require slash-separated paths.

Root cause:
The simplified subagent completion path now exposes native receipt paths
directly, so Windows filepath normalization leaked into provider-visible
attestation text.

Fix:
Normalize only the rendered receipt and claim-violation path copies to forward
slashes. Keep native paths intact for write-scope enforcement and audit state.

Verification:
- focused host receipt and subagent completion tests pass
- deterministic backslash input test covers the platform-independent output
- repository gofmt and git diff checks pass

* Refresh desktop migration inventory

Problem:
The upstream CI refactor added one classified desktop CI job while this branch removes retired delivery surfaces, leaving the merged inventory count stale.

Fix:
Regenerate the desktop migration inventory with the current generator after merging main-v2.

Verification:
- go test -p 2 ./... -timeout 300s
- go test -p 2 ./... -timeout 300s (desktop)
- pnpm test:all && pnpm build
- make lint

* Wait for dock fixture hydration

Problem:
The dock browser benchmark could close Overview before the delayed bench session hydration completed. The authoritative hydrate then replaced the empty-state picker while Playwright was clicking Files.

Root cause:
The benchmark waited for the composer but not for the selected session snapshot, even though the fixture intentionally expands asynchronously.

Fix:
Wait for the fixture completion marker before mutating dock state.

Verification:
- node bench/dock-view-state.mjs (three consecutive runs)
- pnpm test:app-browser
- REASONIX_SETTINGS_BROWSERS=chromium pnpm test:settings-browser
2026-09-12 22:53:36 +08:00
SivanCola 27a45a260f fix(transcript): unify history snapshot and live event projection / 统一历史快照与实时事件的投影 (#9983)
* fix(transcript): unify history snapshot and live event projection

Problem:
Reasonix desktop 1.38.2 renders duplicated session content. When the
persisted history snapshot runs ahead of the live event stream, transcript
hydration appends already-rendered messages again ([A,B,C] + [A,B] ->
[A,B,C,A,B]), and events replayed after hydration can be dropped because
queued events are re-enqueued after the replay cursor already advanced.

Root cause:
History hydration dedupe only matched exact equality between the tail of
history and the head of the live stream. Message identity was not carried
through sampling events, the event merger, or persistence, so local,
remote, restore, cancel, and paging paths each maintained their own
projection logic with mismatched boundaries.

Fix:
- Thread message identity (Message.ID) through sampling events, the event
  merger, and persistence; identity stays out of model requests.
- Merge history and live streams by message ID through one projection
  shared by local Wails sessions and remote Serve sessions, with atomic
  snapshot boundaries (snapshot plus covered event sequence committed
  together) and receipt dedupe separated from commit.
- Controller now persists first, projects second, publishes events last.
- New shared Go package internal/transcript owns display record types and
  streaming coalescing; the snapshot API supports paging and 64KiB content
  chunking.
- Restore path installs a display checkpoint verified against the session
  summary; legacy Serve surfaces get a degradation notice.
- Extract shared modules (remoteStatus, historyItems, historyTypes,
  transcriptContentResolver, event extension surface, ledger metrics) and
  split oversized functions to satisfy repolint without baseline changes.

Verification:
- go run ./tools/repolint: clean, no baseline update.
- go test ./internal/{control,serve,event,turnevent,transcript,agent,store}/...: ok.
- go test -race ./internal/{control,turnevent,event,transcript}/...: ok.
- cd desktop && go test .: ok.
- frontend: tsc --noEmit, pnpm test:transcript (incl. transcript-store 68
  passed), transcript-snapshot-client, new-session-load-race (51 passed),
  turn-event-projection-reset: all pass.

* chore(frontend): ratchet locale/raw bundle budgets for transcript projection

The legacy-transcript degradation notice adds one short zh-TW string,
pushing the zh-TW locale chunk to 65551 B gzip (64.015 KiB) over the
64.0 KiB ceiling; zh stays at 64793 B under its existing 63.3 KiB
ceiling. Ratchet only the zh-TW ceiling one decimal to 64.1 KiB.

The unified transcript projection (snapshot client/state, content
resolver, extracted history-items and remote-status owners) adds a
measured 24.5 KiB raw on the initial path (2458.7 KiB total) while gzip
stays at 459.1 KiB inside the existing 469.3 KiB budget. Ratchet the
raw ceiling to 2458.8 KiB with 0.1 KiB of build headroom.

Verification: node scripts/check-bundle-budget.mjs passes all budgets
after a production build.

* style(transcript): satisfy golangci modernize and staticcheck receivers

CI lint flagged the new internal/transcript package: integer-range loops,
a copyloopvar in the nested-ref test, WaitGroup.Go modernization, and
inconsistent Buffer receiver names. Apply the mechanical modernizations
and unify Buffer methods on one receiver name; no behavior change.

Also point the clear-session identity contract test at historyTypes.ts,
where SessionClearResult moved during the shared-module extraction (the
types.ts re-export keeps the import surface unchanged).

Verification: golangci-lint 2.12.2 run ./... reports 0 issues in both the
root and desktop modules; go test ./internal/transcript/ ok;
clear-session-identity-contract test passes.

* fix(transcript): deduplicate active snapshot records

Problem: active records could be returned both in the page and activeRecords, causing the frontend to reject the snapshot; long-history snapshots also scanned settled turns under the projection mutex.\n\nFix: suppress duplicate active records, add frontend defensive deduplication, and bound active-owner discovery to the current turn when identity is available.\n\nVerification: go test ./internal/transcript; frontend tsc; transcript snapshot client and projection reset tests.

* fix(transcript): preserve recovery actions and bounded replay

Problem: installing an authoritative snapshot could hide recovery actions, strand long user messages at their preview, or stall remote replay on a legal oversized ledger page.

Root cause: terminal display metadata was omitted, content patches confused message identity with mounted keys, and replay used a different wire budget from its reader. The row renderer also omitted the recovery callback.

Fix: retain terminal recovery and read receipts, bind recovery actions through the production row renderer, match snapshot content by stable message identity while preserving mounted keys, and recover oversized replay through bounded snapshot content. Copy checkpoint metadata without serializing immutable bodies under the projection lock.

Verification: regression cases failed before these repairs and pass afterward. Related root Go packages, the full desktop module, targeted race suites, frontend transcript/remote/stream suites, typechecks, production build, repolint and go vet pass. Browser checks confirm full content and recovery command delivery; the native Electron smoke passes. A 32 MiB checkpoint capture benchmark improves from about 130 ms and 128 MB allocated to 11 ms and 14 MB allocated.

* chore(transcript): retain main-v2 bundle budgets after integration

Problem: the earlier transcript branch carried bundle ceiling increases measured before the Electron migration.

Root cause: those measurements no longer describe the integrated bundle.

Fix: restore the unchanged main-v2 ceilings and remove the obsolete measurement notes.

Verification: the integrated production bundle passes the original limits at 2396 KiB initial raw assets and 57 KiB for the Traditional Chinese locale.

* fix(transcript): satisfy current Go iteration lint rules

Problem: the integrated branch retained an integer loop flagged by intrange and introduced a backwards slice loop flagged by modernize.

Fix: use the equivalent integer range and slices.Backward forms without changing iteration order or recovery selection.

Verification: golangci-lint passes for affected root packages and the desktop module; focused control and transcript tests pass.

* docs(desktop): regenerate transcript host inventory

Problem: full root tests detected that the migration inventory still described 580 commands after the transcript APIs raised the host contract to 592.

Fix: regenerate both inventory representations from the integrated desktop sources, preserving the existing classification policy.

Verification: go test ./tools/desktopinventory passes, including the source-archive check without Git.

* fix(transcript): keep recovery tokens out of displayed prompts

Problem: connecting the recovery button through a plain prompt would show its internal command and token as the optimistic user message.

Fix: preserve separate display and submission text through local and remote transcript prompt ports. The visible action uses existing localized copy while the controller receives the unchanged recovery token.

Verification: production row-renderer assertions, local submission lifecycle, remote composer/reconciliation tests, frontend typechecks and production build pass. A real-browser button click reports localized display text and the correct token-bearing submission, with no console errors.

* test(desktop): share browser asset loader in discovered suites

Problem: the full discovered frontend run failed only at topicbar-region because its transitive SVG import reached Node without an asset loader.

Root cause: dedicated suites used the existing SVG/image/CSS loader while the discovery runner registered only the CSS loader.

Fix: use the same existing asset loader for all discovered component suites; no test is excluded and no product rendering changes.

Verification: the previously failing topicbar suite passes through that loader; the final full discovery run remains enabled.

* test(desktop): advertise only callable host fixture methods

Problem: the remote runtime browser replay timed out after generated transcript methods were added to a fallback table with undefined implementations.

Root cause: the test preload advertised every object key as a callable method, falsely enabling snapshot negotiation and preventing history hydration.

Fix: derive the stub contract from callable table entries, preserving its live update behavior. Add a regression for adding and removing optional snapshot support.

Verification: the new stub test, session-load race tests, frontend typecheck and the complete app-browser chain pass. The original runtime-state browser reproduction now restores durable history after lost completion and switches back to the local session without errors.
2026-09-11 16:13:17 +08:00