95 Commits
Author SHA1 Message Date
esengine 8c785fa4b0 refactor(cli,config): remove --fail-on-unverified and agent.host_verification
Both only steered the final-readiness gate, which no longer exists: the flag
chose exit 3 for an unmet readiness verdict and the setting chose whether a
changed turn was held for a check, a review and a sign-off. Neither can do
anything now.

`run` still exits 4 when an untrusted folder refused its edits. A user config
that still carries host_verification loads unchanged; the key is ignored.
Removes the HoldOwedChecks and WaiveOwedChecks plumbing behind them.
2026-11-02 05:27:28 -08:00
Will 4fefec819c fix(sessionstore): an unprovable lease is its own answer
SessionLeaseHeldByOtherRuntime folded every lease-lock probe failure into
"held", so a lock it could not open became the same answer as a live holder.
Redaction then skipped a session nobody held and reported an active lease,
leaving the secret scrub silently incomplete; session and task listings
claimed active/running on the same non-evidence.

Add ErrSessionLeaseUnprovable beside ErrSessionLeaseHeld and expose
SessionLeaseOwnership as the three-state probe. Only ErrSessionLeaseHeld
proves a holder; any other lock failure is unprovable with its cause kept.
The bool wrappers keep their old "may be busy" answers, so callers that only
skip work are unchanged.

Redaction refuses an unprovable session, reports it as a skipped file with
the reason (and exits non-zero), and never calls it an active lease. Machine
session state and running task/subagent status become unknown when ownership
cannot be proven; held and free keep their current answers.
2026-10-28 13:56:02 +08:00
Lancy cfb483ae11 feat(config): support nested project config fallback
Resolve .reasonix/reasonix.toml only when the root project config is absent. Share selection across loading, local edits, installs and repair; retain project-scope restrictions and reject symlink escapes.

Fixes #6007
2026-10-19 11:36:17 +08:00
esengine b5a79cda33 feat(tui): preview each theme as the /theme picker moves
A bare /theme opens the quick picker; moving repaints, Enter keeps and stores the
row, Escape restores the original. /theme list keeps the old listing.
2026-10-13 19:29:07 -07:00
xxoingr 34c84f7a73 fix(tui): /resume <n> continues the nth saved session in place
In 1.x, "/resume <n>" switches straight to the nth saved session. The Studio
TUI ignored the number and always opened the picker, the known gap left in
the CLI migration tracking issue.

An indexed /resume now resolves the session by its row in the same list the
picker numbers and resumes it, with the same refusals the picker gives: a
running turn, the session already open, or an index outside the list. A bare
/resume (or a non-numeric argument) still opens the picker.

The index lookup shared by /takeover <n> and /rename <n> <title> moves into
one helper so the three commands cannot drift apart.

Refs #11493
2026-10-06 00:35:42 +08:00
esengine 7f453a62f3 docs(migrating): match the verified 2.x CLI behaviour for 1.x users
Correct the YOLO shortcut, the --inline form and the unknown slash command row, and list the permission, slash command and compaction differences.
2026-10-04 07:42:42 -07:00
esengine 2719b42f8b fix(run): exit 4 when an untrusted folder refused the work, and write the 1.x migration notes
A headless run in a folder with no trust decision refused every edit and
command and exited 0, so scripts that relied on 1.x defaults looked
successful. It now exits 4 (3 under --fail-on-unverified); the gate and
what it allows are unchanged. docs/MIGRATING.md gains the 1.x differences:
trust prompt, untrusted-folder runs, compact-ratio range, env vars.

Refs #11493
2026-10-04 03:35:56 -07:00
esengine 9692e499bb fix(cli): validate compact-ratio against the config layer bounds 2026-10-03 03:20:23 -07:00
esengine db0c478900 feat(tui): put YOLO on the Shift+Tab cycle and enable it with one Ctrl+Y
The one-time confirmation gate is gone: YOLO is always a stop on the cycle and a
single Ctrl+Y enters it, as in the 1.x terminal UI. --yolo on the command line
keeps its one-time notice, and the sandbox still applies in every mode.
2026-10-02 01:10:30 -07:00
Stephen R. van den Berg e8c7feeeef fix(cli): colour a streaming diff fence hunk by hunk
A ```diff fence rendered through the colourised built-in path on every
frame, and a colourised row costs ~1.4 ms to lay out (chroma via
regexp2) against ~1.3 µs for a plain one. While a fence streams every
delta re-renders the whole growing body, so a multifile diff pegged a
core and the display crawled. Changes:

- Each "@@ " hunk header settles the rows before it, so render that
  prefix and leave only the in-progress hunk on the plain rail: the diff
  colours in as it streams instead of only at the closing fence. A
  closed fence settles the whole body. The formatter runs on each
  settled prefix too, so its output appears hunk by hunk.

- Keep the already-formatted rows on screen while a settled body's run
  is pending: growing the body by one hunk changed its key and redrew
  the whole section on the plain rail until the new run landed, so the
  delta output flashed back to plain at every hunk. The renderer now
  draws the largest settled prefix whose result has landed and leaves
  only the newly settled remainder (and the in-progress hunk) on the
  rail.

- Memoize the colourised rows per hunk (hunk, path, width, rail, theme).
  A completed hunk never changes, so it is highlighted once however many
  frames redraw it; caching the whole body instead would re-highlight
  every earlier hunk at each "@@ " and is quadratic in the hunk count
  (measured: 60 hunks, 79 KB took 40 s with a body memo, 2 s per-hunk).
  The theme is in the key so a runtime theme switch cannot serve the old
  palette's colours.

- A fence still with fewer than two hunks keeps the plain rail, and a
  closed fence whose configured [cli].diff_formatter result is still
  pending also draws the rail, since that output supersedes the built-in
  rows anyway; a landed-but-failed run falls back to the built-in.

- Hold the transcript viewport still when the content shrinks: a settled
  diff collapsing its raw preamble to one formatted line frees rows at
  the bottom, and following them back up made the screen jump. The
  position now holds, the freed rows stay blank, and later rows fill
  them, so scrolling resumes only when the output actually grows past
  what is shown. The hold has only a lower bound, so a shrink larger
  than the viewport would hold the position past the content and blank
  the whole transcript — a collapsing multifile fence, or /clear
  restarting over a shorter one — so the view re-anchors to its tail
  instead. A manual scroll or resize releases the hold.

- Coalesce the stream frames the TUI hands the model: waitUpdate drains
  everything already queued into one updateMsg, so a burst of deltas
  costs one render instead of one per delta. Order is kept and a Gap
  still triggers a history reload.

- Fix the file-header misdetection: a removed SQL "-- x" line renders
  "--- x", which trimDiffPreamble/diffSectionStart mistook for a
  "--- "/"+++ " file header, dropping the "@@" hunk header (line numbers
  reset to 0) or the rows themselves when a removed "--- x" sat before
  an added "+++ y". A pair is a file header only when a "@@ " hunk
  follows it, and countDiff no longer skips "--- "/"+++ " rows.

- Keep a streaming multi-file diff's next file header on the plain rail:
  splitAtLastHunk settled everything before the last "@@ " header, which
  included the next file's "--- "/"+++ " pair (and its git preamble) once
  that file's first hunk header had arrived but no hunk of it had
  completed. isFileHeaderPair then saw no "@@ " after the pair, so the
  settled renderer read it as a removed/added row of the previous file's
  last hunk and counted it in that file's stat. A trailing dangling file
  header now moves from the settled part into the tail until its "@@ "
  arrives.

Tests: the multi-file cases TestSplitAtLastHunk was missing and an
end-to-end streamed test; a theme-switch test pinning the per-hunk memo's
theme key; and a benchmark replaying a 4-file x 15-hunk stream, redrawn
per streamed line.

fix(cli): highlight a streaming diff fence's hunk once

splitDiffSections appends a newline to every section, so splitHunks
returns a section's last hunk with a trailing newline and every earlier
hunk without. hunkRowsCached keyed the memo on the raw hunk, so one hunk
carried two keys over a stream: it was highlighted once while it was its
section's last hunk and again once a following hunk arrived. Every hunk
but a file's last was therefore highlighted twice, at ~1.4 ms a row.

Trim the trailing newline before keying. diffBody already trims, so the
rendered rows are unchanged; the streaming benchmark over a 4-file,
30-hunk, 1816-line diff drops from 7.8 s to 5.6 s.

TestStreamingDiffHunkMemoisedOnce pins one memo entry per hunk: it sees
three entries for two hunks before this change, two after.

Fold the writer-tool preview before highlighting it too. diffBody laid
out every row and then dropped all but maxLines, so a write_file card's
~24-row preview colourised the whole diff: ~772 ms per repaint for a
199-line file, paid on every message while the call runs. Counting the
fold from the source and laying out only the kept rows prices a
2000-line diff like a 24-line one.

BenchmarkDiffBlockPreview prices both shapes through a diffPreviewFold
seam, which TestDiffBodyFoldSeamSameRows proves draws the same rows: a
2000-line preview is 6.3 s and 86 MB with the whole body laid out, 71 ms
and 1.0 MB folded, and flat from 24 lines up.

docs(cli): correct diff-fence comment coherence

Four comments described behaviour the code does not have:

- diffRowCount's doc counted one row per non-blank line, but diffBody draws no
  row for a "@@ " header, so it over-counted by the hunk count.
- renderCodeRail's doc claimed the in-progress hunk and the pending-formatter
  placeholder, both drawn by renderRailText, not here.
- splitDiffSections' doc named only the fence path, omitting DiffText, and
  described the plain-rail fallback where DiffText drops a headerless section.
- the file header claimed md.go still sits at the 800-line ceiling.

Reword each to the constraint that holds now.
2026-10-01 11:01:45 +02:00
esengine 8fa67e544e fix(run): say why a headless run in an untrusted folder did nothing
A run in a folder with no trust decision refused every edit and command
under the asking default and exited 0 with an empty stderr (plain text
run) and only the generic 'no interactive approver' reason for the model.

The refusal now carries permission.untrusted_folder. The model reads the
cause and the remedy, the text run prints refused tools, code and remedy on
stderr, the structured result's denial carries a remedy, and
--fail-on-unverified exits 3 for it. Exit 0 and the trust gate itself are
unchanged.

Refs #11493
2026-09-30 18:55:48 -07:00
YHH fea401fa6f Merge pull request #11359 from BuGlessRB/open/diff-fence-scoped-notify
fix(cli): stop the diff formatter repainting every block when a run lands
2026-09-29 20:02:14 -07:00
Will 89962c4e98 docs(cli): list recall-search in the --ablate module list
ablation.Modules() has seven modules; the sentence documenting --ablate stops at
upstream. #11360 edited the same sentence to drop full-fold but left this one.
2026-09-30 10:30:03 +08:00
Stephen R. van den Berg 90cf3f3f1f fix(cli): stop the diff formatter repainting every block when a run lands
A full-screen repaint draws every settled block, so when a background
[cli].diff_formatter run landed and the re-render hook was keyless, the
repaint dropped every diff block's cached rows and re-consulted the memo
for each. Once the transcript held more diff keys than the memo, the repaint
always re-missed at least one block, spawned a run for it, and that run
landed and repainted again — an endless loop of formatter runs and full
repaints.

Carry the landed DiffKey on the hook and remember, per block, the keys its
last render consulted, so a landed run drops only the blocks that asked for
its key. Gate the drop on the memo still holding the key: re-rendering a
block whose key was evicted would re-run the formatter and evict another
live key, so an evicted block keeps the rows it already shows.

The memo is raised to 128 so a normal transcript does not reach the eviction
path, and keyed by a 128-bit hash of the diff text rather than the text
itself, so an entry retains only the formatted output (~33-41% less
retained heap). Also record the formatter's 1 MiB size cap as a fallback
reason in the CLI reference.
2026-09-30 03:31:34 +02:00
Will a170f2158a fix(bench): retire the FullFold ablation and the inert compaction -arm flag
ablation.FullFold has had no reader since #8244 (397ed30ec) replaced the
multi-threshold path with the single checkpoint design: foldSource and
carryPriorDigests lost their Off(FullFold) checks together with their test, so
-arm=incremental was identical to the default, and the default already folds the
model-visible projection. The checkpoint design is the intended one, so retire
the dead axis rather than restore the old re-read.

- ablation: drop the FullFold module (constant, comment, module list entry)
- compaction bench: drop the -arm flag, the arms struct, foldArm, and the arm
  label from the report title and the JSON report
- benchmarks/README.md: drop the "Fold arms" section, and correct the paragraph
  that claimed a fold re-derives its digest from the canonical transcript
- docs/CLI.md: drop full-fold from the --ablate list

Verified: go build ./...; go vet; go test ./benchmarks/compaction/
./internal/contract/ablation/; go run ./tools/repolint clean; golangci-lint
v2.12.2 (the version CI pins) 0 issues. -mode=cost still reports the same eight
generations, -arm is now an unknown flag, and the JSON report no longer carries
an arm label.

Fixes #11358.
2026-09-30 09:20:25 +08:00
Stephen R. van den Berg 519a4acc11 feat(cli): render a shell result whose whole output is a diff
A shell tool result whose whole output is a unified diff — bash running
`git diff`, or `git show` in its default format — is tagged and rendered as a
diff: the TUI draws it through the same rows a ```diff fence produces, ACP
fences it for its client, and the desktop card renders it with DiffView.

The tag is computed once, at the one sink every emitter shares, and is gated by
the opt-in [agent].embedded_diff_detection (default false). Detection is
whole-text and conservative (diff.IsUnifiedDiff): every line must belong to the
diff grammar — including a `git show` / `git log -p` commit header and its
indented message — and a lossy Bound is never tagged, so a diff followed by
prose, a build log, or a truncation note stays prose. The tag is re-derived when
a session is rebuilt from /history, so a reopened session renders the same diff
the live one did.

The desktop DiffView splits a multi-file diff into per-file sections by the same
rule the CLI uses (git's "diff --git", else the "--- "/"+++ " pair) and names
each with its own "path +A -B" header row, so a `git diff` over several files no
longer shows only its first file's name over rows that belong to all of them.
A single-file diff keeps the card headline as its only name, so the fact is not
said twice.

trimDiffPreamble returning "" for a section with no --- line left a ```diff
fence without a --- /+++ pair rendering as an empty block. splitDiffSections now
returns sections untrimmed; the fence path trims each and falls back to the plain
code rail when it carries no file header, while the shell-diff path still drops a
pure preamble. A marked shell diff keeps the card's "⎿" connector on its first
row, like every other tool card's body. /history reads the embedded-diff flag the
session was built with instead of reloading config per request, and the hunk
parser rejects a body that overshoots its declared count.
2026-09-29 22:45:17 +02:00
Stephen R. van den Berg 4dfab0faf5 feat(cli): format diffs with an external command via [cli].diff_formatter
[cli].diff_formatter names an optional external argv line (run without a shell)
that formats a diff before the CLI/TUI emits it — both a fenced
```diff/```patch block and a writer tool's diff card. The whole diff goes to the
command's stdin and its stdout is re-emitted with non-SGR control sequences
stripped. On failure, timeout, empty output, or an oversized block the built-in
renderer is used. Like [cli].update_channel it is user/global only: a
project-local reasonix.toml cannot run a command on the user's machine.

The run is memoised per (block content, width), because a View re-renders many
times while a fence streams and again on every resize. It runs only on a closed
fence, so a fence still streaming spawns no subprocess per delta, and the command
is handed the terminal width via COLUMNS with its output clamped to it. A
WaitDelay bounds the wait for a formatter's pipes, so one that leaves a child
holding stdout cannot block the render past the timeout. The writer card drops
the "--- /+++ " pair before piping, so the formatter cannot re-emit a raw host
path the card header already names.

This commit also wires [cli].diff_fences, the opt-in toggle that turns the
fenced-diff renderer on.
2026-09-29 22:45:17 +02:00
YHH a63c2b92e4 Merge pull request #11288 from esengine/fix/studio-cli-compat-quiet-stderr
fix(cli): keep a headless run's stderr clean; diagnostics behind run --debug
2026-09-29 04:19:13 -07:00
esengine a062240d9f fix(cli): keep a headless run's stderr clean; diagnostics behind run --debug
1.x's `reasonix run` and `-p` leave stderr empty on a clean run. Studio wrote
three slog records there on every run: `INFO boot: assembly timing`,
`WARN prompt refiner provider construction failed` (whenever the optional
refiner side model cannot be built) and, with -c, `INFO controller: resume
cache state`. A script that treats stderr output as a failure, or captures it
into a report, broke on every invocation.

All three are diagnostics nobody has to act on, so they log at debug level.
`run --debug` lowers the process log level while flags are parsed, before
assembly writes its first record, for whoever wants them back. Warnings the
user has to act on keep their own channel.
2026-09-29 03:56:18 -07:00
YHH 19f8d5b001 Merge pull request #11285 from esengine/fix/studio-cli-compat-headless-verdict
fix(cli): headless run exits 0 when the model finishes; verdicts go in the result
2026-09-29 03:17:25 -07:00
esengine bdf3f96684 fix(cli): headless run exits 0 when the model finishes; verdicts go in the result
1.x's `reasonix run` exited 0 once the model finished. Studio exited 1 with
`error_during_execution` whenever its final-readiness check stayed unmet (for
example, no check ran after the last write), so every 1.x script treated a
finished run as a failure.

Following the migration's decision 3 (in line with how other agent CLIs
report this):

- An unmet final-readiness judgement is a verdict, not a failed run: subtype
  success, is_error false, exit 0, with `readiness` {attempts, missing} in the
  result and in events-jsonl's run_done.
- `--fail-on-unverified` turns that verdict into exit status 3, kept apart
  from 1 (run error) and 2 (usage) and documented.
- `permission_denials` in the result stays the gate's own list; run_done
  carries its count and `-p` names the refused calls on stderr.
- `completion` repeats the turn's completion_summary when there was one.
2026-09-29 01:40:08 -07:00
esengine fb6a82c8e3 fix(cli): restore 1.x's stream-json and events-jsonl line contract
1.x's `run --output-format stream-json` stamped every line with sessionId,
turnId, seq and status, opened the turn with turn_status (queued) and
user_message, reported tool_started between a call's dispatch and its result,
and closed with turn_done; --events-jsonl carried the same lifecycle records
without content. Studio's synchronous controller path publishes no turn
lifecycle, so both streams lacked all four kinds and the envelope, and they
leaked host-internal kinds (workspace_lease and friends) that no 1.x consumer
knows.

The run's output sink now supplies the lifecycle for the turn it drives and
stamps the envelope; only the kinds 1.x emitted reach either stream.
tool_started comes from a new host-only Tool.Executed on ToolResult, set when
the call cleared every gate and ran, so a refused call reports none.

The stats recorder swallowed RecordTurnCompletion instead of passing it to the
sink it wraps, so the run sink never learned a turn had finished: a failed run
reported num_turns 0. It now forwards the signal once (a TurnDone event still
records without a second completion).

Contract tests compare a real run against v1.39.5 output kept under
testdata/v1-run: lifecycle order, envelope keys, kind vocabulary.
2026-09-29 01:40:08 -07:00
esengine a548df7f55 fix(permission): refuse the remaining read-only escapes and trust no home directory 2026-09-29 01:02:46 -07:00
esengine 527eee4b4c fix(permission): close the read-only escapes and keep the posture's sensitive code declared
Found in an adversarial review of the new default posture:

- shellsafe: arguments that make a read-only command run another program or
  write are refused in every spelling the program accepts (clustered short
  flags, abbreviated long options): git grep -O, rg --pre, sort
  --compress-program, go -toolexec/-exec/-vettool, file -C, uniq's output
  operand, date -s, hostname NAME, git reflog expire/delete, npm audit fix,
  kubectl --kubeconfig, gofmt -cpuprofile. less, man and cargo check/doc
  leave the read-only tables.
- The headless gate and the posture decision live in the files REASONIX.md
  declares sensitive again (approval_bridge.go, approval_orchestration.go);
  the MCP fast-path refusal is decided in package permission.
- A sub-agent under an attended Auto no longer gets dynamic shell its parent
  would have to ask about.
- Read-only keeps fresh human decisions human; a refusal no rule produced
  names the posture instead of a deny list.
- The trust prompt and docs state what the sandbox actually bounds (temp,
  toolchain caches, network) and that trust covers reasonix run; a home
  directory or filesystem root is never trusted wholesale; the run result
  names the posture it settled on.
2026-09-29 00:30:16 -07:00
esengine 980008de88 feat(cli): default the terminal posture from the sandbox claim and folder trust
A terminal session with no named mode opens in auto only where the OS
sandbox demonstrably confines shell writes (sandbox.IntegrityEnforced) and
the person trusted the folder; everywhere else it asks, and a headless run
refuses writes and lists them as permission_denials in its result.

read-only is a real posture again: every call that is not a read is refused,
allow rules included, and an authorized MCP server's writers too. Gate
refusals carry a code (permission.unattended, read_only, deny_rule,
declined) that reaches the tool result and the run result.

YOLO keeps skipping prompts only; its first interactive use is confirmed
once and recorded under the Reasonix home. Shift+Tab follows 1.x's order,
Ctrl+Y returns to the posture it left, and a terminal's switch no longer
becomes the desktop's default. git -C/--no-pager classify as the
subcommand they wrap.
2026-09-29 00:00:47 -07:00
esengine adb5f3f9c0 fix(cli): approval flags mean the same thing on either side of run
`reasonix run` rejected --yolo and --dangerously-skip-permissions with
"unknown flag", `reasonix -y run "task"` failed with "unknown command",
and `reasonix --permission-mode auto run "task"` opened the terminal UI
with "run task" as its prompt instead of running headless. Print mode
disagreed with itself: `--yolo run -p hi` ran the task "run hi" and
`-y -p hi` was an unknown command.

Root cause: run's flag set never registered the yolo spellings the
terminal UI takes, and normalizeCommand only looked at argv[0], so a
leading session flag sent everything to the terminal UI, -y was read as
the command name, and the leading -p scan ran past the verb.

Fix:
- run registers --yolo/--dangerously-skip-permissions as an alias for
  --permission-mode bypassPermissions; each conflict (-y with --yolo,
  either with --permission-mode) names only the flags involved.
- run's flags move into runFlags/newRunFlags so they can be consulted
  before dispatch.
- The verb is located with the union of the terminal UI's flags and
  run's on the resume-normalized argv, as runTUI reads it, so a value of
  either is never taken for it. Leading flags move after `run` only when
  the session flags (the terminal UI's plus -y and -p) and run's both
  parse them to the same flags with the same values. Otherwise the line stays the terminal
  UI's: `--resume run hi`, `-r run hi` and `--inline run hi` keep their
  meaning, and a run-only leading flag (--metrics, --output-format) is
  reported by the terminal UI.
- -y and -p may lead like the other session flags, and the leading -p
  scan stops at the verb even when a run-only flag precedes it.
- The first word after the leading flags is the verb, as it is with no
  flags; CLI.md documents that, and interspersed run flags.

Test:
- TestRunApprovalFlagsReachTheHeadlessGate runs Run() against a fake
  provider whose first reply calls write_file. The file appears only
  where the resolved mode lets an unattended write through (ask and
  dontAsk stay unwritten). On origin/studio the five new spellings exit
  2 with no file. A mutation that drops the leading flags while routing
  fails it for the three leading-flag spellings.
- TestNormalizeCommandCarriesLeadingFlagsIntoRun fails on origin/studio
  (10 cases) and on the previous revision of this change (9 cases,
  including the three terminal-UI regressions). The revision before this
  one fails 2 cases: `--model m --metrics x run -p hi` and the same with
  --output-format ran the task "run hi". The revision before this one
  fails 2 more: `--resume --permission-mode
  --permission-mode=bypassPermissions run hi` and `--resume --resume=abc
  run hi` were forwarded to run although the two sets read different
  values. It passes now.
- TestRunTakesTheYoloSpellings covers the alias and the per-flag
  conflict messages.

Gates: gofmt, go vet (also GOOS=windows), make check clean,
golangci-lint 0 issues (also GOOS=windows), go test
./internal/frontend/cli/.

Refs #8355
2026-09-26 11:40:10 -07:00
Stephen R. van den Berg c4f0483223 feat(cli): start a fresh session when --continue finds nothing
When --continue finds no saved session, it reports the miss and starts a fresh
session instead of exiting 1. Both the run path and the TUI resume path fall
through, a --continue miss is no longer reported as "continue" in telemetry,
and the flag help and docs say so.
2026-09-26 02:18:45 +02:00
esengineandBuGlessRB 3047105ea4 feat(cli): --permission-mode reads 1.x's names
A 1.x command line keeps working under 2.x: workspace-write is Auto,
danger-full-access is Yolo, and read-only, which 2.x has no mode for,
falls back to Ask. MIGRATING says where yolo's meaning differs.

Co-authored-by: BuGlessRB <[email protected]>
2026-09-25 07:22:29 -07:00
yhh 01ee3830b1 feat(agent): a fleet dependency edge carries the answer, not only the order
depends_on ordered its endpoints and delivered nothing, so a research →
implement pair paid for the same research twice. Three changes that compose:

- The edge carries the dependency's answer. It travels as
  ContextRequest.Upstream, never inside TaskSpec.Objective, because the
  objective must stay the pristine text delivery classification judges —
  ClassifierTaskText is captured before any host framing. One 8 KiB budget is
  shared across a node's dependencies, so a wide fan-in cannot flood the
  opening turn and no dependency is starved out of it.

- list_subagents enumerates the children a conversation owns. A reference is
  the only address a child answer has, and a background fleet that a restart
  repaired to interrupted never delivered the aggregate carrying those
  references, leaving its completed children unreachable.

- adopt_ref stands a node down to an already-completed child's answer instead
  of running it. It is proven in preflight through ReadFinalAnswer, so what may
  be adopted cannot drift from what read_subagent_result may read. Nothing
  runs, so the node claims no write_paths and cannot collide with a live
  writer; prompt and every execution field are refused on it rather than
  silently ignored.

Whether an adopted answer is still valid is not readable from structure, so the
host does not decide it: adoption is addressed by reference, and the caller that
listed the reference judges freshness. That is also why a plan fingerprint does
not quietly reuse matching children — reuse the caller cannot see is reuse it
cannot correct.

A child now inherits something, which is exactly what InheritedContext exists to
keep a decision rather than an accident, so the capsule records it and SPEC 3.13
says so.

--ablate upstream leaves the edge ordering its endpoints while delivering
nothing, which is what lets the payload be measured apart from the order.
Against the real provider on benchmarks/upstream-edge/fleet-edge-wide, n=6 per
arm, removing the payload cost +27.6% tokens (exact permutation p=0.011) and
+59% sub-agent tool calls for the same answer, 6/6 solved either way. On a fact
one grep away it buys nothing: the edge pays in proportion to what the
upstream's work cost against how small its result is.

boot.go's delegation registrations moved to delegation_tools.go and Execute's
spec loop to buildItemSpecs; that, plus folding two defaulting branches into
firstNonEmpty, is what paid for the new lines without widening the repolint
baseline.
2026-08-24 01:03:50 -07:00
esengine 8c29942890 refactor: the task catalog is removed
Same situation as the session catalog: nothing read it. Its only importer was
the CLI command that rebuilt it, and no product path ever opened the file that
command wrote. Task snapshots and event logs were already the authoritative
data and are read directly.

That leaves two projections, both with real readers and both doing something a
directory scan cannot: history-search maintains an inverted index for BM25,
usage-catalog a precomputed rollup.
2026-08-21 20:57:57 -07:00
esengine 1c5c4adc70 refactor: the session catalog is removed
Nothing read it. Open, ReconcileDirectory, ListSessions and GetSession had zero
callers outside the package: the sidebar, the CLI picker, the bot and doctor all
list sessions by scanning the directory, which answers from sidecars and never
decodes a transcript. The only entry points were `sessions reindex` and
`doctor sessions`, which rebuilt and reported on a file no one then opened.

Measured before removing: scanning 186 sessions costs 17ms, and the sidebar
reads one workspace directory at a time — on this machine 461 of them averaging
a couple of sessions each, so well under a millisecond each.

What stays is what a scan cannot do. history-search needs an inverted index for
BM25; usage-catalog needs a precomputed rollup. Both have real readers. A
projection earns its place when the authoritative data cannot answer directly
and quickly — listing a directory can, because Turns and Preview already sit in
the sidecar next to each file.

The cost of keeping it was not hypothetical: both sidebar bugs fixed this week
were the projection misreading data that was itself correct. 4400 lines, three
schema migrations and a reconcile loop, for rows nothing displayed.

The memory index added yesterday to replace it goes too — it was written for a
consumer that turned out not to exist. Its comparison harness is in the history
if a projection is ever warranted again.
2026-08-21 20:36:44 -07:00
esengine c898679ede refactor(preset): two execution settings, and only fields that do something
The role settings declared far more strategy than they enforced. Six fields
had no reader anywhere — RequireAtomicContract, SemanticRouterOnAmbiguity,
DeterministicFirst, PreferProxy, DiffReview, AllowPartialWithoutChecks —
including two that were the stated difference between delivery and balanced.
A policy table that names a contract nothing builds is worse than a smaller
one: it tells the next reader a guarantee exists.

Light goes with them. Against balanced its only enforced differences were two
sub-agent switches (explore agents, semantic capability routing); the things
its name promised — cheaper verification, lighter review — were identical
settings on both. Its high-risk elevation moves to balanced so no user's floor
drops, and its capability profile selected an economy catalog that
SkillEntriesFiltered discards on its first line.

That leaves one question for the user instead of two: default, or delivery —
where the host enforces acceptance criteria, sign-off, verification, and
review off its own receipts.

"light", "economy", "eco", "lite" all still parse and answer balanced, so an
existing config or an older client is never rejected. They no longer pass
IsValid: they resolve, but nothing writes them back out.
2026-08-15 00:04:53 -07:00
SivanCola f40777ee76 merge: integrate execution-role runtime with continuous Goal
Problem: main-v2 advanced with the execution-role and completion-summary stack while PR #8373 was in CI, leaving the Goal runtime branch conflicted.

Root cause: both changes touch the Desktop and ACP runtime DTO owners and their repolint baselines.

Fix: merge main-v2, preserve the upstream execution-role behavior, retain the additive Goal workDurationMs mappings, and combine the exact repolint baselines.

Verification: git diff --check; go run ./tools/repolint
2026-08-11 21:37:52 +08:00
SivanCola 8ccd09f983 merge: integrate catalog runtime into continuous Goal work
Problem:
main-v2 advanced with the SQLite catalog integration while this PR's final CI was running, leaving the continuous Goal branch conflicting and unmergeable.

Root cause:
Both branches update Desktop runtime DTO owners and the repolint baseline; the catalog merge also extracts substantial Desktop code.

Fix:
Merge the latest main-v2 catalog stack, preserve the Goal workDurationMs contract, and carry forward only its two-line file-size delta on top of the reduced Desktop app baseline.

Verification:
The updated integration tree is rerun through Goal, catalog, race, Desktop, frontend, cache, and repository lint gates before push.
2026-08-11 21:16:20 +08:00
SivanCola c038bcfda4 Merge latest main-v2 into execution settings
Resolve concurrent changes in desktop preset switching, frontend event contracts, bundle budgets, tests, and repository lint state.

Preserve the in-place execution-setting switch and completion-summary routing while retaining the latest catalog, billing, hydration, and runtime behavior from main-v2.

Verified with full Go and desktop tests, frontend test:all and production build, race checks, go vet, and repolint.
2026-08-11 21:07:49 +08:00
SivanCola 200a32aa45 merge: sync continuous Goal runtime with main-v2
Problem:
The PR head predates newly merged main-v2 changes, so its previous green checks do not cover the current integration tree.

Root cause:
main-v2 advanced in overlapping Desktop runtime DTO, frontend, and capacity budget surfaces after the last PR update.

Fix:
Merge the latest main-v2 history while preserving continuous Goal semantics and the newly landed UI and bundle-budget changes.

Verification:
The merged tree is verified by the focused Goal, race, vet, Desktop, frontend, cache, and repository lint gates before push.
2026-08-11 20:22:25 +08:00
SivanCola be91b4ecdc merge(main-v2): integrate billing and task controls 2026-08-11 19:38:08 +08:00
SivanCola ee17c62901 Unify multi-currency billing across all surfaces / 统一全链路计费与币种 (#8058)
* feat(billing): unify multi-currency cost accounting

Problem:
Reasonix exposed provider prices, wallet balances, and session totals through independent currency paths. Mixed model runs could clear or mislabel costs, and display preferences could mutate regional list prices.

Root cause:
The runtime had no currency-tagged money domain, occurrence-time valuation, or shared quote boundary. Individual sinks accumulated floating-point Pricing.Cost values and inferred display semantics locally.

Fix:
Introduce fixed-point Money, CostQuote, official regional price catalogs, ECB FX snapshots, occurrence-time ledgers, wallet conversion, config v6 migration, and a central quote sink shared by Desktop, Serve, CLI, ACP, Remote, stats, and eventwire. Preserve additive legacy aliases and document the new contracts in English and Chinese.

Verification:
- go test ./...
- (cd desktop && go test ./...)
- (cd desktop/frontend && pnpm typecheck && pnpm test:usage-stats && pnpm build)
- go test -race ./internal/billing ./internal/event ./internal/stats ./internal/boot ./internal/config ./internal/cli ./internal/acp ./internal/serve
- go test -race ./internal/billing ./internal/acp ./internal/eventwire ./internal/event
- golangci-lint run --timeout=5m
- (cd desktop && golangci-lint run --timeout=5m)
- go run ./tools/repolint
- ./scripts/cache-guard.sh
- CACHE_IMPACT_PR_BODY_FILE=/tmp/reasonix-billing-pr-body.md ./scripts/check-cache-impact.sh
- DOCS_IMPACT_PR_BODY_FILE=/tmp/reasonix-billing-pr-body.md ./scripts/check-docs-impact.sh
- git diff --check

* fix(billing): keep cost totals complete and lossless

Problem:
Exact original-currency totals could be marked incomplete when an unused FX valuation was unavailable. CLI and ACP recomputed authoritative totals from a 64-quote sample, and live controllers retained the pre-refresh FX table pointer.

Root cause:
CostQuote completeness conflated cost-fact availability with optional valuation coverage. Run surfaces used bounded quote slices as ledgers, while QuoteContext captured a one-time RateTable snapshot.

Fix:
Make completeness follow the selected shared display total, preserve unrecoverable fact failures, and keep incomplete frontend quotes unavailable. Accumulate CLI and ACP totals through the billing ledger, including restored ACP scalar history, and read the current FXCache snapshot for every quote.

Verification:
- go test ./...
- go test -race ./internal/billing ./internal/event ./internal/boot ./internal/cli ./internal/acp
- (cd desktop && go test ./... && go vet ./...)
- pnpm --dir desktop/frontend typecheck
- pnpm --dir desktop/frontend build
- ./scripts/cache-guard.sh
- go run ./tools/repolint

* refactor: remove runtime FX from billing

Problem: runtime FX downloads and wallet conversion mixed pricing estimates with provider balances and could fabricate cross-currency totals.

Root cause: CostQuote, ledger aggregation, display selection, and wallet rendering shared one currency path and treated display completeness as cost completeness.

Fix: keep fixed-point original facts, restrict new valuations to identity and official regional tables, add explicit cost/display status and currency buckets, remove ECB startup/cache paths, and use exact original wallet balances with runtime-only hints.

Verification: go test ./..., desktop go test ./..., targeted race suites, frontend typecheck/usage-stats/build, cache guard, cache-impact review, and secret scan.

* Fix display-only currency rebinding

Problem: changing the billing display currency still rebuilt Desktop controllers, stale wallet responses could bind a newer session, and two obsolete locale-pricing helpers failed golangci-lint.

Root cause: the display preference reused the general runtime-settings rebuild path, while wallet callbacks validated only the controller pointer and had no tab/session display generation.

Fix: persist the preference without rebuilding, re-select existing ledger valuations across runtime tabs, compare-and-apply wallet hints against controller and telemetry generation, and remove the obsolete locale-pricing paths.

Verification: focused RED/GREEN tests; Desktop focused race tests; go test ./... in root and desktop; go vet; golangci-lint; repolint; cache-impact check.

* Clarify cost display currency settings

Problem: the Desktop setting still used ambiguous pricing terminology and packed the auto-selection rule into the segmented control.

Root cause: the billing refactor renamed the preference but left the surrounding appearance copy and compact option label partially aligned with the old pricing semantics.

Fix: present one cost-display preference, mark auto as recommended, explain wallet, pricing-table, fallback, and mixed-currency behavior in the hint, and align English, Simplified Chinese, and Traditional Chinese copy.

Verification: source contract scan; pnpm typecheck; pnpm build with bundle budgets.

* Shorten cost display currency guidance

Problem: the Desktop cost-display hint wrapped to roughly four lines and visually dominated the setting row.

Root cause: fallback and mixed-currency details duplicated behavior already surfaced by the cost UI.

Fix: retain only the display-only contract and automatic currency-selection order in concise English, Simplified Chinese, and Traditional Chinese copy.

Verification: in-app browser DOM measurement at 904x936; pnpm typecheck; pnpm build with bundle budgets.

* Fix narrow settings control overflow

Problem:
- General settings kept its two-column layout at narrow desktop widths, clipping controls at the modal edge.

Root cause:
- A component-specific 900px override won over the shared 980px responsive rule and sized the row independently of its actual content pane.

Fix:
- Make the general page an inline-size container and stack fields below 620px of available width.
- Align the media-query fallback with the shared 980px breakpoint.
- Add responsive and startup contract coverage.

Verification:
- pnpm test:settings-responsive
- pnpm exec tsx src/__tests__/startup-settings-contract.test.ts
- pnpm check:css
- pnpm typecheck
- pnpm build
- 904x936 browser DOM check: maximum control overflow is 0px
2026-08-11 19:02:51 +08:00
SivanCola 46923b9895 Rename role settings to execution settings
Problem: User-facing role terminology suggests a persona switch, while these presets actually control planning, verification, and delivery behavior.

Root cause: Desktop, CLI, ACP, and documentation retained the earlier role-setting product name.

Fix: Use Execution setting / 执行设定 / 執行設定 consistently across product surfaces while preserving agent_preset, work_mode, tokenMode, and compatibility values.

Verification: go test ./internal/i18n ./internal/cli ./internal/acp; cd desktop && go test ./...; cd desktop/frontend && pnpm test:all && pnpm build; local browser menu click and overflow check.
2026-08-11 17:04:07 +08:00
SivanCola 1bf365af6e fix(goal): make execution continuous by default
Problem:
Goal mode could pause at host-owned numeric ceilings even while useful work was still progressing.

Root cause:
Default round, turn, wall-time, and stall thresholds were wired as terminal runtime boundaries and persisted as blocked Goal state.

Fix:
Remove host-owned Goal execution ceilings, preserve only user-selected resource limits, convert numeric stall guards into replanning redirects, migrate legacy numeric pauses, and expose cumulative work duration as observational runtime data.

Verification:
- go test ./...
- go test -race ./internal/agent ./internal/control
- go vet ./...
- cd desktop && go test ./...
- cd desktop/frontend && pnpm typecheck && pnpm test:all && pnpm build
- scripts/cache-guard.sh
- scripts/check-cache-impact.sh
- go run ./tools/repolint
2026-08-11 16:02:29 +08:00
SivanCola 5e03e03eaf feat(agent): light/balanced/delivery role settings with unified tools / 角色设定与统一工具面
Problem:
Work modes (economy/full/delivery) mixed token surface trimming, delivery
gates, and UI labels. Economy forced connect_tool_source schema growth;
switching modes rebuilt controllers and busted prompt-cache prefixes.
Skill profiles could hide tools from the model surface.

Fix:
- Introduce AgentPreset (light|balanced|delivery) + TaskPolicy host floors
  for plan route, verification, and independent review.
- Share one provider-visible core surface across presets; optional tools
  dispatch only through use_capability (retire connect_tool_source boot).
- Switch role settings in place without controller rebuild (CLI /preset,
  desktop, ACP agent_preset; legacy work_mode/tokenMode dual-write one version).
- Emit turn_phase and completion_summary events; wire desktop/CLI UI.
- Keep skill profiles diagnostic-only; dual-write agent_preset on tabs/branch meta.
- Docs, i18n, golden surfaces, and regression matrix updated.

Verification:
- go test ./internal/boot ./internal/agent ./internal/cli ./internal/acp
  ./internal/control ./internal/skill ./internal/agentpreset ./internal/taskpolicy
- desktop role-setting smoke tests
2026-08-11 09:25:13 +08:00
SivanCola 0d41f2ca46 merge: adopt task catalog from #8219
Add the cross-project task catalog, Desktop task center APIs, and CLI
reindex surface on the shared multi-domain SQLite projection stack while
keeping history/usage registration and the hardened session foundation.
2026-08-11 01:52:01 +08:00
SivanCola 40b15fd327 merge: adopt usage catalog from #8218
Add idempotent usage rollups as a disposable SQLite projection with CLI
doctor/reindex surface, layered on the shared projection lifecycle and
preserving the hardened session/history foundations.
2026-08-11 01:50:15 +08:00
SivanCola 608543afee merge: adopt history search catalog from #8217
Add the token-only FTS5 history projection, non-blocking persist observer,
paged Desktop history manager, and CLI doctor/reindex surface on top of the
hardened session-catalog foundation. Keep corruption-only projectiondb
quarantine and session-catalog lifecycle fixes from #8186 while adopting
history session pagination (schema v3).
2026-08-11 01:49:38 +08:00
SivanCola 5cec97d996 merge: adopt session catalog foundation from #8186
Bring the non-blocking desktop session discovery and hardened disposable
SQLite projection lifecycle into the multi-domain catalog integration.
2026-08-11 01:48:25 +08:00
SivanCola 397ed30ec8 feat(agent): content-driven context maintenance (single compact_ratio) / 内容驱动上下文维护(单一 compact_ratio) (#8244)
* feat(agent): content-driven context maintenance with single compact_ratio

Replace multi-threshold prune/snip/native auto maintenance with one
content-driven checkpoint path (stable prefix + one summary + recent
tail) gated solely by compact_ratio (default 0.85). First-visible tool
results are bounded at creation; prune/snip APIs remain no-ops for
call-site compatibility while legacy config/sidecar fields load and
migrate away.

Also align max_output_tokens auto ladder (0=auto; 128K explicit only),
desktop/settings/docs, and force CompactNow planning so mid-size
strict-alternating sessions (e.g. guardian) still reduce tokens.

Verification: go test ./...; desktop go test ./...; frontend test:all;
cache-guard; race on core packages; live seed→resume→continue smoke
(version=1, one summary, ~$0.0012).

* fix(agent): make context projection durable on Windows and clear CI gates

Problem:
- SaveCompactionState fsynced the parent directory after rename. Windows
  returns ERROR_ACCESS_DENIED for directory Sync, so every projection
  install failed and Windows smoke tests went red.
- gofmt, repolint, frontend AgentView fixtures, and required cache/docs
  PR-body lines also blocked CI for #8244.

Root cause:
- Custom atomic write path reimplemented durability with a directory Sync
  that is not portable, instead of using the shared Windows-safe helper.
- Residual format/debt and a missing coldResumePrune fixture field.

Fix:
- Persist sidecars through fileutil.AtomicWriteFile (temp + file fsync +
  replace) without directory Sync.
- gofmt touched Go files; shorten essay comments; extract fold helpers;
  carry residual structural debt in the repolint baseline after reducing
  new essay debt; restore coldResumePrune on settings fixtures.

Verification:
- gofmt clean; go run ./tools/repolint clean
- go test ./internal/agent/ ./internal/config/ ./internal/provider/...
  ./internal/guardian/ ./internal/cli/
- pnpm settings-refresh-snapshot: 91 passed

* chore: nudge PR head refresh after Windows durability fix

Empty commit so GitHub re-resolves the fork head for #8244 after the
merge of main-v2 and the Windows sidecar write fix.

* fix(agent): close #8244 review blockers for content-driven maintenance

Problem:
Review of PR #8244 requested changes for CI failures, threshold under-count,
legacy native lineage drop, non-atomic CAS/write, residual cold-resume UI, and
duplicate context_maintenance notices.

Root cause:
- Threshold used message-only estimates without tools/role projection.
- Sidecar lineage required exact PromptCacheKey and marked restored before
  covered-hash validation.
- Checkpoint install checked transcript outside the install lock and used
  non-strict atomic write with EXDEV copy fallback.
- coldResumePrune UI/API remained after behavior removal; locale gzip grew.
- Maintenance notices ignored operationId on reconnect/replay.
- golangci reported unused prune/summary helpers and minor modernize issues.

Fix:
- Estimate compact trigger from final request shape (messages + tools +
  provider role projection).
- Normalize legacy |context-editing-native-* keys; restore only if projection
  still validates; persist normalized key without version bump/events.
- CAS install under compactionMu; SaveCompactionState uses AtomicWriteFileStrict.
- Remove coldResumePrune settings UI/Wails field/locales; keep config migration.
- Dedupe context_maintenance notices by operationId (bounded FIFO) + tests.
- Delete unused prune/summary helpers; clear golangci; trim locale strings for
  bundle budget; strip SPEC trailing space.

Verification:
- make lint (golangci + repolint) clean
- go test agent/config/control/guardian/provider
- pnpm frontend build (locale budgets pass)
- context-maintenance-notice + settings-refresh-snapshot tests

* fix(desktop): gofmt reasoning_display fixture after cold-prune removal

Desktop CI failed gofmt on reasoning_display_app.go after ColdResumePrune
was dropped from the AgentView fixture literal.

* fix(agent): unblock context-maintenance merge blockers

Problem: PR #8244 still had three merge blockers: emit under
compactionMu (deadlock risk), restore rewriting exact-key
sidecars, and initial raw bundle over budget.

Root cause: commitSummaryProjection held the lock through
synchronous sink.Emit; LoadProjectionSidecar persisted whenever
the key already matched; ContextPanel/TranscriptSelectionMenu
were eagerly imported into the initial chunk.

Fix:
- Unlock before emitContextMaintenance; reentrant sink test
- needsNormalization only for legacy native lineage rewrite;
  log persist errors instead of ignoring them
- Lazy-load ContextPanel and TranscriptSelectionMenu
- Document pre-interceptor threshold estimate + overflow recovery
- CLI/GOAL docs: compact_ratio default 85%

Verification:
- go test ./internal/agent/ (+ race focused tests)
- make lint (repolint baseline essay bump)
- pnpm build: initial raw 2236.8 / 2240 KiB
- context-maintenance-notice test ok

* fix(agent): close remaining context-maintenance plan gaps

Problem: After the merge blockers landed, plan gaps remained: no
parent-dir fsync on strict checkpoint writes, new writers still
mirrored last_*/blocked_* fields, dead CompactionCapabler shell,
and the pre-interceptor threshold contract lacked a regression test.

Root cause: AtomicWriteFileStrict only fsynced the temp file;
LastReceipt was authoritative but writers still dual-wrote mirrors;
provider.CompactionCapabler had no production callers; Prepare never
invoked interceptors by design without an explicit test.

Fix:
- Strict atomic write: fsync parent directory after rename (Windows/
  unsupported FS soft-fail)
- SaveCompactionState / commit / blocked paths: LastReceipt only;
  strip last_trigger/last_mode/token mirrors and top-level blocked_*
- Restore receipt-only blocked sidecars on load
- ContextReport/CLI/status prefer LastReceipt with legacy fallback
- Delete unused CompactionCapabler/NativeCompaction shell
- TestPrepareThresholdSkipsExtensionInterceptors

Verification:
- go test ./internal/agent ./internal/fileutil ./internal/provider
- go test -race focused packages
- make lint (repolint clean)

* docs: drop removed context_editing example from zh GUIDE

Problem: GUIDE.zh-CN still suggested context_editing = "native"
after native Anthropic context editing was removed and startup
migration deletes that key.

Root cause: bilingual GUIDE config sample was not fully synced
with the single compact_ratio local-summary contract.

Fix: remove the obsolete config line from docs/GUIDE.zh-CN.md.
English GUIDE already had no such example.

Verification: grep docs for remaining context_editing user examples
(only SPEC migration notes remain, which correctly describe removal).

* fix(fileutil): never fail strict write after successful rename

Problem: AtomicWriteFileStrict returned parent-dir fsync errors after
rename had already published the new file. commitSummaryProjection
and blocked-receipt writers treated that as "not committed" and
rolled memory back, forking disk (new generation) from process
memory (old generation) and allowing paid re-summary loops.

Root cause: post-publish power-loss durability was modeled as a
hard failure even though the atomic publish already completed.

Fix:
- After successful rename, parent-dir fsync is best-effort only
- Returned error still means "destination not published"
- syncParentDir also covers relative parents (".")
- Agent tests: applied and blocked receipts survive injected
  post-publish dir-sync faults without generation fork / re-summary
- PR body: document AtomicWriteFileStrict + best-effort dir fsync

Verification:
- go test ./internal/agent ./internal/fileutil
- make lint
2026-08-11 01:30:26 +08:00
SivanCola 3739f12a7d fix(tasks): complete catalog lifecycle contracts
Problem: the isolated Task Catalog branch lacked shell completion metadata and only flushed its shared projection during desktop shutdown, allowing late reconcile goroutines to outlive the shutdown boundary.

Root cause: the task implementation was originally layered on the integrated History branch, so domain-specific lifecycle and documentation details were lost when it was split into an independently reviewable branch.

Fix: register task reindex completions, document the task cache and recovery commands, gate reconcile scheduling during close, wait for scheduled workers, close the shared catalog within the 250ms desktop drain, and account for the measured 1 KiB frontend bundle increase.

Verification: focused Desktop tests, frontend production build and bundle budgets, repository lint, core package tests, and race tests for projectiondb, taskcatalog, taskmonitor, and control passed.
2026-08-10 20:06:02 +08:00
SivanCola d9ca43ac5e docs(stats): document usage catalog recovery
Problem: Operators need a discoverable cache path, diagnostic command, exact-fallback guarantee, and safe rebuild path for usage rollups.

Fix: Link the bilingual Usage catalog guide from CLI and storage references and ratchet repolint to the Usage-only branch shape.

Verification: repolint baseline generation and git diff --check passed.
2026-08-10 19:58:55 +08:00
SivanCola 98e30ee1a7 docs(history): document catalog recovery
Problem: Users and operators need a discoverable cache location, diagnostic command, and safe rebuild path for indexed history.

Fix: Link the bilingual history catalog guide from CLI and storage references, document its cache path, and ratchet repolint to the History-only branch shape.

Verification: repolint baseline generation and git diff --check passed.
2026-08-10 19:45:27 +08:00
SivanCola 623d536e00 merge: refresh session catalog on latest main-v2
Bring the session catalog prerequisite onto the current upstream base before publishing the History, Usage, and Task follow-up branches.

# Conflicts:
#	desktop/frontend/scripts/check-bundle-budget.mjs
2026-08-10 19:43:48 +08:00