Files
DeepSeek-Reasonix/desktop/session_errors.go
T
SivanCola b17c1b5c3b Restore ProjectTree and integrate session recovery fixes / 恢复项目树并整合会话恢复修复 (#10380)
* fix(session): keep history index checkpoints on complete batches

Bound history and search scans to their captured log snapshot and persist
the byte offset and sequence from the same validated complete transaction.
Reject replaced history logs before publishing. Rebuild a mismatched derived
history checkpoint from the authoritative log, preserving real corruption
errors instead of discarding session data.

Treat history preparation as pending during startup and pagination. Stop
polling after navigation or generation changes and keep lifecycle callbacks
out of IPC payloads.

Add deterministic regressions for append interleavings, persisted mismatched
checkpoints, partial tails, replacement, corruption and preparation lifecycle.
Verified a disposable copy of the reported session recovers 32 messages.

Frontend initial raw bundle: 2498286 B at base to 2499170 B (+884 B, 0.035%).
Raise only its budget from 2439.9 to 2440.7 KiB with 0.1 KiB headroom; gzip,
CSS and individual chunk budgets remain unchanged.

* fix(desktop): bind session navigation to its canonical workspace

* test(session): model replaced readers without renaming open files

* fix(desktop): preserve active sessions across navigation

Transfer active controllers, event sinks, writer leases and shared-host references to detached ownership when navigating. Reattach the exact owner on return and replay pending prompts. Build independent runtimes for blank targets, isolate display and telemetry, and fence retired sinks. Cover running, pending approval, background jobs, shared hosts, failed opens, rapid navigation and repeated active-owner swaps.

* fix(session): derive catalog previews from authored user text

Problem: Session lists displayed truncated internal prompt wrappers instead of user requests.

Root cause: Shared message previews preferred provider content and truncated it before display extraction; persistent projections retained those previews.

Fix: Reuse authored-message extraction before truncation, exclude host messages and steers from catalog labels, and rebuild older catalog, history, search, and recovery projections. Preserve custom titles and durable conversation content.

Verification: Session package tests, authored-text and legacy-checkpoint regressions, root Go suite excluding the subsequently repaired stale inventory gate, and desktop workspace-list tests passed.

* fix(desktop): restore compact workspace navigation and recovery access

Problem: The SessionID sidebar lost compact rows, project ordering, and its bottom Trash entry; archive access occupied a separate sidebar row.

Root cause: The replacement workspace browser omitted existing presentation and navigation behaviors.

Fix: Restore single-line titles and folder names with full-name tooltips, durable drag ordering with insertion feedback, and the bottom Trash entry. Combine archived and deleted sessions in that page while preserving their distinct actions. Share archive notification behavior across browser fixtures and isolate workspace styles with their component.

Verification: Browser checks cover light and dark themes, narrow layouts, drag ordering, failed saves, archive/restore and deleted-session navigation. Workspace persistence tests, Trash management regressions, typecheck, frontend production build and repository lint passed.

* docs(desktop): refresh generated migration inventory

Synchronize generated source locations after the session navigation changes. The inventory content and classification remain unchanged.

Verification: go test ./tools/desktopinventory -count=1 passed.

* fix(desktop): route sidebar sessions through navigation owner

Problem: Sidebar session clicks can change the backend session while leaving the previous transcript visible.

Root cause: Direct OpenSession and CreateSession calls bypass the frontend navigation owner. Ready events for a different active tab are correctly ignored as background events, leaving the visible surface stale.

Fix: Route canonical session opening and blank-session creation through the shared navigation queue, synchronize the active surface after a successful open, and preserve last-intent-wins behavior. Load the navigation executor on demand to retain the existing startup bundle budget.

Verification: Frontend build, type and hook checks, bundle budgets, repository lint, navigation lifecycle tests, browser lifecycle checks, and packaged macOS sidebar and startup smoke tests passed. The packaged sidebar regression reproduces the stale transcript on the prior build and passes with this source tree.

* fix(desktop): render authored chat text across history readers

Problem: Chat history exposes host context and prompt wrappers as user bubbles.

Root cause: The canonical message adapter renders provider content without respecting message origin or authored raw text; reference-only previews can retain the same internal content.

Fix: Centralize authored-text projection for inline and hydrated messages, hide host messages, unwrap legacy protocol content, and rebuild derived preview caches. Keep durable messages unchanged and load SVG preview styles with their component.

Verification: Root Go suite, owning session regressions, transcript tests, browser transcript and authored-text scenarios, type checks, frontend build and bundle budgets, and repository lint passed.

* test(desktop): verify history errors through shared reader

Problem: CI checks for slice error handling in a file that no longer owns it.

Root cause: History preparation moved fetching and failure propagation into the shared reader while the source-only assertion retained the old location.

Fix: Assert the store delegates to the shared reader and verify that reader rejects an actual error response.

Verification: History failure and preparation lifecycle tests passed.

* fix(shell): preserve non-ASCII commands across interactive PTYs

Problem: Desktop launches without a UTF-8 locale can hang on Chinese
commands until the persistent Bash deadline expires.

Root cause: Readline interprets raw high bytes as meta input before Bash
parses the command wrapper. Variable-width control escapes can also absorb
following octal digits.

Fix: Encode non-ASCII and control bytes as fixed-width octal escapes in
the shared ANSI-C quoting boundary. Preserve the original command bytes,
user locale, and persistent shell state.

Verification: Reproduced the pre-fix timeout; passed locale, Unicode path,
grep, emoji, quoting, heredoc, long-input and cancellation/recovery PTY
regressions, Bash/Zsh byte round trips, public Bash tool/schema tests,
the persistent-shell race suite, and targeted Go vet.

* fix(runtime): preserve UTF-8 across terminal and output boundaries

Problem: Integrated Bash still corrupts non-ASCII keystrokes under C locale,
tool progress can split characters into invalid JSON text, and GUI-launched
children without a locale use unintended byte-oriented text semantics.

Root cause: Terminal and subprocess environments lacked explicit text
defaults, while progress and bounded output sliced arbitrary byte offsets.

Fix: Select an installed UTF-8 locale only for unconfigured child environments.
Preserve user inputrc settings while enabling eight-bit Bash terminal input.
Buffer incomplete progress characters through EOF/cancellation and align
bounded output slices without raising byte limits. Preserve the head on the
first oversized write and document the shared contracts bilingually.

Verification: Native Bash PTY tests cover missing/C/UTF-8 locales and retained
editing preferences. JSON round-trip, byte-cap, head/tail, cancellation, locale
priority, sed character semantics, tool schema and focused race tests pass.
Targeted Go vet and documentation/cache metadata guards pass.

* fix(locale): use the shared background process constructor

Problem: The desktop process-constructor guard rejected the new locale probe.
Root cause: The helper called os/exec directly instead of the repository's
shared process entry point.
Fix: Route the bounded locale discovery through internal/proc while retaining
its explicit environment and deadline.
Verification: The desktop constructor guard, native Unicode terminal tests,
locale/secrets package tests and targeted Go vet pass.

* docs(desktop): refresh terminal migration inventory locations

Problem: The generated desktop inventory was stale after the terminal edit.
Root cause: Source line locations changed when locale defaults were wired in.
Fix: Regenerate both inventory artifacts with their owning generator.
Verification: go test ./tools/desktopinventory passes, including the no-Git
reproducibility check.

* fix(session): restore cold history without clicks and bound catalog rebuild memory

Problem: Cold historical sessions could remain loading or appear as empty new
sessions until another navigation action refreshed them.

Root cause: Busy rebuild slots dropped metadata work, pending metadata was
treated as blank, and completion did not refresh the sidebar. Each workspace
also scanned the entire catalog, while metadata replay retained full bodies.

Fix: Queue deduplicated metadata work with two workers, preserve pending titles,
refresh only while metadata is pending, and prepare obsolete history indexes
asynchronously. Read workspace members directly and stream catalog reduction
while retaining only IDs, short previews, and required metadata.

Verification: Root Go suite, Desktop workspace/history tests, targeted race
tests, Windows amd64 session suite, sidebar and transcript browser tests,
frontend build and typechecks, and repository lint passed. Repeated 512 MiB
synthetic history reductions passed retained-heap and reclamation checks.

* fix(windows): disable restricted Bash and validate native shell startup

Problem: Windows Git Bash can fail during MSYS initialization before even a
simple command executes, and repeated startup attempts hide the useful error.

Root cause: host discovery does not validate the actual sandbox environment;
automatic selection favored MSYS, persistent startup discarded its diagnostics,
and interactive shells used a different Windows sandbox lane than one-shot shells.

Fix: use only native PowerShell for Windows auto selection, reject explicit
POSIX shells in restricted execution, and retain explicit Bash in full-access
mode. Preflight shells and child processes under the actual policy, retain
bounded startup evidence, suppress repeated failed starts, and align interactive
shell sandbox selection with one-shot execution. Preserve sandbox enforcement.

Verification: full root Go suite, owning-package tests, race tests, go vet,
Desktop Shell/Terminal tests and frontend TypeScript checks passed on the local
fix branch. Windows test binaries cross-compiled. Added native Windows policy
and write-boundary acceptance tests; real Windows execution remains pending.
This change does not claim to repair MSYS restricted-token compatibility.

* Restore the v1.38.6 project-tree sidebar and drop the creation layout / 恢复 v1.38.6 项目树侧边栏并移除创作布局

Problem: The SessionID cutover replaced the workbench sidebar session list
with the workspace-browser grouping (archived toggle, per-workspace rows),
which lost the v1.38.6 project-tree UI and interactions.

Fix: Render the ProjectTree in the workbench sidebar again (variant
workbench, trash entry restored) while keeping the current SessionID-era
bridge underneath. Delete the creation layout end to end — sidebar feature
zone, creation geometry clamps, dock/panel creation modes, the composer
context-ring gate and the desktopLayoutStyle preference chain — so the
desktop ships the workbench layout only, and remove the "Desktop style"
settings row with its locale copy and mocks. WorkspaceSessionBrowser and
its CSS/test/locale keys are removed with it.

The Go config field and host bindings stay untouched for config
compatibility; the frontend simply no longer reads or writes them.

Verification: pnpm typecheck; pnpm build (lint/check gates + bundle
budgets); node scripts/run-tests.mjs --keep-going (368/369; the single
topicbar-actions-lifecycle failure reproduces identically on the pristine
base commit under suite load and passes standalone).

问题:SessionID 切换把工作台侧边栏的会话列表换成了 workspace-browser
分组(已归档开关、按工作区行展示),丢失了 v1.38.6 的项目树 UI 与交互。

修复:工作台侧边栏重新渲染 ProjectTree(workbench 变体,恢复回收站入口),
底层继续使用当前 SessionID 架构的 bridge。端到端删除创作布局 —— 侧边栏
功能区、创作几何钳制、dock/面板 creation 模式、composer 上下文环开关以及
desktopLayoutStyle 偏好链路 —— 桌面端固定只提供工作台布局,并移除设置中的
「桌面风格」行及其文案与 mock。WorkspaceSessionBrowser 及其 CSS/测试/文案
一并删除。

Go 侧配置字段与宿主绑定保持不变以兼容既有配置,前端不再读写。

验证:pnpm typecheck;pnpm build(lint/check 门禁 + bundle 预算);
node scripts/run-tests.mjs --keep-going(368/369;唯一失败的
topicbar-actions-lifecycle 在未改动的基线提交上同样复现,单测独立通过)。

* fix(desktop): keep running sessions switchable after archive and project change / 归档与跨项目切换时保留正在运行的会话

Problem: Archiving a session, or creating a new session in another project
and then switching back to a running session, froze the Desktop UI.

Root cause: GetRuntimeStateSnapshot held its projection mutex across
controller reads, so archive emission deadlocked with a live turn.
keepOnlyVisibleTab snapshotted running sessions before detaching them.
ActivateTopic and OpenSession looked up detached runtimes by file path
only, so they built a competing controller instead of reattaching the
session:<id> owner.

Fix: Sample runtime projection off the projection mutex. Detach running
sessions without snapshotting. Look up live runtimes by session identity
and unique topic, and reattach that owner on ActivateTopic/OpenSession.
Prove OpenSession identity with Stat instead of a full live history
snapshot.

Verification: go vet .; go test -count=1 -run
TestRuntimeStateProjectionDoesNotHoldMutexAcrossControllerRead|TestPruneRunningSessionDoesNotSnapshot|TestActivateTopicReattachesDetachedRunningSession|TestBlankOnOtherProjectThenOpenSessionReattaches

* refactor(shell): keep launch validation within repository lint limits

Problem: The integrated shell fixes exceed function-size limits and introduce
small file-size and comment-budget regressions detected by pre-push repolint.

Root cause: Launch preflight and result classification expanded existing
execution functions; UTF-8 and Windows probe wiring added three source lines
across two already-baselined files.

Fix: Extract Bash launch validation and foreground result classification while
preserving execution order, environment selection, failure metadata, and lease
ownership. Shorten the octal-escape comment without losing its invariant.
Adjust only terminal.go's measured file-size baseline by two lines and
controller.go's by one; retain function-size and global ceilings.

Verification: Repository lint, Go vet for the three affected shell packages,
their full package tests, and git diff --check pass.

* fix(shell): acknowledge bounded PTY input before executing long commands

Problem: Linux CI exposed silent truncation of long Unicode commands, while
root/Desktop lint and Windows fixtures rejected the integrated branch.

Root cause: ASCII escaping expanded a command beyond canonical PTY input
limits. Readline mode changes also made queued multiline input unreliable.
An interrupted staging attempt could fall back to one-shot execution because
the user command had not started. Windows fixtures still expected restricted
Bash execution and an auto-selected Bash cache contract.

Fix: Stage bounded ASCII blocks with per-block acknowledgements, then eval the
assembled value in the same shell. Keep partial commands unexecuted on failure
and prevent fallback after the persistent runtime resets. Align Windows policy
assertions with native shell selection and pin the existing Bash golden fixture
without changing its artifacts. Resolve root/Desktop lint findings using range,
slices.Contains, and strings.CutPrefix without weakening checks.

Verification: Long-line contract regression failed before the transport fix;
the fallback regression failed before its guard. Five repeated Unicode/native
PTY runs, full persistent-shell/builtin/sandbox suites, focused race tests,
boot goldens, navigation/terminal regressions, root/Desktop lint, repository
lint, inventory freshness, and whitespace checks pass locally. Native Linux
and Windows acceptance is delegated to the replacement CI run.

Cache-impact: No new provider-visible changes; the golden fixture explicitly
selects Bash while existing Windows auto-selection changes remain documented.

* test(shell): select canonical input mode before Bash starts reading

Problem: The Linux canonical-input regression exited during setup before
submitting its long command.

Root cause: Switching off Readline inside eval with stdin redirected to
/dev/null changes Bash's input reader during a temporary descriptor binding.
The fixture unintentionally tested that transition instead of canonical input.

Fix: Launch the fixture with --noediting so its input mode is established
before setup. Retain long Unicode input, shell-state, and output assertions.

Verification: The focused native PTY regression passed five consecutive runs;
package lint, repository lint, and whitespace checks passed. Native Linux
acceptance is checked by the replacement CI run.

* fix(ci): isolate native Windows desktop test groups

Problem: Native Windows desktop checks exhausted a shared 25-minute step deadline before the final group completed.

Root cause: The first two sequential groups consumed about 19 minutes 42 seconds including build overhead; the first group itself approached the default package timeout.

Fix: Partition the suite across four independent Windows runners. Validate current native inventory for unique coverage, preserve the required aggregate check and existing deadlines, and retain the separate ConPTY probe and systray test once.

Verification: All 17 partition/workflow tests, actionlint, release workflow contracts, repository lint, native inventory validation, and whitespace checks passed.

* fix(ci): classify Windows shards and remove duplicate full sweep

Problem: The new native Windows matrix was missing from the desktop migration inventory. Non-PR Windows runs also executed the entire root module before executing the same coverage through partitioned jobs.

Root cause: The explicit migration classifier was not updated with the new job, and the generic full-suite condition included Windows despite its existing disjoint selectors.

Fix: Classify the matrix and its aggregate, regenerate both inventory artifacts, and restrict the generic full sweep to Linux and non-PR macOS runs. Windows retains full plus agent, boot, and control groups.

Verification: Full root Go tests, 18 partition/workflow contracts, inventory freshness, actionlint, repository lint, and whitespace checks passed. The unchanged protocol-recovery test also passed ten consecutive native local runs.

* fix(ci): let superseded Windows workers cancel

Problem: Superseded native Windows desktop groups kept their concurrency slot and blocked the next PR run.

Root cause: always() kept the costly matrix workers running even after workflow cancellation.

Fix: Gate workers with !cancelled() while retaining preparation checks and the always-running result aggregate.

Verification: The cancellation regression, all 18 workflow/partition tests, actionlint, inventory freshness, repository lint, and whitespace checks passed.

* test(ci): verify macOS routing by behavior

Problem: The macOS workflow contract rejected the corrected Windows deduplication condition even though macOS routing was unchanged.

Root cause: The assertion matched the obsolete expression text instead of evaluating platform and event behavior.

Fix: Check actual macOS PR, push, manual-run, and disabled-step conditions for the full and platform suites.

Verification: The exact complete release workflow contracts CI step passed locally, along with repository lint and whitespace checks.
2026-09-16 07:06:32 +08:00

67 lines
2.5 KiB
Go

package main
import (
"errors"
"log/slog"
"syscall"
"reasonix/internal/agent"
)
// Sanitized errors surfaced when a destructive or restore session operation
// hits a real filesystem blocker. Like errSessionBusyElsewhere, they
// intentionally carry no path, so raw OS error text never reaches the UI.
var (
errSessionFileLocked = errors.New("a session file is temporarily locked by another program (often antivirus or sync tools) — wait a moment and retry")
errSessionFileAccessDenied = errors.New("access to a session file was denied — close programs that may be using it or check folder permissions, then retry")
errSessionDiskFull = errors.New("not enough disk space to finish the operation — free some space and retry")
errSessionHistoryUnreadable = errors.New("session history could not be loaded safely; the session files were left unchanged")
)
// friendlySessionLoadError keeps decoder details and local paths in logs while
// preserving the already path-free replay-budget error for the startup banner.
// A failed authoritative event log must never be hidden by opening an empty
// session or falling back to an older checkpoint.
func friendlySessionLoadError(err error) error {
if err == nil || errors.Is(err, agent.ErrSessionReplayLimitExceeded) || errors.Is(err, errSessionWorkspaceConflict) {
return err
}
if friendly := friendlySessionFileError(err); !errors.Is(friendly, err) {
return friendly
}
slog.Warn("desktop: session history load failed", "err", err)
return errSessionHistoryUnreadable
}
// friendlySessionFileError rewrites raw OS-level filesystem errors from the
// session trash/restore/purge flows (e.g. a Windows sharing violation while a
// scanner holds a transcript) into the actionable, path-free errors above.
// The original error is logged so diagnostics keep the path and errno.
// Unrecognized errors — including already-sanitized ones like
// errSessionBusyElsewhere — pass through unchanged.
func friendlySessionFileError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, agent.ErrSessionFileLockHeld) {
return errSessionBusyElsewhere
}
var errno syscall.Errno
if !errors.As(err, &errno) {
return err
}
var friendly error
switch {
case isFileInUseErrno(errno):
friendly = errSessionFileLocked
case isAccessDeniedErrno(errno):
friendly = errSessionFileAccessDenied
case isDiskFullErrno(errno):
friendly = errSessionDiskFull
default:
return err
}
slog.Warn("desktop: session file operation blocked", "err", err)
return friendly
}