A batch printed its aggregates and kept nothing they could be recomputed
from, so a published figure could not be tied to the runs behind it after
the temp directory was gone. -batch DIR now writes results.jsonl (one row
per attempt) beside batch.json (manifest v1), and -mode audit re-derives
the report's headline figures from those rows with the harness's own
aggregation, printing the claim beside the recomputation and naming every
figure that moved.
The manifest records what the figures are over (command axes, a task-id
digest, filters, and a denominator defined in tasks), which run produced
them (commit or an explicit unknown, model/provider names, seed when
known, timestamps, harness and runtime), and why they can still be
checked (results_sha256, the claimed figures, and a ledger of planned /
attempted / completed / scored / failed / unmeasured / excluded that the
audit checks for consistency). Absent figures are omitted and read as
unknown, never as zero.
This is an aggregation audit, not a replay: it proves the published
figures follow from the retained rows. It does not re-execute the model,
tools or environment, and a real replay (commit, corpus fingerprint,
model/provider configuration, seed, flags, raw results, runtime version)
is not implemented.
Problem: Accepted total-only usage remained visible to reporting but did not
cross token budgets, allowing additional tool rounds after the known spend.
Root cause: Both local and shared-root enforcement added only input/output
components, so missing or partial splits hid a known reported total.
Fix: Use one accounting helper for the larger of the nonnegative total and
component sum, and saturate both counters. Keep usage, pricing, shadow component
telemetry, context ownership and existing soft-gate/finalization behavior.
Verification: Real OpenAI HTTP/SSE adapter and Boot execute real file tools.
Final19named regressions pass; exact preceding owners fail10 events with9controls.
Root240packages once:24330named passes/215existing skips. Complete Agent race
2162pass/10existing skips; focused Boot race7pass. Vet, pinned lint, check, format,
workflow and release gates pass; four Win/Linuxamd64 checks compile only.
Problem: Linux coverage could not establish the carried-proof fixture;
CodeQL also flagged the shared inventory parser for exponential backtracking.
Root cause: the fixture inherited an unavailable host bash sandbox, and
repeated name-suffix groups could repartition the same slash sequence.
Fix: select sandbox off only in the isolated test user configuration;
parse the entire optional suffix once and add a bounded long-input test.
Verification: required builtin/Boot exit0, focused coverage four named
passes, 14-name shared contract, two Node tests, vet, pinned lint and check.
Original regex fails the bounded regression with one passing control.
No production Go, assertion, workflow, timeout, skip or baseline change.
Hosted Linux coverage, CodeQL and own-head Windows evidence remain due.
Problem: CodeQL reports exponential backtracking in the shared Windows
inventory test-name parser.
Root cause: repeated suffix groups can partition the same slash sequence.
Fix: accept the entire optional non-whitespace suffix once, preserving the
previous accepted language. Cover nested names and a long malformed suffix.
Verification: two Node tests pass; the original regex fails the bounded
regression while the existing contract test passes. Go and inventory bytes
are unchanged; prior exact-head Go qualification remains separate.
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.
The registry declares 324 pairs now: the 202 it already had and 122 page
copies that agree field for field with the struct their handler writes. The
table moved to a second file, wireparity_table.go, rather than taking
wireparity.go past the 800-line ceiling; the checker still reads one list.
Left out on purpose, because the writer is not a named struct and a guard
over the lookalike struct would not see the response drift:
account.DeviceGrant (/account/login assembles a map), market.Page
(/market/packages assembles a map) and control's McpDraft (/mcp/parse
assembles a map). host.ts's Refusal and ViewRect are the Electron preload's
bridge, not kernel wire; workspace.ts's WorkspaceEntry has no writer.
Tests: the added half resolves, is exact today and is unique per reader; a
dropped field on createBackupRequest -> BackupCreateRequest names the field.
The terminal's hand-written HistoryMessage dropped images, via and modelRef.
A turn that was only an image has empty text, so Restore saw nothing to draw
and dropped it from the transcript, and every reload lost the device and the
model of turns it did keep. Carry the fields that decide that a turn exists
and what it was, restore the image-only row with an honest line, and register
the pair in repolint so a dropped field fails make check.
The navigation already shows the section and page, so the first card now sits
at the top of the page. The setting count and the section's value stay in the
navigation row.
Problem: Competing continuations could create two copies of one ancestor in
the same parent session, leaving later continuation ambiguous.
Root cause: The source ref lock ended before a newly allocated copy published
metadata, and each preparation acquired a different destination ref lock.
Fix: Reserve the canonical nearest-source and target-parent pair before
searching for copies. Transfer the reservation to the returned run's existing
release lifetime, and release it on failed preparation. Preserve independent
parents, sources, current-reference locks and persisted formats.
Verification: Real TaskTool publication interleavings and Store regressions;
exact old-owner overlay fails 9 named events with 5 controls. Root 240-package
coverage, delegation/sessionstore race, vet, pinned lint, make check,
actionlint and Windows/Linux compile-only checks pass. Windows named-step
inventory validation rejects missing package completion and skipped cases.
Refs #14025
Invalidate persisted Delivery proof when file mode changes without a byte edit.
Use the existing content-version reader and verify surrounding identity, mode,
size and mtime observations. Refuse links, directories and special files as
regular-file proof inputs.
Version fingerprint values so legacy byte-only proof conservatively requires
fresh evidence in both reader directions, while goals and transcripts remain
readable. Cover real Boot persistence/rebuild, compatibility and Unix kinds,
and add a required native Windows inventory with bilingual documentation.
Refs #14025
Refs #14133
Preserve every workflow step, condition, timeout and assertion. Move the
status-owned pair away from concurrently added budget inventories so both
sets compose, and collect the named evidence before broad smoke can fail.
Refs #14656
Carry the accepted UsageQuoter event owner alongside Goal usage scope capture.
Retain both optional capabilities in wrappers and their shared inventory while
keeping scope-specific fixture additions independent of the quote additions.
Verify that nested bound usage reaches the occurrence-time quote owner once,
keeps the earlier Goal attribution, and preserves the emitted JSON. Document
the two owners and retain existing Windows required-test coverage.
Refs #14661
Refs #14671
Problem: local task cost gates could count a base rate while the accounting
event charged the vendor's occurrence-time peak rate, allowing extra tools.
Root cause: Agent budgeting recomputed spend with the legacy float adapter
instead of consuming the quote owned by the existing pricing sink.
Fix: forward an optional usage-quoter capability, capture one authoritative
quote for both delivery and budgeting, and accumulate fixed-point original
currency buckets. Preserve known subtotal and incomplete-usage semantics.
Keep display currency, provider requests and cancellation separate.
Verification: actual Agent and Boot peak/off-peak/relay regressions, preceding
owner negatives with controls, full root package inventory, relevant owner
and Boot race suites, static gates, cross-compilation and a pinned Windows
inventory with verifier mutants. Hosted native and independent review remain
separate. Shared root money and reservations are not implemented here.
Refs #14025.
Every model request carries the whole conversation, so upload grows with
context size times request count and nothing in the app showed it. Each send
attempt now reports its body size, a per-process meter keeps it per provider
and day beside the usage records, and Settings > Usage shows today, the last 30
days, the average request and the providers.
Own one disabled-by-default shortcut in the local Electron profile, with immediate authorized IPC and visible registration failures. Preserve old registration and saved state when rebind or persistence fails; restore at ready and unregister during quit.
Keep browser/network viewers unsupported and in-app bindings unchanged. Cover lifecycle, authorization, failure rollback, bounded native input and settings neighbours; add a real Linux/X11 OS-key smoke and transparent platform limits.
Fixes#8290. Refs #7623; stopped-app launching is not supplied.
A session whose message payload passes the replay bound could not be opened
and the window offered no way forward. The refusal now carries a code, and
the window offers to continue from the most recent part as a new session; the
original files are only read.
The two notices a fold leaves when user turns survive only through the summary
carried no code, so a Chinese window showed the English fallback. They now
carry a code and a typed payload like the other coded notices.
Problem: background children from an older chat or Goal could charge late
usage to a newly active Goal while still retaining their original root budget.
Root cause: the usage tee selected its current recorder at event delivery,
so recorder epoch checks were applied to the wrong owner.
Fix: capture an opaque process-local accounting scope at the shared tool-call
boundary. Forward the optional capability through sink wrappers, preserve it
for nested delegation, and select the captured Goal recorder at consumption.
Keep session tally, quoting, title exclusion, provider requests and wire data
unchanged. Document between-turn accounting and persistence limitations.
Verification: real Boot delayed-background and nested fixtures, same-Goal
controls, Goal lifecycle and JSON tests, exact preceding-owner negatives,
root package inventory, owner/Boot race tests, static gates, cross-compilation
and a pinned native Windows inventory with fail-closed verifier mutants.
Hosted native execution and independent review remain separate gates.
Refs #14025.
Every inbox handler answered a store refusal with -32600 and prose, so a client
could not tell "gone" from "wrong state" from "may already have run" — and one
that retried on any error re-sent work the kernel refuses precisely because the
turn may already have run. The HTTP frontend already names 14 conditions; ACP
now answers the same names, with -32020 for "the queue refused this call" and
-32021 for "the work may already have run", and leaves -32600 to a malformed
frame alone.
docs/ACP.md states the table and each refusal's class (terminal,
retry-after-repair, do not retry). repolint's family-vs-writer check now reads
both writers, so a sentinel added to the store cannot reach ACP unnamed.
A row already records why its money may be incomplete (`cost_estimated`,
`incomplete_reason`), but RangeStats kept only a bool, so a total could say
"this folded in an estimate" and never "of these calls, N were never priced,
because …". Both read paths now count those calls and name the distinct
reasons; the projection carries the reason as schema 8, which reindexes from
the daily files; an old or reason-less row still counts, it just adds no
reason to the vocabulary.
Problem: Saved child records with missing or unknown status could reach
continuation and transcript-copy preparation.
Root cause: The shared metadata validator rejected three known bad states
but implicitly admitted every other string, including future enum values.
Fix: Permit only the existing completed/cancelled states before unchanged
identity checks. Keep metadata decoding and writers unchanged so refusal
preserves unknown fields and transcript bytes for diagnosis.
Verification: Real on-disk store/reopen matrix covers nine state shapes and
five preparation routes, artifact preservation and reference-lock release.
Exact preceding owner fails 25 named events with 31 passing controls.
Qualification results and compatibility limits are recorded in the PR body.
Problem: Explicit slash, composer and CLI profile children could exceed a
configured root token/wall budget because they bypassed the normal Agent turn.
Root cause: Those Controller entries invoked skill runners without preparing
the executor-owned scope; Goal skills installed observation without the spend
limit. Synchronous profile calls also lacked turn admission serialization.
Fix: Bind the existing Goal/root scope after blocking hooks for interactive
skills. Reuse synchronous Controller admission and root preparation for CLI
profile run/preview, retaining child pause and cancellation semantics.
Verification: Actual Boot HTTP/file-read and Controller lifecycle regressions;
exact old-owner overlay fails 13 named events with four passing controls.
Complete 239-package coverage includes the retained original Boot 15m alarm
and two disjoint remainder groups; four owner race suites and focused Boot
race pass. Root vet, full lint, check and actionlint pass. Windows/Linux
compile-only checks and the literal native gate contract pass locally.
Problem: The setting catalogue described health consent as applying on session open.
Root cause: The declaration did not match the controller's per-request global read.
Fix: Mark the guarded setting Immediate; disable and authorization changes are
checked before cached evidence is returned.
Verification: Settings package tests and the registered HTTP opt-in/disable
regression pass. No payload or request logic changed.
Problem: automatic picker probes need explicit user consent and must not
borrow credentials for project-defined destinations.
Root cause: the combined implementation used the merged configuration and
had no backend-enforced default-off setting.
Fix: use a strict user-global configuration snapshot, require the opt-in and
provider access before every cache read, refuse all redirects, and expose a
guarded settings API. Cache only fixed-size digests and status/timestamps;
never retain upstream error bodies or log credentials.
Verification: authorization/default-off/disable and malformed-config HTTP
regressions, previous-reader save overlay, desktop setting round trip,
provider-health race tests, and frontend route contracts passed. Full lint
and inventory-partitioned boot validation are tracked separately in the PR.
Refs #14617
Problem: Directory labels were inconsistent across the header and composer,
and the default green badge looked like a success status.
Root cause: Separate presentation strings described the same workspace
state, including an unnecessary badge for the ordinary directory case.
Fix: Show neutral labels only for isolated worktrees, retain explicit
current-folder/worktree choices with checkmarks, and share explanatory
tooltips while preserving full paths and branches. Update translations,
usage documentation, and the release note.
Verification: Frontend interaction, translation and style checks;
TypeScript; make check; gofmt; go vet; make lint; browser comparisons in
light/dark themes at wide and phone widths. Required Go package and
production build results are recorded in the pull request.
The boot package ran 726s on that job against 264s here, and this change does
not reach it: projectiondb.OpenReadOnly/Inspect have one production caller and
it is the doctor command. No code change.
Problem: `reasonix doctor catalogs` printed the projection's integrity_check
string as the catalog's state, so a history catalog whose history_sources row
recorded health='corrupt' printed `ok`, and a future-schema database -- which
the runtime opens as StateDegraded -- printed `ok` too. The command a user is
told to run to check their data agreed with neither the catalog nor
`catalogs reindex --json`, which reports the same state as "failed": 1.
Root cause: the state column came from Inspection.Integrity and nothing else.
The command never asked the catalog what it recorded, and never compared the
recorded schema against the schema this build migrates to.
Fix: read the facts reindex already reads. refreshStatus and refresh now share
one readStatusCounts with the new read-only Inspect readers, so the
diagnostic's failed is the number reindex reports, and the reason is the one
the catalog recorded. A newer schema reports degraded against
projectiondb.SupportedVersion, which ApplyMigrations also uses. --json gains
state, failed, pending, corruptLines/lagBytes and failures only when there is
something to say, so a healthy catalog's output is unchanged. Text stays one
line per catalog. Exit stays 0: like `catalogs reindex --json`, this command
reports rather than repairs.
Verification: red-first. A reindexed undecodable transcript printed
`history ok`; it now prints `history failed ... failed=1 source=<path>:
<recorded reason>`. A schema-2 database printed `ok`; it now prints
`degraded ... supported=1`. Controls: healthy line and exit unchanged, missing
line unchanged, healthy JSON keys unchanged. Mutation: dropping the failure
read turns the corrupt-source test red, dropping the degraded read turns the
future-schema test red. projectiondb.OpenReadOnly reads a live WAL, which
immutable=1 skipped. No projection schema, history index or reindex path
changed. Refs docs/USAGE_CATALOG.md:25, docs/HISTORY_SEARCH_CATALOG.md:42-43.
Problem: A dual-model Goal resumed after a spend-budget pause can stop its
new planner research after one tool round despite the user renewing the slice.
Root cause: ResumeGoal resets only the executor's local budget. The tool
planner keeps its own Agent accumulator and stricter local gate, so renewing
the shared executor root does not clear that old planner spend.
Fix: On a successful budget-spend resume, also reset the existing companion
planner Agent through the runner's established accessor. Preserve manual
pauses, refused resumes, cumulative Goal statistics and delivery evidence.
No new API, default, cancellation authority, prompt or persisted format.
Verification: Actual Controller/Coordinator loop reproduces two resumed
planner requests instead of four on the preceding owner. Actual Boot plus
HTTP/SSE reads three fixture files per renewed slice; the exact old owner
overlay compiles and fails both regressions with four passing controls.
Full 239-package coverage reconciles 24061 unique test/subtest passes and
215 existing skips. The original Boot command hit its 15m package alarm;
all 846 top names close via 842 original terminals and disjoint 2/2-name
remainder groups, without raising timeouts. Complete control/coordinator
race1758 passes/4 existing skips, actual Boot race1 pass, static gates and
Windows/Linux compile-only checks pass. New-head hosted/native gates remain.
Problem: The Windows fallback control missed its recorder marker and could
not remove the still-running taskkill test executable.
Root cause: exec.Command("taskkill", ...) preserves the bare command in
argv[0]. The recorder compared that argument with taskkill.exe, missed its
special entry point, and entered the complete package suite instead.
Fix: Identify the recorder with os.Executable, require LookPath to select
the fixture's same file, and fail closed on an unexpected recorder entry.
Add a bare-command alias probe whose fallback selector runs only the inert
helper. Run/Wait completes before temporary-directory cleanup. Keep the
production ownership repair unchanged and extend the native inventory to
seven pass events versus four expected failures and three passing controls.
Verification: Windows cross-compile, Windows vet and pinned Windows proc
lint pass; full root vet/lint and make check pass at unchanged baseline.
All 6268 selected Darwin inputs match the previous qualified commit; this
append changes only the Windows fixture, verifier inventory and docs.
The complete original Windows failure log is preserved. Fresh native
Windows execution on this new commit remains required, not inferred.
Problem: Repeated cancellation or shell reaping can discard an established
Windows Job Object owner and perform a new PID-based stop attempt.
Root cause: TrackedCommand removes its one-shot TrackedJob on the first kill
and also starts a redundant PID tree observer after successful job assignment.
Fix: Keep the published owner across repeated and late cancellation, and let
an established Job Object own its tree. Preserve existing non-job fallbacks.
Add native Windows owned-child probes with a harmless taskkill recorder and
an exact preceding-owner counterfactual inventory gate.
Verification: Root inventory reconciles 239 packages, 24060 test/subtest
passes and 215 existing skips. Original Boot command hit its 15m package
alarm; all 845 top-level tests close through the recorded disjoint remainder
groups without a timeout increase. Complete proc and shell race suites pass.
Root vet, full lint, make check and actionlint pass. Windows new/old owners
compile and Windows vet passes; actual native regressions remain a hosted gate.
No full PID-identity, descendant-quiescence or physical-scope claim.
Problem: semantic routing runs before Agent/Coordinator root preparation, so its reported usage can permit an extra executor tool round.
Root cause: Controller binds no root before routing, and SemanticRouter does not report finalized usage to the accepted context observer.
Fix: share executor root preparation after admission/hooks and before routing in headless and interactive turns. Observe semantic terminal usage once, independent of audit/sink and answer success. Preserve cache hits, new-task/scope/reset lifetimes, normal accounting and cancellation authorities.
Verification: disjoint required/rest commands pass all 239 root packages; four complete owner race suites pass 3939 events with 14 existing skips; actual Boot race passes 12 events. Exact preceding owners compile and fail 15 regression events with six passing controls. Format, vet, full lint and make check pass at byte-identical baseline. Six Windows/Linux binaries compile; local literal Windows gate verifies 21 required events/two package terminals and rejects skip/missing-terminal controls, not native execution.
Problem: ambient Git index/object variables can redirect host worktree preparation and object access.
Root cause: pinned Git/common/worktree directories do not override GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY or GIT_ALTERNATE_OBJECT_DIRECTORIES. The shared constructors retained them.
Fix: remove those three ambient overrides in Env and Detached. Keep deliberate host private-index/object overrides and repository-declared alternates. Cover actual delivery/candidate preparation and a Boot isolated-write/apply flow.
Verification: all 239 root packages pass across disjoint required/rest commands; complete gitcmd/worktree/isolation race 306 passes; Boot race four passes. Exact preceding owner compiles and fails 18 regression events while seven controls pass. Full lint, vet and make check pass with byte-identical baseline. Windows/Linux builds compile; local literal Windows inventory verifies 25 required events and three package terminals, not native execution.
Problem: configured tasks used independent planner and executor root
counters, allowing an extra executor tool round after planning spend.
Root cause: Agent root binding began only inside each role's Run. A
plan-only turn never opened executor Run and could lose its accounting.
Fix: prepare the executor-owned root before routing, bind companion Agent
runs to it, and account legacy raw-planner usage at its logical terminal.
Track preparation by session object and host task scope. Adopt only the
preparing executor's binding after its own evidence reset; preserve clock,
immutable limit snapshots and captured old-job counters across continuation.
Keep local per-Agent gates/telemetry and all approval/cancellation boundaries.
Verification: required2 plus disjoint rest237 cover239 packages exactly
once,23980 test/subtest passes215 existing skips. Four full owner race
suites2786pass10skip; actualBoot shared and delegated token/wall16racepass.
Precise preceding Coordinator overlay compiles9fail4controls/2pkgfail.
LiteralWindows20named events/3pkg passes locally;6Win/Linux builds compile
only. Rootvet/full lint/makecheck/gofmt/diff pass,2663 baseline unchanged.
Refs #14025
Problem: Anthropic error, EOF and cancellation exits could discard usage
already reported by message_start or message_delta. Bounded consumers lost
the counts, while normal recovery reconstructed counts it already knew.
Root cause: the cumulative usage emitter was reachable only after
message_stop; consumers may stop immediately upon receiving ChunkError.
Fix: give each response one cumulative accumulator and delivery attempt.
Flush partial usage before explicit errors with existing Estimated identity,
retain cancellation-aware best-effort delivery on other exits, and compute
the original error classification before accounting changes sink state.
Keep exact committed usage, request construction and retry policy unchanged.
Verification: required builtin/boot plus disjoint rest cover all239 root
packages once,23966 test/subtest passes215 existing skips. Six full owner
race suites2615pass10skip; actualBoot4race events0skip. Exact preceding
decoder compiles and fails14events with4controls passing. Native-step
contract18events/2pkg passes locally;4Win/Linux binaries compile only.
Rootvet/full lint/makecheck/gofmt/diff pass; baseline2663 byte-identical.
Refs #14025
The prefix is read off the entry's own model list up to its last slash and left
out of how models are drawn, in every listing; ids and refs never change, an
alias wins, and models that would read the same keep their prefix. Fixes#14573.
Problem: an explicitly bounded Agent root omitted known native search usage,
allowing an extra normal tool round after a reported search answer.
Root cause: the search terminal finalized its independent request count and
reported usage without calling the accepted optional root observer.
Fix: observe the finalized record once before optional reporting. Preserve
query isolation, request counts, callback copies, unknown usage, native
completion, paid-backend consent and zero-limit behavior.
Verification: all 239 root packages pass through disjoint commands; four
complete owner race suites and real boot OpenAI/Anthropic HTTP regressions
pass. The compiled preceding owner fails seven events while nine controls
pass. Static gates, six cross-compiles and the literal 16-event Windows
contract pass locally; own native execution remains a hosted gate.
The sheet now covers the title bar too, its header standing in for the bar
(drag region, macOS light clearance, Windows buttons); the enlarge button's
arrows turn inward while enlarged. Refs #14418.
When a turn that ran a task from the session that dispatched this one ends,
its outcome and final answer go back as a message to that session. Only the
opener is answered, so a reply never starts a reply. Refs #13341.
A newly held message stamps the receiver through the existing unread fact and
announces itself with a quiet operator notice, which makes the window read the
session list again. Refs #13341.
Problem: a Windows run rejected three correctly refused capability calls
because the child conversation took 2.138s against a 2s wall-time check.
Root cause: the metric spans provider rounds and persistence, so it does
not establish whether target resolution refused before execution.
Fix: assert error/refusal guidance, Executed=false and no shell receipt for
all three results. Remove unused timestamps and preserve the existing 60s
completion guard. Production behavior and permission policy are unchanged.
Verification: the single root command passes all 239 packages, selected
boundary race checks pass 17 events, and a compiled executed-receipt test
mutant is rejected. Formatting, vet, lint and repository checks pass;
Windows/Linux boot binaries compile, without a native-execution claim.
Problem: an explicitly bounded root omitted billed command classifications
and external-content screening, allowing extra normal tool rounds.
Root cause: the shared triage terminal emitted usage without charging the
existing context-bound root counter.
Fix: observe terminal usage through the accepted private root helper before
the optional display guard. Preserve verdicts, screening notices, event
conditions, missing usage, normal telemetry and cancellation authority.
Verification: all 239 root packages pass in disjoint commands; full Agent
race and real boot HTTP/SSE matrices pass. The compiled preceding owner
fails ten events while five controls pass. Static gates, four platform
cross-compiles and the literal 15-event Windows contract pass locally.
Problem: an explicitly bounded Agent root omitted reviewer/advisor usage
already emitted by the shared bounded-call terminal, allowing extra rounds.
Root cause: normal-round accounting does not own auxiliary request usage.
Fix: connect the bounded terminal to one copied, replaceable context
observer bound to the original root counter. Preserve job cancellation,
normal telemetry, missing usage and zero-limit behavior.
Verification: all 239 root packages pass in disjoint commands; nine complete
owner/caller race suites and real boot HTTP/SSE regressions pass. Exact old
owners reproduce the gap; selected accepted auxiliary composition passes.
Formatting, vet, lint, repository checks and eight cross-compiles pass.
The 20-event Windows contract is locally verified, not native execution.
A local pane rides the hub's multiplexed stream, which attaches it only
once that stream is open. Until then the subscription has no position,
and the attach's watermark became its baseline as if nothing before it
mattered. A pane that read its transcript mid-turn and was attached after
the turn ended never received the reply or turn_done, and went on showing
the person's message alone.
The subscription now reads where the stream stands when it subscribes,
with the attach read of /events/replay. An attach that finds the stream
has moved on since reports what went by as a gap, and the pane rebuilds
from /history as it does for any other hole.
open_projects raises a fresh card naming every folder; approving adds the
folders to the project list and trusts them, vetted per folder like an extra
workspace root. Off unless the user's dispatch switch is on. Refs #13341.
Fold summary and failure-diagnosis terminal usage into the existing
context-bound root scope, including billed failures. Preserve local
normal-round telemetry and manual maintenance outside a root context.
Presets declare whether they relay other makers' models; the model menu
heading and the settings service list draw the service mark, while a relay's
model rows fall back to the letter chip. Refs #14538.
Pass the existing TaskBudget conversion to planner Agent Options. Cover
real HTTP planning reads, context overrides, disabled and unknown-cost
cases, executor fallback and request-prefix stability.
both ignored everything after the subcommand (or the name), so a typo like --jsno printed the human output and exited 0. now they exit 2 like mcp browse does.
The kernel answers POST /agent-presets/default with 204 and no body, but the
port read the answer as JSON, so a change that had landed reported
'Unexpected end of JSON input'. The call is status-only and uses post().
The plan bar sat inside the composer box and the open list had no obvious way
back out. It is now a card floating just above the composer; the open list has
a header (running and pending counts) and a collapse button at its top right,
and Esc or a press outside closes it through the shared dismiss path. The jump
buttons move up so the card does not cover them, and the conversation gets a
little bottom room while a plan exists.
Problem: restart-style MCP tests keep prior controllers alive while changing
server definitions or constructing a later session. Their background handshakes
and schema writes are therefore outside the test's advertised phase boundary.
The macOS job on #14531 observed a missing followed-tools notice, although its
precise interleaving was not captured and local old/new stress both pass.
Fix: close the owning controller before each transition in follow/unfollow and
accepted-tool restart fixtures. Existing idempotent Close drains the fixture's
MCP host and queued schema writes. Retain every notice, held-tool, provider
surface, actual-call and refusal assertion without retry, sleep or weaker gate.
Verification: six MCP effect test/subtest events and four existing deterministic
Close barrier tests pass under race. Forty fresh compiled processes pass each
before and after; they do not reproduce the hosted nil notice. Full lint, vet,
make check and four Windows/Linux amd64 builds pass. Full builtin/boot plus
disjoint root inventory continue separately; no production source changes.
Refs #14025
Refs #14133
Refs #14531
Problem: broad validation found three adjacent fixtures requiring selected
cost aliases or complete quotes even when their usage was reconstructed.
The shared quote repair correctly takes the existing incomplete-display path.
Fix: assert preserved original amounts, incomplete/partial coverage and absent
selected aliases for estimated wire, ACP status and persisted stats rows. Keep
reported-usage alias and legacy-row controls. Expand the existing Windows
inventory to all eighteen named events across seven packages.
Verification: affected consumer tests pass, with complete eventwire/ACP/stats
race suites at 351 passing events and no skips. The compiled preceding pricing
owner now fails fifteen regression events with three controls passing. Final
local Windows-step contract passes eighteen events; required-skip and missing
package-terminal controls fail. Full lint, vet and make check remain clean;
final original root validation continues against unchanged frozen Go sources.
Refs #14025
Refs #14133
Interrupted provider streams can reconstruct positive usage without terminal
reported counts. The shared quote owner forwarded that provenance but ignored
it when certifying complete cost facts, allowing recovered usage to make a
fully covered ledger.
Use the existing incomplete-usage result while retaining fixed-point amounts
and occurrence-time valuations. Reported usage, including rate-card estimates,
keeps its completeness. Existing quote fields, reasons and ledger versions stay
unchanged; no provider-visible request, permission or budget default changes.
Cover shared event/config adapters, persisted ledger coverage and real boot SSE
recovery with an actual file read. Add a Windows fourteen-event inventory in the
existing job. This is a monetary-fact prerequisite of the full concurrency plan,
not shared root-money admission or hard reservation.
Verification: fourteen focused events and the real boot race pass; compiled
preceding pricing fails twelve regression events with two controls passing.
The prior reader accepts the incomplete quote fixture. Root vet, full lint and
make check pass with the baseline unchanged. Eight Windows/Linux amd64 owner
builds compile; full root inventory and complete owner race continue separately.
Refs #14025
Refs #14133
Problem: a host wall budget could land the root Agent while delegated Agents
and Manager-owned background jobs kept independent start times or no wall bound.
Root cause: the opaque inherited root scope carried only token accounting; the
existing task time gate read each Agent's own clock after its tool rounds.
Fix: attach an immutable root task start/limit pair to that existing scope.
Preserve the start across continuations and snapshot new root overrides without
rewriting old job bounds. New tasks/reset use a new clock. The existing task and
fleet projection helper also carries this sidecar while keeping Manager-owned
cancellation/deadlines. Use existing time soft landing and finalization; keep
defaults unlimited, with no hard deadline, new timer, schema or permission.
Verification: frozen6730Go/fourmodules; required2+rest237 cover239packages,
23754test/subtestpasses/215existingskips. Complete Agent/delegation/jobs race
2652passes/10existing skips; actual boot background task/capability/fleet4race
passes; accepted14523sixGo selectedoverlay54racepasses; prefixguards4passes.
Exact preceding owner compiles and fails9events/2controls across2packages.
Windows17-event step passes locally and rejects skip/missingterminal controls;
six Windows/Linuxamd64 test binaries compile, not native execution.
Rootvet/full lint/makecheck/format/diff pass; baseline byte-identical2663.
Refs #14025
Refs #14133
Problem: a priced Agent could report complete task costs after a round omitted
usage, and estimated usage could certify a cost threshold.
Root cause: the shared HTTP attempt helper synthesized request-only usage
without its existing Estimated identity; the budget observer also treated
missing or estimated token accounting as a fully priced reading.
Fix: preserve exact HTTP counts while marking synthesized usage incomplete.
Keep only reported priced costs in the Agent subtotal, retain uncertainty for
the task lifetime, and land when the known subtotal alone reaches the limit.
Keep defaults, schemas, cancellation and finalization unchanged. Add real
OpenAI HTTP boot coverage and an explicit Windows test inventory.
Verification: frozen 6729 Go files/four module files; required2 +rest237 close
239 packages, 23754 test/subtest passes and215 existing skips. Complete five
owner race2407passes10existing skips; real HTTP boot race4passes; cache guards4.
Exact preceding owners compile with13failing events/5controls; producer-only
rollback4fails/1control; deliberate unknown-erases-known mutation1fail.
Root vet, full lint, make check and formatting pass with baseline unchanged;
six Windows/Linux amd64 owner/boot binaries compile (not native execution).
Refs #14025
Refs #14133
Problem: configured task token limits counted only each Agent's own provider
usage. Delegated Agents could continue spending outside the parent's reading.
Root cause: local runBudget instances never shared a root token total, while
background task/fleet closures start from Manager-owned contexts that omit
caller values.
Fix: bind a private atomic root counter to the existing task ledger and pass
its immutable per-run threshold through derived contexts. Project only this
scope into both background producers, preserving job cancellation authority.
Keep local telemetry, cost/wall gates, defaults, prefixes and soft landing.
Verification: compiling preceding owners9fail/2controls; preceding job-context
only4fail/2foregroundcontrols. Complete3owner race2639pass/10existing skip;
realboot5scenes+parent6racepass, explicit cacheguards4pass. Final239 package
inventory23710pass/215existing skip, required builtin/boot, rootvet, full lint,
makecheck, formatting and6Windows/Linux builds pass. New native inventory
requires20namedpasses/no skips; local jq and required-skip negative verified.
No hard reservation, root money aggregation, process kill, remote/durable
accounting or Coordinator cross-root scope is claimed. Refs #14025, #14133.
Cause: internal/runtime/isolation and the isolated task tool refuse with
isolation.unknown_id, unavailable, not_git, apply_conflict and
unsupported_arg, but the kernel catalogue words none of them. A tool card
renders say(code), so these refusals showed no reason in either language.
Fix: word the five codes in kernel.ts and en_kernel.ts, in the terms of the
tool path that emits them.
Refs #14025
Problem: whole-file rewrites can discard changes hidden by a later overlay
version, a paged read, or an oversized read that clears the previous guard.
Root cause: read_file renders one read and records another. Missing and
incomplete observations also collapse into the permissive never-seen path.
Fix: observe the renderer's decoded stream with bounded capture, refresh only
complete returned reads, retain older guards, and track first partial reads
explicitly. Keep never-seen compatibility and disabled protection behavior.
Verification: compiling old-owner controls fail five behavior regressions;
fixed focused race tests and real boot.Build checks pass. Full builtin,
root-package tests, go vet, make check and full lint are recorded in the PR.
Written bilingual security bounds and release notes accompany this slice.
A first partial read refuses replacement with the file's line count and the
read_file limit that certifies it. Without that, a default-limited read of a
file longer than 2000 lines can never be certified by rereading as told.
Refs #14025
Refs #14133
Problem: the no-index history search test could inherit a process catalog from
a temporary runtime fixture, so its asserted precondition was not established.
Root cause: shared catalogs deliberately outlive controllers. Several actual
boot fixtures and the shared unread harness omitted their existing fixture
cleanup, allowing the process pointer to outlive the temporary home.
Fix: use the existing shared cleanup in those owners, establish no-index state
explicitly, and preserve coverage for real active-catalog failures. Production
code, workflow gates, timeouts, defaults and baseline remain unchanged.
Verification: preceding fixtures compile and fail the lifecycle regression;
the provider-editor sibling control passes. Complete serve1798pass/5existing
skip and complete disjoint race inventory1798pass/5skip; root239 package
inventory23696pass/215existing skip, required builtin/boot, rootvet, full lint,
makecheck, formatting and Windows/Linux amd64 compile pass. Original Linux500
and the local fullrace5m alarm remain separate evidence, not claimed fixed by
an external rerun. Refs #14025, #14133; followup to #14505.
A mark that kept focus after a press set the rail's focus again whenever the
window handed focus back, so its neighbours stayed magnified and its peek stayed
open with the pointer elsewhere. Focus now moves the rail only when it is
focus-visible.
Foreground panic unwinding could publish a completed worker graph before its error was known, while legacy nil panics were swallowed by both foreground and background exits.
Classify return versus panic at one private delegation boundary, persist the failed transcript while owned, and release before terminal callbacks without letting old cleanup retire a reentrant replacement. Keep foreground panic propagation and accepted Manager finalizers. Cover real boot direct/capability dispatch, compiled nil-panic modes, error identity and ordinary outcome controls.
Settings and feedback refused with 'add a folder first' when no pane was open and
no folder was known, and the empty main area offered only Add folder. The kernel
already opens a session with no root in the app's own workspace, so the sheets
and the empty state now ask for exactly that.
Problem: legacy nil-panic recovery can publish a successful job after its
invocation or cleanup panics, and error-valued panics lose their cause identity.
Root cause: two boundaries treat a non-nil recover value as the only panic
signal and format panic errors without wrapping the original error.
Fix: share a private normal-return guard across invocation and finalization,
retain typed panic causes, preserve finalizer error classification and sibling
cleanup, and keep the existing invocation stack message without adding stacks
to finalizers. Move invocation recovery into its cohesive owning file.
Verification: compiling preceding owners fail six test/subtest events with two
controls passing. Actual compiled Manager child tests cover explicit panicnil
modes zero/one, invocation, cleanup and no-launch. Frozen required2/rest237
partitions close all239 packages with23656 passes/215existing skips; complete
jobs/delegation race549 passes and accepted14464 three-owner composition671
passes have zero skips. Independent required command has2349passes/66existing skips.
Vet, complete lint, make check and formatting pass at unchanged2664baseline.
Windows/Linuxamd64 owner builds are compilation only; exact-head hosted/native
and independent review remain separate gates. No Goexit, other-goroutine panic,
process-scope completion or crash recovery is claimed.
Problem: accepted job finalization in #14469 conflicts with the pending
cancellable writer registration paths in task and fleet.
Root cause: both sides own background cleanup. Closure-only cleanup misses
proven no-launch, while ID-only cleanup cannot protect replacement writers.
Fix: retain Manager-owned terminal callbacks and pass each returned writer
registration token through the shared jobCleanup option. Remove the obsolete
ID-only helper. Preserve the foreground token lifetime, accepted transcript
and progress finalizers, and every other upstream change in this ordinary merge.
Verification: the original full 239-package root command passes with 23668
test/subtest passes and 215 existing skips. Complete checkpoint/delegation/jobs
race passes 663 events, zero skips (one unchanged package cached); real boot
handoff and no-launch race passes ten events. Independent required builtin/boot
passes 2354 events with 66 existing skips. Compiling preceding callers fail
four no-launch scenarios plus parent; the Manager/token generation regression
passes. Vet, full lint, make check, formatting and diff gates pass, baseline
2664 unchanged. Six Windows/Linux amd64 owner compilations are compilation
only. Exact new-head hosted/native validation and independent review remain.
The reader menu opens from a promise callback, so its commit runs at
default priority and the effect that focuses the first item is a
separate Scheduler task (setImmediate). findByRole resolves from the
MutationObserver that sees the menu, then RTL drains with setTimeout(0);
when a millisecond passes before the next loop turn, the timer wins and
the assertion reads the reader, which still has focus.
Wait for the focus the way the other focus assertions in the suite do.
Problem: candidate groups need a common baseline while preserving the result
Store's source, managed-root and identity ownership.
Root cause: ordinary Begin captures afresh, while arbitrary snapshot/path
adoption would bypass the owning Store and its existing settlement contract.
Fix: add an opaque process-local Preparation, recheck source/workspace/Git
directory identities, and create independent results from the captured
snapshot. Extract the existing Begin bodies into private shared helpers.
No best-of, runtime, default or persisted-format activation is included.
Verification: complete 239-package root inventory, full isolation race,
independent builtin/boot command, vet, full lint and make check pass with the
2664 baseline unchanged. Compiling unsafe controls distinguish baseline
refresh and replaced-directory admission; the preceding Store implementation
passes the new preparation tests. Linked-source overlay passes; Windows and
Linux amd64 compile only. Native/hosted validation and independent review
remain separate gates.
Problem: local settings cannot load the file-backup catalog because both
machine settings URL forms return an untyped 404.
Root cause: file-backup handlers are registered only with account backup
routes, while the machine settings router registers a separate route set.
Fix: register the existing file-backup handlers with machine settings.
Normal runtime routes still register that same set once. Preserve host-only
checks, paired-device rejection, local-owner selection and mutation gates;
do not change payloads, encryption or persisted formats.
Verification: new regressions fail before the fix with plain 404 responses
and pass afterward for both machine prefixes. Focused machine settings,
file backup, account backup and mutation-gate tests pass. gofmt, go vet,
make lint, make check and the required builtin/boot tests pass.
Problem: switching settings categories retains an unrelated scroll offset, while a searched setting that loads later never receives navigation feedback.
Root cause: categories share one scrolling element and landing resolves the anchor once before asynchronous content arrives.
Fix: key the panel by category, clear pending landing only when a guarded category change succeeds, and observe target arrival until its feedback lifetime begins. Keep existing categories, layouts, disclosures and unsaved-change guards.
Verification: five regressions fail on the unmodified baseline; all 28 settings test files and 215 tests pass with the fix. TypeScript/Vite and repository checks pass. Browser verification confirms a tools panel at scrollTop 5747 switches to network at 0 and search opens the existing advanced contrast disclosure.
Problem: Network settings could be saved without edits, and pending reads,
saves or diagnostics could overwrite the state of a newly selected connection.
Connection tests also appeared to exercise unsaved form values.
Root cause: The form used one settings object for both saved state and edits,
with async completions that were not scoped to the owning connection.
Fix: Keep saved settings and the editable draft separately, disable no-op
saves, freeze submitted drafts, and identify diagnostics as testing saved
settings. Remount the form on connection changes and ignore completions from
unmounted owners. Preserve the existing expanded connection details layout.
Verification: The seven extracted regression cases failed on the baseline.
Network and i18n tests pass (10 files, 181 tests), including edit reversion
and loading/error coverage; tsc -b, make check and git diff --check pass.
Problem: the ordinary test synchronization append conflicts with upstream's
independently accepted fix for the same passive draft-persistence boundary.
Root cause: both sides replace the immediate heldDraft assertion with waitFor;
upstream additionally explains why the effect may follow the thumbnail commit.
Fix: resolve the sole conflict to the exact accepted upstream test including
its comment. Preserve all incoming dependency/UI changes and every original
runner-panic implementation Go file. This merge is required by a verified
content conflict, not unrelated base drift; no rebase or force-push.
Verification: final inventory of 239 root packages is reconciled from disjoint
required2 and rest237 commands, both exit0, with frozen Go/module inputs.
Isolation race covers all 51 top-level tests through the original completed
39 trees plus the remaining12 under the same5m budget; original full owner
command hit its cumulative alarm and is retained as exit1, not called pass.
Five affected frontend files have99 tests pass; production typecheck/build,
formatting, root vet, full lint and make check pass without baseline widening.
Exact merge-head hosted/native checks and independent review remain pending.
NewStreamDialer is how SSH reaches a remote host under the user's proxy
(remote/attach), and none of dialer.go was exercised: netclient sat at
47% against a 47.2% floor on a declared-sensitive path.
Four tests drive it against fake proxies on loopback:
- an http proxy gets CONNECT to the target with Proxy-Authorization, and
the credentials never also travel as Authorization;
- a proxy that answers 407 yields an error, not an open stream;
- an https proxy sees a TLS handshake first, so CONNECT and credentials
never cross in cleartext;
- a SOCKS5 proxy gets username/password auth and the target by name.
Each fails when the matching guard in dialer.go is removed. The
netclient floor rises from 47.2 to 65.8.
Problem: Studio job 121205496051 fails after the pasted image preview appears,
because the retained draft still contains no image.
Root cause: findByAltText observes the DOM commit, while holdDraft runs in a
passive effect. The test assumes those distinct boundaries complete together.
The unchanged merge passed a later external rerun, which does not repair the
scheduling assumption.
Fix: keep the exact single-image assertion for both heading and tab targets,
but await the draft condition with waitFor. Production UI and Go source stay
unchanged.
Verification: full Feedback interaction file 65 pass, restored focused cases
2 pass, typecheck pass. Two independent no-op-save controls each fail both
targets, proving persistence is still checked. Root vet, full lint, make check
and required builtin/boot pass at unchanged baseline 2664. The preceding
native/hosted evidence remains attached to af958f; the new head needs fresh CI.
Problem: ambiguous private registry records can be admitted or scavenged as
known dead-owner state.
Root cause: struct JSON decoding merges repeated members and folds case;
unknown-field rejection alone does not preserve the original declarations.
Fix: validate unique decoded names and exact schema spelling for records and
capacity objects before typed decoding and liveness checks. Keep writer
encoding, legal spacing/order, escaped unique names and existing resources.
Join a killed process before closing cooperative input in death fixtures.
Verification: complete 239-package root inventory and required builtin/boot
commands exit 0; mutation race 88 pass, pending wait/opaque composition 116
pass. The compiling preceding decoder fails 22 behavior test/subtest events,
with two controls passing. Formatting, vet, full lint and make check pass
without changing the 2664 baseline. Windows/Linux amd64 compile only; decode
microbenchmark overhead is measured and disclosed separately from product
performance. No runtime or default activation.
`.railhost` sets pointer-events: none, and only `.srail` opted back in. The
unloaded-history marker and the opened run inherited the none, so presses fell
through to the transcript. A style test now holds the rule.
Problem: an isolated task could write files, then lose its discoverable result
identity when a host runner or provider callback panicked.
Root cause: Store.Execute called Finish only after an ordinary runner return;
panic unwind bypassed both completed-result and inspection publication.
Fix: recover at the runner invocation boundary, retain an error-valued panic
as a cause, and publish the existing inspection identity under the result
operation owner. Do not infer physical completion, inspect for emptiness, or
delete work on a panic. Preserve ordinary outcomes and truthful live-only
recovery when bookkeeping cannot be admitted or persisted.
Verification: final 239-package inventory closes using 238 unchanged broad
packages and the final isolation race package; 111 owner race and 3 real boot
race cases pass without skips. The compiling preceding owner fails 11 cases
with 3 ordinary-error controls passing. Required builtin/boot, root vet, full
lint and make check pass at unchanged baseline 2664. Linux/Windows amd64 are
compile-only; hosted/native and independent review remain separate gates.
Refs #14025
Refs #14133
Problem: Windows PR smoke did not run jobs/delegation unit tests, and pure
delegation changes could skip native platform legs. Linux PR race coverage
also omitted the delegation owner tests.
Root cause: package routing predates the shared background resource lifecycle.
Compiling these owners as boot dependencies does not execute their tests.
Fix: run both complete packages on Windows with a retained JSON inventory,
require all15 portable lifecycle cases to pass, route pure delegation changes
to native legs and include delegation in the existing Linux race sweep.
Verification: platform-scope tests and pinned actionlint pass; the inventory
gate accepts actual local owner output and rejects missing-case, skipped-case
and missing-owner controls. Make check passes at unchanged baseline2664.
All6699 Go files match the qualified implementation commit; no product Go
source or deadline changed. Exact-head native/hosted qualification is pending.
Problem: background task/fleet can strand checkpoint writers, transcript locks
and progress resources when the Manager never invokes the supplied closure.
Root cause: resource ownership moves to closure-only defers before the Manager
checks invalid identifiers or its closed root context. Those branches bypass
the closure and its cleanup, while the caller suppresses its own ending.
Fix: register optional host finalizers at the Manager's managed-execution
boundary. Run all callbacks once before completion after actual closure return
or proven no-launch, preserve cleanup failures/panic and transfer the actual
task/fleet resources and uninvoked terminal projections to that owner.
Verification: final239-package inventory uses238 broad packages plus the final
jobs package after a deterministic liveness-test-only change:23596 test/subtest
passes/215 existing skips. Full owner race648 and real boot race5 pass without
skips; required builtin/boot2346 passes/66 skips. Original public regressions
and compiling predecessor controls fail. Selected token-adapter composition
passes40 race cases. Vet/lint/check pass at unchanged baseline2664.
The liveness pair uses a controlled clock; production thresholds are unchanged.
Managed-closure completion is not descendant scope or durable crash recovery.
Refs #14025
Problem: cancelled background task/fleet calls can remain behind checkpoint
admission while the shared writer registry also blocks queries and cleanup.
Root cause: registration holds the registry mutex across the barrier, durable
undo retirement, notification and Store persistence. Callers discard their
admission context and defer cleanup by reusable string ID alone.
Fix: reserve identified pending registrations, prepare outside the registry
mutex and serialize latest snapshot publication separately. Pass context at
all task/fleet sites and transfer token-bound cleanup to admitted jobs. Preserve
legacy APIs, completed undo retirement and active work across parent turns.
Verification: original root239 passes with23590 test/subtest passes/215 existing
conditional skips; required builtin/boot2346 passes/66 skips. Complete owning
race487 passes/zero skips; real boot handoff race5 passes/zero skips. Compiling
preceding-owner/caller controls fail the intended assertions. Vet/lint/check
pass without raising baseline2664. Linux/Windows compilation is not native QA.
The jobs-owner no-launch finalization gap remains explicitly separate.
Refs #14025
Problem: A foreground command can return bounded cancellation while cmd.Wait
is still blocked, so call return cannot establish resource-lifetime completion.
Root cause: The tracked wait owner exposes its logical result and cleanup
diagnostics but no separate observation of the still-running root Wait.
Fix: Register a stable root-wait channel before the waiter starts and close it
after Wait returns, before result publication. Expose it with the explicit
RootWaitCompleted name. Preserve existing execution, cancellation, retry and
error behavior. No production lease consumer or whole-scope claim is added.
Verification: Deterministic cancellation/retry/publication tests, portable Go
child execution and a real blocked-output process/lease regression pass. Root
Wait remains distinct from descendant or external-scope completion; nil means
no registered observation. Full local/static and platform evidence is recorded
in the PR without inheriting previous heads' native results.
Problem: Directory-scoped coordination cannot exclude writers that address a
source file through a hardlink outside that directory.
Root cause: Path containment and named inode identity do not represent an
unresolved write effect; reinterpreting old directory records would silently
change their resource promise and nested admission could reuse a weaker hold.
Fix: Publish explicit version-3 opaque effects in the existing registry,
coordinate them symmetrically against all path-bearing transactions, and
require effect coverage when reusing a parent lease. Preserve ordinary named
parallelism, model capacity, previous directory semantics and final OS-locked
grant authority. No runtime or default requests the new effect here.
Verification: Full 239-package root inventory and independent builtin/boot
commands exit 0; mutation race 82 passes/zero skips; preceding-reader binary
preserves live/dead new records; weakened-owner control fails eight cases;
selected wait-snapshot composition has 36 race passes/zero skips. Root vet,
full lint, changed-file checks and formatting pass at unchanged baseline 2664.
Windows/Linux amd64 compile only; local execution is macOS.
Problem: unfamiliar coordination records can be ignored during admission and
deleted after their OS owner exits.
Root cause: registry decoding discarded unknown fields, and version-1 resource
validation accepted every effect except capacity.
Fix: reject unfamiliar object fields and unsupported path effects before
liveness/scavenging, retain original bytes, and include record identity in the
typed protocol error. Preserve known version-1/2 records and omitted-effect
legacy semantics without changing writers or activating the runtime protocol.
Verification: complete 239-package root inventory, full mutation race,
compiling preceding-owner counterexamples, actual owner-process death,
known-record controls, selected waiting-observation composition, required
builtin/boot and static gates. Windows/Linux builds are compile-only local
evidence; exact-head hosted/native review remains separate.
Problem: A durable future settlement record must stop ordinary result
writers, including stale queued operations and destructive siblings.
Root cause: The per-ID mutex serializes operations but carries no durable
knowledge that a recovery owner has published a settlement fence.
Fix: Check host-rooted external fence metadata after operation ownership,
refuse any existing entry without interpreting its bytes, recheck context
and release only this attempt on refusal. Cover tools, Finish/Abort, empty
inspection, sweep, a real child process and actual boot/controller rebuild.
No journal writer, schema or complete recovery is introduced. Preceding
writers ignore this fence; future activation needs a cooperating protocol.
Verification: Full isolation race100 passes, zero skips; real boot race3
passes. Preceding owner compiles and fails15 test/subtests, including a
child process; predecessor boot owner actually writes source in both cases.
Final root inventory/required and static qualification recorded in PR.
Linux/Windows amd64 compilation is separate from native evidence.
Refs #14025
Refs #14133
User turns, retained failures and [[keep]] turns were budgeted as shares of
the window. On a 1M window under the 100K cost trigger those shares plus the
tail passed the trigger, so every fold was refused as still at or above it.
Each budget is now also held to a share of the trigger.
The folder and host labels on a pane tab were flex: none, so a long one
could not shrink: the title, the only part that could, went to zero width
and the close button was pushed past the tab's edge onto the next tab,
where a press activates that tab instead.
Both labels now shrink to an ellipsis and take at most 40% of the tab, and
the title keeps at least 4em. perf/tab-close-inside.mjs checks, in the
real stylesheet, that the close button stays inside its tab and is what a
press at its centre reaches, with a long title, a long folder, and both a
long folder and a long host.
Only a serve with --port-file or --open listened first; a plain serve printed the
banner, share link, token path and the non-loopback warning and bound afterwards, so
an address that can never work still looked like a running server. Every command now
binds first, the report uses the bound address, and the now-unreachable
bind-inside-RunGraceful branch is gone.
.prefs-col had a max-width and no inline margin, so once the settings window was
enlarged the column hugged the navigation and every extra pixel piled up on its
right. In the enlarged mode the column is now centred in the room beside the nav.
Problem: a candidate filename could act as a Git wildcard or magic pathspec,
writing an undeclared source file or dropping the actual literal result.
Newline-delimited hashing could also reinterpret a single filename.
Root cause: option separation was mistaken for literal path selection, and
ambient Git pathspec flags could override the host's intended selection.
Fix: centralize literal pathspecs for tree/index inspection and the shared
restore owner; C-quote hashing input by bytes; remove inherited selection
flags in the common host Git environment while preserving explicit flags.
Whole-workspace application reuses the shared writer. Keep the portable
stdout/stderr Git fixture repair aligned with the native CI follow-up.
Verification: complete 239-package root inventory, three-owner race, real boot
race, uncached required builtin/boot, compiling predecessor controls, six
Windows/Linux owner cross-compiles, formatting, root vet, lint and make check.
Native hosted checks and independent review remain separate gates.
Refs #14025
Refs #14133
withinExact used filepath.Rel, which ignores case on Windows, so a different
spelling of an absent file was reported as held there while heldWithin's comment
and TestHeldPathsCoverOnlyTheSameFileAcrossCaseSpellings require the filesystem to
prove two spellings are one file. Require the prefix bytes to agree as well.
The terminal's copy of /history's tool call had no argumentNormalization
field, so a restored transcript rebuilt the card from Arguments alone —
the proxy's effective text — and drew it as what the model wrote. The
original was not merely hidden; it never left the decoder.
Carry the host's normalization through HistoryToolCall into the
eventwire.Tool the restored transcript draws and inspects, following the
serve payload and the desktop copy that already do. The card still draws
only the effective arguments: showing the original is a UI decision for a
later change, and changing the card would also change a live call.
Register the pair as a declared subset of the serve historyToolCall in
wireparity, naming the six fields the terminal decides from. The exact
table is the desktop's and is read as a TypeScript interface, so a Go
reader belongs in the partial table, which compares both ways over the
declared names.
The helper used CombinedOutput, so on Windows, where core.autocrlf is true, git's
'LF will be replaced by CRLF' warning on stderr was returned with the blob id and
TestApplyPathsWithUntrustedModesPreservesTheIndex/candidate_type passed the pair to
update-index --cacheinfo. Standard output is what a caller reads; standard error
now only joins a failure message.
Problem: the newly executed native Windows owner inventory failed while
constructing its candidate-type fixture, before product application ran.
Root cause: gitIn returned CombinedOutput, so a successful hash-object CRLF
warning on stderr was concatenated with its object ID and fed to update-index.
Fix: keep stdout and stderr separate at the shared fixture owner, retaining
both in failure diagnostics. Pin autocrlf=true in the compatibility regression
so this contract is exercised on every host. No production or gate changes.
Verification: the preceding helper compiles and reproduces the same diagnostic
pollution locally; the complete worktree race, Windows compilation, required
builtin/boot, vet, lint and make check pass. Hosted native revalidation remains
separate from local and earlier full-root evidence.
A live holder keeps the OS lock for its whole lifetime, and its `.lease.json`
sidecar can be deleted by a person or a file scanner while the process still
owns the session. The probe answered free on a missing sidecar without looking
at the lock, so session list said idle, task list flipped a running item to
interrupted, and redact-sessions would rewrite a live transcript. It now asks
the lock whenever a lock file exists, and still creates nothing on the common
uncontended path.
A live holder keeps the OS lock for its whole lifetime, and its
`.lease.json` sidecar can be deleted by a person or a file scanner while the
process still owns the session. The probe answered free on a missing sidecar
without looking at the lock, so session list said idle, task list flipped a
running item to interrupted, and redact-sessions would rewrite a live
transcript. It now asks the lock whenever a lock file exists, and still
creates nothing on the common uncontended path.
Problem: green PR checks could omit complete isolation/worktree owner tests on
Windows and under Linux race; pure isolation changes could skip native legs.
Root cause: the smoke and race package allowlists lacked both owners, while
the platform classifier treated untagged isolation Go as neutral.
Fix: classify isolation paths as platform-relevant; execute complete Windows
owners with an uploaded JSON inventory, require both package terminals and
portable boundary tests, and include both packages in PR race. Replace the
redundant rollback subset. Preserve existing permissions, deadlines and cache
rules; do not alter product code.
Verification: compiling old-workflow/classifier regressions, exact inventory
positive/negative controls, final classifier/testenv suites, complete 238-package
root inventory, three-owner race, required builtin/boot, vet, lint and check.
Local execution is macOS; native hosted outcomes remain a separate gate.
Refs #14025
Refs #14133
The width a hand set is kept in --flow-w and outranks the stylesheet, so once the
reading column had been dragged, choosing the full-width preset changed --doc but
not the column and did nothing. Choosing a preset now drops the dragged width.
Problem: executable-only changes and equal-byte regular/link changes could be
discarded as already applied, and source metadata edits could be overwritten.
Root cause: baseline, candidate and live file versions retained only blob IDs,
collapsing distinct Git modes and file kinds into one identity.
Fix: compare Git mode and blob together, follow the checkout's trusted/index
mode rules, hash real links and emulated links with their actual Git semantics,
and reject unsupported transitions before any source write. Keep the user's
index and configuration unchanged.
Verification: complete 238-package root inventory, worktree/isolation race,
real boot race, required builtin/boot, compiling predecessor controls and
operation-lease composition. Formatting, root vet, lint and make check pass.
Cross-compilation is recorded separately from pending hosted/native review.
Refs #14025
Refs #14133
Problem: the rooted-lock rename regression failed on Windows before reaching
the lock operation.
Root cause: Go's direct os.OpenRoot uses a Windows handle without delete
sharing, while production opens the lock directory relative to its managed
Root using an Openat handle with delete sharing. The fixture conflated them.
Fix: open the test directory through its parent Root, matching the production
constructor. Retain the real rename/replacement and physical lock-location
assertions on every platform. Document the direct-root Windows limitation.
No production code, timeout or platform skip changes.
Verification: complete filelock race (25 passes), Windows amd64 compilation,
required builtin/boot, root vet, full lint and make check. The prior full root
qualification is retained separately; new hosted Windows evidence is pending.
Problem: concurrent Stores could inspect one retained result and then write
or remove it while another operation was still using its candidate.
Root cause: reinspection checked authority without retaining a common owner.
Paths, first Finish, Abort and expiry cleanup could bypass Apply/Discard too.
Fix: add directory-handle OS file locks and one persistent per-result mutex
namespace. Hold admission through reinspection, mutation and synchronous
cleanup; preserve lock identity, active owners and live-only Finish retries.
Forward path-query cancellation and keep legacy writer limitations explicit.
Verification: complete 238-package root inventory, owner and real boot race,
compiled predecessor regressions, actual prior reader/sweeper compatibility,
Windows/Linux owner cross-compilation, formatting, vet, lint and make check.
No new native-platform or independent-review result is claimed here.
Refs #14025
Refs #14133
actions.ts sat exactly at the 800-line ceiling, so any pull request that added a
single action id failed repolint. The 44 feedback.* ids are one concern and move
to actions_feedback.ts, the way the market, schedule and document groups already
live in their own files; the registry content is unchanged.
Problem: cancelled calls remain parked behind checkpoint exclusivity, delay
their provisional resource cleanup, and may later enter hooks or execution.
Root cause: condition waits do not consume the invocation context. Observer
cleanup also exposes a shared writer-count decrement that can run twice.
Fix: centralize context-aware barrier admission, recheck cancellation after
durable preparation, return idempotent permits and bind the Agent caller.
Preserve completed undo retirement and every other active owner's admission.
Verification: deterministic cancellation and owner-preservation schedules,
compiling predecessor behavior failures, real boot direct/deferred coverage,
durable undo reloads, full root inventory, owner/boot race and required suites;
gofmt, vet, full lint and make check with unchanged gate baseline.
The literal branches of applyOldStringEdit wrote the replacement verbatim, so a
multi-line new_string inserted bare LF into CRLF files while only the fuzzy paths
converted it. Each literal replacement now takes the ending of its own line, which
also keeps an LF region of a mixed file free of stray CR.
Problem: a Store could apply an old cached Git tree after another Store had already applied or discarded that result, resurrecting its bytes in the source workspace.
Root cause: cache hits skipped durable publication and candidate admission; Git objects survive removal of the worktree. Refreshing without comparing the previous observation would also silently widen an earlier path declaration.
Fix: always reuse durable recovery/inspection for lookups, keep finished caches out of Pending, and refuse changed baseline/candidate/target trees until fresh path admission. Preserve first-Finish live-only and unsupported-version checks. No persisted, permission, tool-definition or default changes. Cross-process settlement locking and journals remain separate.
Verification: final complete 238-package root inventory passes (217 pass/21 no tests;23310 test/subtest passes,215 existing skips,194 valid cached packages). Full isolation race52 passes and real boot focus31 passes with no skips. The accepted predecessor Store compiles and fails9 behavior events. Gofmt, vet, full lint, make check and diff checks pass. Additional required builtin/boot command is running; new hosted/native evidence remains pending.
Problem: worktree task dispatch silently discarded effort, step limits and configured subagent models; parent model aliases also lost live session effort overrides.
Root cause: isolation branched before ordinary task runtime resolution, and alias resolution reloaded config instead of retaining the canonical parent runtime entry.
Fix: reuse shared task/model/effort/budget resolution, pass ephemeral options to the unattended Build, and canonicalize parent aliases to their runtime entry. Existing unsupported isolation restrictions remain refused; best-of keeps its prior policy. No persisted, wire, tool-definition or default changes.
Verification: complete 238-package root inventory passes (217 pass/21 no tests; 23289 test/subtest passes, 215 existing conditional skips, 198 cached packages). Owning delegation/isolation race has 400 passes; real boot focus has 39 passes, no skips. Compiling old owners fail 18 runtime scenarios plus parent, with two automatic-budget controls passing. First-Finish composition race has 32 passes. Gofmt, vet, full lint, make check and diff checks pass. Supplemental required builtin/boot is still running; hosted/native results remain pending.
Problem: Windows CI rejected successfully recovered isolated output because the new boot assertion required LF bytes.
Root cause: Git checkout may apply core.autocrlf conversion; recovery preserves the expected text with CRLF.
Fix: explicitly enable autocrlf in the disposable repository and normalize CRLF for the content assertion, retaining same-ID recovery and source-before-apply checks.
Verification: the old assertion compiles and deterministically fails with CRLF on macOS; the repaired four boot regressions pass under race (6 test/subtest events). Required builtin/boot pass (2291 passes, 66 existing conditional skips); vet, lint, make check and formatting pass. Native Windows replacement CI remains pending.
Problem: isolated work could lose its recovery ID or be removed when the
first candidate inspection or completed-result publication failed.
Root cause: Finish treated inspector/persistence errors like successful
empty results, and Execute hid Finish errors behind a runner failure.
Fix: retain the candidate and original ID, publish a separate versioned
inspection marker without overwriting existing records, reuse rooted
admission and same-ID inspection, and report live-only retry explicitly
when recovery publication is unavailable. Join both errors. Keep normal
completed records unchanged and refuse unsupported inspection versions.
Verification: complete 238-package root inventory, isolation and real boot
race tests, compiling preceding-owner behavior failures, actual previous
reader/sweeper compatibility controls, formatting, vet, lint and check.
Local evidence is macOS only; hosted checks and independent review remain.
Problem: A reader's hooks can change files without participating in rewind
exclusion, retiring undo, or recording the checkpoint's coverage gap.
Root cause: The invocation caller selects checkpoint admission using only the
concrete tool's read-only flag, even though it already knows that its hook set
may write. The checkpoint owner is correct but this caller bypasses it.
Fix: Include possible tool-hook writes in the two existing admission/coverage
conditions. Reuse the existing writer permit and deferred cleanup. Keep empty
hook readers unchanged and keep tool execution, coverage, and effects separate.
Verification: Compiling original owners fail the actual callback/undo and four
boot regressions; fixed owner/checkpoint and boot race tests pass. Full root
inventory, required builtin/boot, formatting, vet, lint and make check are
qualified separately in the PR. Asynchronous hook process lifetime and the
barrier's existing cancellation limits remain explicit follow-up work.
Problem: A hook can change files before refusing a tool or outside a reader's
declared paths without notifying an opted-in workspace sink. Nested writers
also omit publication until an outer writer happens to complete.
Root cause: Invalidation is gated by concrete tool execution and classification,
and publication belongs to the batch caller rather than the invocation owner.
Fix: Track possible tool-hook effects separately from Executed, publish through
the shared invocation exit after cleanup, and share result metadata projection
between batch and nested calls. Unknown hook scope invalidates content, tree and
Git metadata without claiming a confirmed write or widening authorization.
Verification: Full root 238-package inventory passed; complete agent/event race,
eight boot direct/capability hook cases under race, original-owner compiling
negative controls, and provisional-lease composition passed. Formatting, vet,
full lint and make check passed without changing the baseline. Local evidence
is macOS only; optional sink wiring and evidence accounting remain separate.
Problem:
Aborting provisional admission can expire an unrelated live user wait or
retake paths deliberately yielded to a user. A pending wait is also lost
when the last writer or an earlier restoration finishes afterward.
Root cause:
Physical release reset run state, and one retained extent represented both
active and parked ownership. Restoration dropped its temporary protection
before reholding the original caller. Expired queued grants and retention
callbacks could contribute resources to the next run.
Fix:
Separate active and parked retention, advance generations at the final
participating run end, and reconcile outstanding yields whenever writing
holds drain. Keep restoration protection through rehold, fence late
admission and retention, and clean expired grants without dropping current
writers. Actual in-flight writes stay protected after their run ends.
Verification:
The submitted owner compiles and fails all 14 selected test/subtest failure
events across the new public-API schedules. Corrected full workspacelease
and agent race pass, as do actual boot race, all 238 packages in disjoint
required/remaining partitions, required builtin/boot, vet, lint and check.
Local evidence is macOS only; hosted checks and review apply to the new head.
Refs #14025
Refs #14133
Problem:
A refused write can pin a never-executed path through later model requests.
Repeated failed restoration can also report success to an expired caller.
Root cause:
Admission merges every request into the run-retained extent before local
preparation succeeds. Call cleanup only decrements in-flight accounting.
Resume keeps its once-only result in per-invocation locals, and refused
upgrades omit the terminal wait notice after registrar contention.
Fix:
Track identified provisional calls separately from retained extents and
publish their remaining union through the existing claim registrar. Retain
before possibly writing hooks or execution. Preserve earlier writes and
parallel claims, report failed cleanup conservatively, validate lifetimes
before and after reacquisition, and cache terminal callback outcomes.
Close registrar waits on refusal and clear completed cleanup's wait state.
Verification:
Complete 238-package inventory and required builtin/boot pass on the final
source; full workspacelease/agent race and focused real boot race pass.
Compiling original execution, resume and upgrade-wait owners fail the
selected deterministic regressions. Formatting, vet, lint and check pass.
Local platform evidence is macOS only; hosted checks and review are separate.
Refs #14025
Refs #14133
Gathers the 38 fragments since 2.50.0 into one note: video wallpaper, find
case/regex switches, shortcut sheet, paged transcript loading, a narrower
reading column, dispatch trust and the delegation write-safety fixes.
Problem: standalone UI route checks require the paging API to have landed.
Maintainer guidance confirms that this check should remain a landing
dependency until #14365 is merged and the UI branch updates its studio base.
Restore the original route scanner and keep the HTTP fallback integration
tests, which prove runtime compatibility independently of landing order.
Verification: the original route test passes in the combined API/UI tree.
The standalone failure remains expected until the API base update. Vet,
complete lint, check and builtin/Boot validation pass without changing
production code, timeouts or coverage floors.
Problem: optimistic children can write paths held by live sibling tasks,
including through dynamically resolved local capabilities.
Root cause: whole-workspace optimistic grants bypass explicit-fence wrapping,
child options omit the shared scheduler, and parent admission skips children.
Fix: inherit the existing scheduler and admit actual named targets at execution,
using the actor grant's root and one complete resource set. Retain parent and
explicit-fence ownership, opaque tool availability and existing permissions.
Leave observation roots, provider prefixes and tool definitions unchanged.
Verification: compiling old owners write held files in four real boot task/fleet
direct/capability cases; fixed cases refuse overlap and preserve disjoint writes.
Full238-package root, required builtin/boot, complete three-owner race and focused
real assembly race pass. Vet/lint/check pass without raising the baseline.
Native hosted checks and independent review remain separate delivery gates.
Problem: the API-only PR failed the English refusal catalogue check.
Root cause: splitting the implementation retained the refusal codes but left
their English wording only in the UI branch.
Fix: include the same two English translations in the API branch, without
adding UI implementation.
Verification: reproduced the catalogue failure; all 170 i18n tests, typecheck,
vet, full lint, check and builtin/Boot suites pass. Existing catalogue
assertions and budgets are unchanged.
Place the shared refusal translations in EN_KERNEL on both branches so the
independent API and UI changes compose without a translation conflict. All
170 locale tests and typechecks pass on each branch.
Problem: standalone paging UI failed the CI serve route contract.
Root cause: the scanner assumed every frontend endpoint was mandatory, while
paging explicitly supports older servers through full-history fallback.
Fix: declare only the exact paging call optional, require its fallback route,
and exercise the HTTP adapter and hook across unsupported and real failures.
Verification: reproduced the original failure; route tests, 19 frontend tests,
typecheck, vet, lint, check and builtin/Boot suites pass. Covergate tests pass;
unchanged computer coverage is 88.2% on macOS versus the 89.8% floor, also
reproduced on the sibling branch. Ubuntu coverage remains for CI.
Place the shared refusal translations in EN_KERNEL on both branches so the
independent API and UI changes compose without a translation conflict. All
170 locale tests and typechecks pass on each branch.
Problem: CodeQL found two new uncontrolled-path stat alerts in record admission.
The first validator also read a claimed Git administrative backlink before
confining that path to the host-opened common directory.
Root cause: comparing embedded paths with global os.Stat did not itself provide
a filesystem namespace boundary, and marker discovery still used path globs.
Fix: enumerate and read slots through the opened managed root, perform identity
stats through root-relative handles, and constrain candidate/common metadata
reads before opening a backlink. Derive effective identities from checked host
roots, retain unknown fields unchanged, and treat brackets in root names literally.
Existing alias resolution remains metadata-only and grants no content-read scope.
Verification: final 238-package root inventory and required builtin/boot pass;
complete isolation race has 32 passes and real boot isolation race has five,
without skips. Six old-owner behavior groups compile and fail, namespace and
literal-directory regressions pass, and vet/lint/check retain the same baseline.
The replacement hosted CodeQL verdict and independent review remain pending.
Problem: the transcript paging UI conflicts with the upstream first-save bookmark fix. Root cause: both changes update the Pane-to-Transcript boundary, and retaining the runtime-id fallback loses unsaved stars on first save. Fix: merge studio f96ec2f869, retain session-path-only bookmark ownership alongside paging, and exercise both full and paged restoration. Verification: the upstream regression fails with the old scope and passes after resolution; 37 focused tests and 791 frontend files / 6343 tests pass, as do the production build, go vet, complete make lint and builtin/Boot suites. No lint baseline or deadline was changed.
Problem: a rebuilt store can trust a copied or mismatched durable record and
use its embedded candidate paths outside that record's actual managed slot.
Root cause: discovery checks the claimed workspace but does not bind snapshot
and candidate identity to the host-opened repository and observed marker slot.
Comparing Git markers only by inode also accepts cross-candidate hard links.
Fix: centralize record admission before every durable consumer; verify managed
directory membership, source identity, prefix roots and the linked candidate's
actual marker/backlink. Retain rejected records without rewriting metadata,
and resolve legacy identity through filesystem checks without launching Git.
Verification: five compiling old-owner negative-control groups fail as expected;
final 238-package root inventory and required builtin/boot pass, owning isolation
race has 30 passes and real boot isolation race has five, all without new skips.
Formatting, root vet, full lint and make check pass with the baseline unchanged.
Native hosted checks and independent review remain separate delivery gates.
An unsaved conversation scoped its rail stars by the pane's runtime id, and
the first save switched the scope to the session path, which reloaded from
an empty key: the star disappeared. The id is also a per-launch counter
(r1, r2, ...), so the key it left behind was read again by whichever unsaved
conversation pane r1 held after the next restart.
An unsaved conversation now keeps its stars in memory, and the first save
writes them under the session's path, merged with anything stored there.
The pane passes the session path alone as the scope.
TestFinalObservationIsBoundedAfterACompletedAnswer gave Run one second
after cancel (three after the deadline) to come back. Run still seals the
contract and the bundle after its context ends, because the first lock
attempt does not wait; locally that takes about 18ms, but on a loaded
windows-latest runner it overran the window and the test reported a held
observation that was not there (studio runs 40110025629, 40064086851).
The observation the test holds never returns until it is released, so any
finite wait catches the regression; use testenv.Budget for both waits.
Recovery conflated a failed Git inspection with an empty result and erased
the durable marker before attempting worktree removal. Return inspection
errors without cleanup and report the existing unavailable refusal instead
of forgetting a valid result ID.
Exercise apply path discovery, Apply and Discard against real retained
worktrees after restarting the store and temporarily hiding Git. Preserve
marker bytes and work, then retry the same ID after Git returns. Keep age
expiration and pre-publication Finish recovery as separate lifecycle units.
Separate review-role FileViews from the executor and bind direct/deferred
local tools to one fresh record for each role. Install the planner frontend
before binding so its ledger/audit and view ownership survive boot wiring.
Keep the generic read-only filter a same-actor operation, and preserve the
existing nil-runtime planner adapter without changing its tool surface.
Add four real-boot stale-overwrite regressions, runtime/clone accounting
checks, a generic-filter compatibility case, and written security bounds.
The compiling old-owner overlay reproduces all four overwrites.
Guardian's visible registry was filtered, but its read-only capability proxy
could resolve an executor writer or a declared host mutation. Set the
permanent execution flag in the session constructor so actual targets are
checked before they run, without changing the review's executor approval.
Add local resolver and real boot regressions, with compiling old-owner
failures for both mutation classes and an actual file overwrite. Document
the authorization boundary and the separate file-view ownership concern.
The drag floor was the readable measure itself (760), so the two presets were the
only widths a hand could reach on a typical window. A hand now reaches down to
640; the readable measure stays the untouched default and what Home returns to.
Problem: a child's read can refresh the parent's stale overwrite guard,
allowing the parent to discard an edit it has never read.
Root cause: copied file tools share FileViews, while deferred local tools
resolve through the root registry even inside nested children.
Fix: bind one fresh view per child to its direct and deferred file tools.
Keep disabled protection, tool identity, grants, read-only execution and
stable schemas intact. Install bindings on fresh capability frontends only.
Verification: original-owner controls reproduce the real boot overwrite;
a parent-pointer mapping control fails four nested cases, while the actor
binding passes all six shared-runtime/clone combinations. Root package,
owning race, composition, vet, check and lint evidence is recorded in the PR.
Refs #14025
Refs #14133
A dispatch target is resolved against the user's sidebar and the dispatch
switch is already the user's consent, so stopping on the folder's trust card
asked the same person twice. The folder is now recorded as trusted and the task
goes through; a folder that cannot be trusted whole still keeps its pane open on
the card with the task unsent.
Problem: Both extracted PRs became modify/delete conflicted while their local
qualification was running.
Root cause: Upstream removed FIRST_RUN_WORKSPACE.md in #14367 after the slices
had repaired its inherited lint metadata and paragraph violations.
Fix: Accept the upstream deletion and remove the now-obsolete prerequisite
repair. Retain every feature, regression and security-document change.
Verification: make check passes. The staged merge changes no Go file relative
to the independently qualified slice heads; their full package/race evidence
remains explicitly scoped to those source trees. Hosted checks must run on
the new merge heads. No unrelated production change or baseline relaxation.
Refs #14025
Refs #14133
Problem: Both extracted PRs became modify/delete conflicted while their local
qualification was running.
Root cause: Upstream removed FIRST_RUN_WORKSPACE.md in #14367 after the slices
had repaired its inherited lint metadata and paragraph violations.
Fix: Accept the upstream deletion and remove the now-obsolete prerequisite
repair. Retain every feature, regression and security-document change.
Verification: make check passes. The staged merge changes no Go file relative
to the independently qualified slice heads; their full package/race evidence
remains explicitly scoped to those source trees. Hosted checks must run on
the new merge heads. No unrelated production change or baseline relaxation.
Refs #14025
Refs #14133
Problem: A snapshot-only regression does not establish that the shared tree
reader also protects the source workspace before Apply publishes a candidate.
Root cause: The old cached index can hide a user's intervening edit from the
whole-tree drift check, allowing Apply to return success and overwrite it.
Fix: Exercise actual snapshot/candidate/Apply assembly on disposable real Git
repositories. Require ErrWorkspaceMoved and unchanged user bytes. Document the
existing Git driver confinement and the additional rehashing cost explicitly.
Verification: A compiling overlay of the original production file fails for
both wrong success and actual overwrite; the repaired owner passes. Full final
worktree race passes (48.247s). Broader root and static qualification records
are retained separately; production code is unchanged by this test follow-up.
Refs #14025
Refs #14133
Problem: A candidate snapshot can contain old bytes after the working file
changed while preserving its size and mtime.
Root cause: Copying the source index carries Git's trusted stat cache and
assume-unchanged state into StageAll, so staging can skip the actual bytes.
Fix: Preserve the copied index's staged tree, rebuild the private index from
that tree, then stage current content. Handle an unmerged private index first.
Keep staged ignored inputs, the real conflicted index, HEAD and refs intact.
Document concrete Git/path/authority boundaries and the unchanged need for
source coordination. Include the inherited documentation-lint prerequisite.
Verification: All three cache-policy variants fail against the original
function through a compiler overlay and pass with the repair. Full worktree
race passes, plus final expanded cache and conflict regressions. Check, lint
and root vet pass. Full builtin/boot exits zero (22.243s / 444.076s tests),
using a measured 15-minute package alarm with no product-deadline change.
The exact-commit full root sweep and hosted checks remain separate evidence.
Refs #14025
Refs #14133
Problem: Task-length write claims cannot independently represent permission,
assignment and the physical mutation lifetime across cooperating runtimes.
Root cause: Shared admission, input preconditions, cancellation and release
have no common short-transaction owner that callers can adopt independently.
Fix: Extract the mutation resource/queue/capacity owner and content versions
from the preserved concurrency implementation. Add minimal non-blocking legacy
claim compatibility and exception-safe publication cleanup. Keep all runtime,
isolation, hook, controller and default activation outside this review slice.
Document authority, actual sinks, compatibility and unenforced boundaries,
and map the remaining source into dependent review units. Normalize an
inherited design-document lint prerequisite without relaxing its baseline.
Verification: make check, full lint and root vet pass. Owning-package race
passes, including real subprocess death and a strengthened cleanup regression
that fails without the cleanup via compiler overlay and passes with it.
Builtin passes. The full boot invocation reaches its 600-second package alarm;
all 811 top-level tests close in four inventory-verified groups (808 pass,
3 conditional skips, no missing/unexpected/failing selected tests). Linux and
Windows amd64 test binaries cross-compile; native platform and hosted CI
evidence are separate gates. The remaining root-package sweep is in progress.
Refs #14025
Refs #14133
Problem: searching for a message outside the initial page could remain incomplete after one fast response. Root cause: the search effect depended only on busy/hasMore flags that React may batch back to their previous values. Fix: advance search on committed page revisions and release the completed single-flight request before publishing that revision. Verification: failing-before revision regression, 28 focused tests including scroll and anchor neighbors, and a browser replay fetching four pages and finding the first message.
Problem: full transcript projection and transfer scale with conversation length. The session store now owns stable page snapshots and cross-page context, exposed through optional controller reads and an additive HTTP route. Existing full-history clients are unchanged. Validate through real Boot/resume, full-projection equivalence, cursor invalidation and append stability, request authority and foreground preemption. Focused history tests, race checks, Go lint and release-note tests pass; broad suites are recorded in the PR.
Rebase the UI portion of #14087 onto current studio, preserving regex/case search and session-scoped navigation. Use optional paging with explicit unsupported-route fallback and retain mounted controls when prepending. Browser verification exposed zero-box display-contents wrappers; share rendered-box geometry with the landing writer and add a failing-before regression. Verification: 37 focused frontend tests, TypeScript checking, production build and wide/narrow light/dark browser renders; full-suite results and backend dependency are documented in the PR.
Define the optional history-page response and typed refusal translations shared by the API and UI splits. No caller switches to paging in this commit. Verification is performed on each independent split before publication.
Documents the last coupled first-run decision: with nothing remembered,
the first pane's workspace root falls back to the user's home directory
(pickLaunchWorkspace), so a session that was never given a chosen project
reads as temporary. Proposes a kernel-provisioned auto workspace instead,
modelled on MiMo Desktop's session-first behaviour (auto-created session
directories, global provider config, optional projects), and maps it onto
the existing launch chain, session store and workspace switch.
Keep the install slot claimed until a cancelled download finishes cleanup, preventing it from cancelling a new install or deleting its files. Add regression tests and a release note.
Expose download cancellation through the update API and Versions UI. Cancellation waits for the transfer to stop, removes its partial and cached artifacts, and reports a distinct non-failure state. Automatic updates do not retry a cancelled release but can proceed when a newer version is offered.
Off means nothing sleeps, cap included: a person who never turned the
setting on kept losing the least recently used page's renderer when a
seventh page opened. The cap now runs only on a finite threshold, the
same one the sweep reads.
The rx-page-sleep desktop preference decides it, default off: prefs.js
owns the threshold, the shell reads it at boot and on every preference
save, and Settings -> Appearance carries the toggle. A woken page also
comes back fitted into its panel at the scale it had before the sleep.
Five real Chromium pages through BrowserViews at a tiny threshold: the
working set they cost, the working set the sleep returns, a wake that
must answer against the page as it loaded, and a command the sleep
outlives that must fail rather than hang. Run with pnpm sleep-live.
An agent page's renderer is hundreds of megabytes the Studio pays for
whether anyone is watching. A page untouched for fifteen minutes and not
on screen is closed whole — address and a picture kept — under the same
targetId, so the kernel's handle survives; the next command, show, or
typed address rebuilds the view, replays the domains the kernel last
enabled (a domain switched off mid-session stays off), and answers only
once the page has loaded — a mid-load answer would describe the page it
was, and the kernel could not tell. A command the sleep outlives is
failed fast, for the session that dies never settles it and the kernel,
told nothing of a sleep, would wait on a page that will not speak. Six
awake pages at most — a wake yields no room: the page asked for
outranks the pages left alone — and eighteen asleep ones; past that the
oldest is buried and the kernel is told, the way a browser discards
abandoned tabs. REASONIX_NO_PAGE_SLEEP puts the feature away.
It sized itself 42px inside the pane on purpose; pulling that to 34px to match
the text moved the mirror's wrapping off the caret's in composer-reflow.
Opening the todo list grew the composer, which shortened the scroller, which
re-measured the rail: every mark moved up and the two jump buttons went with
them. The panel now floats above the composer instead of taking its room.
`--flow-w` went to `:root`, where a committed value on `.app` outranks it;
the sidebars only gave way on release, and the composer never followed at all.
Each key cell was as wide as its own text, so the descriptions started at six different positions. A shared minimum on the key cell gives them one column.
`groups` is a state slice; after a rewind shortens `marks`, a stale fold still pointed past its end and `marks[v.from]` was undefined. The fold is filtered to what exists.
`useSidebarOpen` dropped the promise with `void`, so `Workspaces` never saw the rejection and the row stayed on its old selection. It now returns the open's own promise.
`en_export.ts` repeated 我 and 会话, and `i18n.test.ts` rejects a key that lives in two catalogue files. The headings now use the entries already there. The menu test follows the second export row.
`^` and `a*` match the empty string, so the counter reported hits the painter had no range for. Both sides now skip them. Transcript.tsx keeps its 800-line ceiling by typing the mode inline instead of importing it.
The painter still searched the DOM for the literal text while the counter honoured the new modes, so a regex hit had no range to scroll to and a case-sensitive count did not match the painted one. Both now share one finder, a regex match advances past its own length, and a pathological pattern is cut short instead of freezing the window.
prompt_tokens_details with cached_tokens: 0 is the endpoint reporting the
split, so miss is derived from prompt - hit instead of staying zero and
vanishing into the unknown bucket; a hit above prompt_tokens is a broken
report and clamps to it. The overview and the meter floor the rate, so 100%
means every split token hit.
A shortcut sheet in the settings.
Every chord the window answers to, on one page: find, search, the three views, stepping through
your questions, the workbench toggles, send, close, and the width keys. Chords are written the way
the platform writes them, so macOS shows ⌘ where Windows and Linux show Ctrl. It is a page of the
settings table of contents, so searching for "shortcut", "Esc" or "运行分析" lands on it.
The strings live in `en.ts` with every other catalogue entry: the i18n guard requires one
catalogue file, and an earlier revision of this branch split them out and failed it.
Make run analysis easy to enter and to leave.
- The entry in the window bar now carries its name. It needs an inline layout for that: the
button is a 30px grid cell, so the icon and the label stacked and the text broke one character
per line. `.chrome .thbtn.top-action` takes over the layout for this variant and keeps the
label on one line; below 1180px the label steps aside so the other controls stay put.
- The analysis page has a back arrow beside its title.
- Picking a conversation that is already on screen returns its pane to the reading view instead
of re-opening it. That rule now lives in `sidebaropen.ts` — partly because it is a rule about
panes rather than window wiring, partly because `App.tsx` was 11 lines over its ceiling with
it inline. It has its own test: open pane returns to reading, unopened opens a pane, a folder
request opens a pane, and asking twice lands twice.
`Pane.tsx` differs from `studio` in one line (`onBack`), so the `useAgentRequest` wiring from
#14291 stays intact.
Match case and regular expressions in the find bar.
`usefind` now hands back `regex`, `caseSensitive` and `setMode` with the rest of its state —
the previous push declared them on `Finding` and never returned them, which is what
`frontend-next` caught. Changing either reading restarts from the first hit.
Rewritten against the shape the kernel actually writes. A transcript line is a provider message
(`role`, `content`, `tool_calls`, `host_authored`), so the export keys on `role`: user and assistant
only, skipping host-authored user lines, tool results and system lines, and skipping assistant turns
that carry only tool calls. Headings go through `t()`. Native archives keep their `records` instead of
exporting an empty list. The test now uses real JSONL lines and covers the native-archive case.
The controller runs only `/mcp connect` (#14167), so `/mcp add x` now
answers "unknown /mcp subcommand". Both guides and `reasonix mcp help`
still sent a running chat there to save and connect a server. They now name
`n` in the terminal /mcp panel, which installs through the same
POST /mcp/install as Desktop's Add and connect. The guides also name `p`,
the panel's project install, which InstallMCPServer writes to the
project's `reasonix.toml` (MCPConfigPathForEntry -> ProjectConfigPath).
TestTheDocsNameOnlyMCPAndSkillsSubcommandsThatRun reads docs/*.md and the
READMEs and holds every `/mcp <sub>` and `/skills <sub>` they write to the
subcommands completion offers, which are the ones the controller runs.
TestTwoAsksRetakeTheClaimOnlyAfterTheLastIsAnswered answered the first ask
as soon as two asks were registered. Ask registers before it yields the
workspace claim, and Yield reads the claim log from disk before it counts
the waiter, so on a slow Windows runner the second ask could still be short
of the yield when the first one resumed. The first resume then saw no other
waiter and took the claim back, and the test reported the claim retaken while
the second ask waited (studio 39941591112, windows-latest).
The test now waits for the second ask's prompt-queued notice. Ask reaches
lockPromptFor only after its yield, so the notice is the point at which the
second ask is waiting on the user.
CI on Windows failed both new assertions: the detail named
`c:\outside-watch\reasonix cli.desktop` and the temp deliverable came
back lowercased, while the tests compared against the path spelled as
constructed. `evidence.normalizePath` lowercases on Windows, because the
ledger needs one identity per file and Windows hands back either case.
Compare against that identity instead of the constructed spelling: the
rule under test is about which paths are reported, not how the host
happens to capitalise them.
The gap names writes the host watches nothing of. A folder the person
granted as workspace is walked with the root and observed the same way,
so a write there is work product and must not carry the caveat.
This pins the rule at the boundary extraRoots decides, so a later change
cannot start reporting exactly the deliveries the grant exists to allow.
`Caches` read the process's home directory itself, which put the package two
reads over its `process-input` budget and the repo one over its ceiling. Both
lists answer from the same fact, so it is read in one place and passed to each.
The cache list also gained the two entries `Dirs` already had on the platforms
that have them: `$HOME/.cache` everywhere, and `Library/Caches` on macOS. A
cache filter that named only `go`, `.cargo` and `.npm` would report a Unix build
cache as out of sight on the one platform whose caches live under a different
directory name.
Two defects, both found by CI on Windows and both in this PR's own new code.
`underScratchOrCache` asked `hostwrite.Dirs()` for the toolchain caches. That
function answers what a jail grants a command, and Windows jails nothing, so it
returns nil there: every build cache write was reported as out of sight on the
one platform where the list is empty. `hostwrite.Caches()` answers the question
actually being asked — where does a build write through — and is defined on every
platform. The two questions stay apart because conflating them is what made a
Windows build produce a caveat about its own module cache.
The one-off directory `sessiontemp` hands a turn is recognised by its position,
not by its name. It is a direct child of the host's temporary root, so that root
is read from the host and the first component below it is compared against the
prefix. The previous version walked every component, which exempted any folder
deeper in the tree that happened to spell the prefix, and it assumed the root was
named `/tmp`, which is not where Windows puts it.
The tests that pinned these edges set `HOME` and `TMPDIR` and then read the
result, which is why they agreed with the code on Linux and disagreed with it
everywhere else. `HOME` and `TMPDIR` are POSIX-only names: on Windows the
temporary root comes from `TEMP`, so `t.Setenv("TMPDIR", …)` changed nothing and
the assertions ran against the real root. They now call `disposable` with the
roots as arguments, and build paths from the host's own temp root, so each
assertion is about the rule rather than about an environment.
Tested: the thirteen out-of-scope tests, the packages this PR touches, and
`GOOS=windows go vet` on `internal/runtime/agent` and `internal/base/hostwrite`.
A turn that wrote a file outside the workspace and then answered ended with a
`done` receipt and no gaps. The host saw the tool call succeed, but its
observation only covers the workspace and its granted extra roots, so the write
was dropped before the report was built: no mutation, nothing to verify, nothing
to say. `final_readiness` agreed — it judges a turn by whether it has a writer,
and a write it cannot see is not one — so the turn ended as a quiet success
while the work it claimed had landed nowhere the host could check.
Record those writes as a gap instead. `outsideObservedWrites` reads the turn's
own successful write receipts and keeps the paths that are outside the workspace
and outside what the host owns: the session scratch directory, toolchain caches.
Two exemptions carry the weight. A public temporary root is not exempt — a
person who asked for a file there asked for something to keep — and neither is
`bin/` under a cache, because `go install` puts the user's own executable in
`~/go/bin`. A scratch directory earns its exemption only as a direct child of a
temporary root, so a folder is not exempt for spelling the prefix.
Reported, never enforced. The verdict drops from done to partial and the receipt
names the gap; the turn ends exactly as it did before. A person who asked for a
file outside the workspace got what they asked for, and what they are owed is
the line saying that part is out of sight, not a turn that will not end. For the
same reason the gap is a notice, not a warning.
CompletionReceipt also stops discarding the report's own gaps when the verdict is
unknown. It returned only the claim-yield and mod gaps there, so a turn the host
could not judge — a plan with no criteria, writing only outside the workspace —
went back to saying nothing at all.
Blast radius: the completion report on turns that write outside the workspace,
and the receipt the host renders for them. The readiness gate, Delivery, Goal,
cancel and budget paths do not read the report, so none of them is affected.
Neighbouring behaviours tested: a turn that writes outside the workspace is not
reported as finished; one that stays inside keeps its quiet ending; a plain
answer and a read-only investigation end with no caveat and no continuation; a
scratch probe carries none; a deliverable in the public temp directory does; a
lookalike directory and an installed executable are not exempt; an unfinished
todo list still holds the turn; an unjudged turn keeps its gap. Desktop
`ReceiptCard` gained one case, with the three catalogue entries the i18n gate
requires; typecheck and the full vitest run pass.
Scope: this reports a write the host cannot see. It does not decide that a turn
which never wrote anything was unfinished — a read-only multi-step turn that
stops early still ends as it did.
A background sub-agent writes its work into its own transcript and its tool
calls, never into the job that started it. The job's activity stamp therefore
never moved, and the watchdog judged the run by bytes on streams that stay
empty: a busy sub-agent read as a job that had been silent since it started, and
a stalled notice went out about work that was progressing.
Bind the child's progress to its job at beginExecution — the one boundary every
delegated run crosses, foreground included, where a context with no job binds
nothing. A finished tool call is one reported step; token deltas refresh the
activity stamp at most once every two seconds, because the watchdog reads
minutes and the child emits far more often than that.
Reported steps are kept apart from output text rather than folded into it. They
answer different questions — whether the child advanced, and whether it wrote
here — and a caller decides from the pair. `wait` says both, and a stalled
notice says what the job had reported before going quiet, since a job that
worked and stopped needs a different next move than one that never got going.
Tested: internal/tools/jobs (a job that writes nothing stays alive while it
reports progress; wait reads steps separately from output), internal/tools/builtin
(progress wording per reading), internal/runtime/delegation (binding at
beginExecution, a finished tool call reports one step, deltas are paced, a
foreground run stays unbound), internal/contract/event, and desktop
frontend-next (typecheck; full vitest run, 6120 tests). The desktop stalled-notice
sentence had no English for its new wording and the i18n gate caught it; the
three entries are added here.
`wait` accepted any `timeout_seconds`, and omitting it meant blocking until
every job finished. A sub-agent that never exits then holds the turn open with
no way for the caller to tell a long wait from a hung session: the tool result
is the only thing that returns, and it returns only when the job does.
Cap one wait at 300 seconds and apply that cap when the timeout is omitted too,
so omitting it means "wait for the jobs" rather than "wait forever". The result
says when the ceiling was reached, and only then: a job that finishes first
returns before it, and claiming a 300s wait that never happened would be a
second false report in a message whose whole job is to be accurate.
The schema states the same bound the runtime applies. It previously promised an
unbounded wait, which the tool no longer performs.
Tested: internal/tools/builtin (wait ceiling applied and reported, a 1s wait
returns progress instead of holding), internal/tools/jobs, and the boot golden
recapture for the changed tool schema. internal/assembly/boot has one
pre-existing failure on this host (TestEffectSessionScratchIsReachableByItsSpelling)
that reproduces on the unmodified base: bubblewrap cannot create a namespace
here, so the sandboxed bash call the test relies on refuses to run.
absorbInterruptedCopy called os.Lstat itself, which CodeQL reports as a new
path sink in changed code. sessionArtifactExists is the store's pre-existing,
baselined reader for the same question: the copy exists when its transcript or
an owned sidecar does. It answers bool, so missing and unreadable look alike;
the import's callers already accept that, and a copy that is there but cannot
be read still reports its error where it is loaded.
CodeQL flagged the two sidecar paths as new sinks once copyBranchMetaSidecar moved into migrate_copy.go: the os.ReadFile(BranchMetaPath(srcPath)) body was baselined in migrate.go, and a moved copy is not. The function moves back to its original position with only the error return changed, and completeBranchMetaSidecar now asks the pre-existing LoadBranchMeta reader whether the destination sidecar is there instead of calling os.Stat itself: ok means present and never rewritten, an error means unreadable and never clobbered, and only a clean absence proceeds. sameDirPath (a pure path comparison, no filesystem access) moves to migrate_passes.go so migrate.go stays inside its ratcheted file-size budget.
An import publishes the transcript first and the branch sidecar that carries
what it means second. A run stopped in that window left a transcript with no
sidecar, and the retry treated the transcript as completion: the flat import
healed only subagent artifacts and stamped the marker, and the v4 import only
healed the title. Both now finish the sidecar, refuse a failed sidecar write
as incomplete so the next run retries, and leave a finished copy untouched.
A v4 copy that is still a prefix of 1.x is brought up to the source; a copy
continued in 2.x keeps its own turns.
A power loss on NTFS can leave a .meta sidecar extended but never flushed, so it holds only NUL bytes. LoadBranchMeta reported that as a decode error, which made the recovery-lineage walk refuse to archive anything in the folder and kept UpdateBranchMeta from ever rewriting the file. The file records nothing, so it now loads as a missing sidecar and the next save replaces it.
Signed-off-by: ShryukGrandhi <[email protected]>
loadMemory returned (false, nil) for a read error, so every caller treated a
present-but-unreadable fact file as missing: it vanished from /memory, the
desktop panel, the memory tool and recall, and a save by name then minted a new
id at revision 1 over it with no snapshot, losing the old identity and history.
loadMemory now returns a typed FactReadError. ListActive reports unreadable
facts beside the readable ones, and the listing, read-by-name, panel, tool and
recall audit all name them; recall still serves the readable facts and keeps
its old sentence for a whole-directory failure. A save by that name refuses
before snapshotting or writing anything.
The terminal counted and drew a host-authored inbox entry as the person's
own pending line, so `d d` on it reached DELETE /inbox/items/<id> and the
kernel dropped the runtime's continuation. The desktop has always filtered
these out; the terminal never decoded the field it filters on.
The mirror now carries origin, readonly and capacity, and a declared-subset
wire-parity check holds it (and the page's copy) to the fields a decision
reads, so dropping one is a `make check` failure instead of a silent zero.
A host continuation is out of the pending rows and out of the count, except
where it is blocked or uncertain and needs a decision only the person can
make; there it is drawn marked as the runtime's. A read-only snapshot offers
no mutation key and says why.
The force-push to the restacked head did not start the test/build workflows
(closing and reopening only re-ran the labeler), so this empty commit emits a
synchronize event. No code change.
A line that carried a currency and a quote of zero was priced by the exact
JSONL read but discarded by the projection, which treated amount 0 as "no
price". A single-day range reads JSONL while a multi-day range reads the
projection, so one session showed [{CNY 3},{USD 0}] on 今日 and [{CNY 3}] on
30 天, lost costEstimated on a day whose only quote was zero, and could emit
the false "usage but no cost record" note.
A currency is the whole test for "priced"; zero is a genuine free quote. Both
build paths now keep it: projectRecord no longer drops amount 0, the rederive
view no longer filters cost<>0, and the rollup read no longer skips amount 0,
while a row with no currency still carries no money. No schema column was
needed, so SchemaVersion is unchanged; every launch reconciles the day files
through rederive, which repairs a projection built by the old filters.
Tests cover the fixed two-day history through the exact read, the incremental
append projection and the rebuilt projection, the zero-amount-only day's
costEstimated in both, and controls for a currency-less row and a non-zero
amount.
<active-goal contract="full"> carries the whole goal-mode contract into the
model-visible history, and nothing retired it when the goal stopped: the next
request carried no goal block while the contract still said to keep pursuing
it, and active-goal being a superseded tag made a fold keep that stale copy.
A stop the model did not report now renders a tombstone under the same tag:
reason paused, cleared, budget, or host (the completion check could not run).
A model-reported block and a completed goal compose nothing, because the model
already knows. Delivery is asked of the visible history through the delivery
PR's projectionDebt: the tombstone is owed while the newest active-goal block
in view is a contract or reminder, and not once it is the tombstone itself, so
a rewind or fold that drops it owes it again with no remembered state.
SupersededUserBlockTags is unchanged, so the tombstone is the newest copy a
fold keeps.
The boot harness's blockOf now matches a tag prefix, since a variant block
opens with attributes after its tag.
Retain legacy DAG payload locations instead of discarded message bodies, preflight JSON without copying large strings, and refuse oversized records or retained histories without modifying source data or falling back to stale checkpoints.
Refs #14116
A call that spent tokens but recorded no money was counted in the token
and request totals and contributed 0 to Cost, with nothing in the payload
saying its money was unknown. Two paths produced that shape.
Title generation wrote such a row live: the serve title model builds its
own stats.Recorder instead of emitting through the assembled sink, so no
CostQuoteSink sits in front of it. The title event carried a rate card but
no quote, and the recorder writes cost fields only for a non-nil quote.
It now prices the call at emit time with event.EnsureCostQuote, using the
same QuoteContext construction the assembled sink uses
(boot.QuoteContextFor), so it is quoted like any other call.
Rows written before the cost fields existed (and any row that simply
lacks them) had the same effect on reads. The incompleteness trigger is
now "no cost fact": an explicit cost_complete=false, or tokens counted
with no cost_amount. One predicate (usagecatalog.CostIncomplete) is
applied on all three read surfaces, so they agree.
No price is invented and no legacy row is backfilled.
The flat-import re-home took the save locks but never asked the session
lease, so it could move a WAL-backed transcript out of a live session root
and delete the old path while another runtime (or this one) still owned it.
Gate every move/remove in the migrate family on the same predicate
renameOverlongSession uses: sessionLeaseHeldLocally OR
SessionLeaseHeldByOtherRuntime. A deferred base is not counted as imported
and leaves the one-time marker unwritten, so a later run settles it.
Tests: audit red/green pair, unleased control, same-process lease control.
Problem: the post-answer finalization fix note would be missed by the current release fragment collection.
Root cause: it used the retired shared unreleased.md path and did not name the fixing PR.
Fix: move the bilingual entry into unreleased/14140-bound-post-answer-finalization.md, preserving #13953 and adding #14140 to both bullets.
Verification: release-note and Markdown-reference tests pass (12 tests); make check, fragment discovery, bilingual references, release rendering, and whitespace checks pass. Only release-note documentation changes.
Problem: the compaction fix note was outside the release fragment glob and cited the related finalization PR instead of its own fix.
Root cause: the note used the retired shared unreleased.md layout.
Fix: move the bilingual entry into unreleased/14144-compaction-eof-vs-cancellation.md and reference #14144 in both bullets.
Verification: release-note and Markdown-reference tests pass (12 tests); make check, fragment discovery, exact bilingual references, release rendering, and whitespace checks pass. Only release-note documentation changes.
Problem: compaction could report success and accept a partial digest after
the summary deadline or caller cancellation.
Root cause: providers close their channel when context ends. Selecting EOF
instead of the simultaneously ready cancellation accepted nonempty output;
the deferred ceiling classifier only corrected non-nil errors.
Fix: keep EOF validation at one result boundary and check the request context
before accepting a digest or classifying truncation. Preserve typed ceiling
and cancellation failures, usage accounting, and stable provider requests.
Verification: force EOF/cancellation overlap with a progress barrier and fake
clock, cover normal completion/truncation and charged usage, and exercise
the real Build-to-notice path. Baseline production source fails on macOS
and Linux; fixed Linux cases pass 20 repetitions. Focused race/cache tests,
full root tests, required tool/boot tests, vet, lint, make check, and release
script checks pass. A separate combined tree also validates this fix with
the post-answer finalization change from PR #14140.
The seer controller.New bound was the executor's method value, and that value
reads the session in place: a SetSession from /new, resume, a branch switch, an
extension system prompt or a fold is already visible to it. Those sites answer
from the current session. A model switch is different — it builds a new
controller, and ApplyRuntimeMigration adopts the old conversation into it. The
carried turns still hold the standing blocks the old model was sent, so the
derived rule reported them delivered and the new model's first request carried
no listing.
The seer now resolves through the controller and answers "nothing is visible"
for a generation that has delivered nothing yet. ApplyRuntimeMigration marks
that state when the replacement's model ref differs from the outgoing one; the
hand-off to the runner clears it, so exactly one request restates the standing
blocks and the visible history answers after that. A rebuild that keeps the
model keeps the derived answer.
Problem: A completed model answer could leave the controller running while
workspace observation or an evidence stream lock kept finalization alive.
Root cause: Post-answer observation and sealing detached their deadlines
from the turn context. Observation called filesystem operations synchronously,
so its existing timeout did not actually bound the caller's wait.
Fix: Carry the run context through every finalization entry point. Bound
observation with a buffered result that captures no mutable turn state,
record missing observations as gaps, and cancel unused initial snapshots.
Keep optional finalization cancellation from discarding completed answers.
Verification: Deterministic cancellation, deadline, observation-budget,
held-seal-lock and real boot/controller completion regressions fail before
the fix and pass on macOS and Linux arm64 after it. Related race checks,
121 frontend tests, formatting, vet, lint and changed-file checks pass.
The required builtin-tool and full boot package suites also pass.
Two rows of the picker table described the 1.x CLI: an "MCP import picker"
the 2.x terminal does not have (`reasonix mcp import` imports without one),
and an "MCP manager" whose `r` refreshed and whose number keys chose actions.
The 2.x /mcp panel (mcppanel.go, mcpmanage.go) moves with Up/Down or j/k,
opens a server's tools with Enter, toggles with Space, adds with n through a
review that installs with y, l or p, removes with d then y, signs in with a,
grants requested access with u, and reconnects a failed server with r.
The chat table also missed keys the terminal handles: Ctrl+S (steers a
running turn, sends an idle draft), Shift+PageUp/PageDown, Ctrl+Insert to
copy a selection, Shift+Insert to paste text where the terminal hands it
over, Ctrl+G to keep folded pastes as files, and Ctrl+D twice in the resume
picker. The tool approval row now says its numbers follow the rows shown and
that Esc does not answer in vi mode, and plan approval gets its own row. The
Chinese table also gains the Ctrl+Enter / /steer row it never had.
Pin the three behaviours no test held: Ctrl+S on an idle draft, an approval
number with a row missing, and Esc on a plan approval.
The English remote section gained two blocks the Chinese guide never got: a WSL
distribution as a machine (kind = "wsl", what it accepts, how Studio adds and
keeps it, where its model credentials come from, which subcommands stay
SSH-only), and the rule that an agent's own shell may run only `list`, `help`
and `wsl list|test` of `reasonix remote` (exit 77 otherwise). Translate both into
the same place in GUIDE.zh-CN.md, between the config example and the CLI block,
using the Studio labels the Chinese window shows (设置 → 远程, WSL 发行版).
The guide says `wsl test` runs for an agent, but the test pinned only
`wsl list`; add the `wsl test` row. A distribution that does not exist answers
after the same `wsl --list` the `wsl list` row already runs, so no distribution
is started.
Both were raised as non-blocking notes on #13784.
validPriceSource and its page mirror sourceProblem refused control and blank
characters but not format characters (Unicode category Cf: zero-width, soft
hyphen, bidirectional controls). A source carrying one was stored as written
while the page drew something else: with U+200B or U+00AD inside the host the
kernel stores a host that is not "api-docs.deepseek.com", and the page's host
line, read through the browser's URL parser, shows "api-docs.deepseek.com";
U+202E in the path reverses the tail in the hover title.
Refuse Cf in the same loop on both sides, the check validElicitURL already
makes for a link a person is shown. The page uses \p{Cc}\p{Cf} so it also
refuses the C1 controls Go's unicode.IsControl already refused.
Raised as a non-blocking note on #13864.
Problem: CodeQL flagged direct path opens in the cancellable checkpoint readers.
Root cause: The normal and torn-tail JSONL readers followed file symlinks beyond
the parent directory supplied by their caller, even though frontend request
authorization is owned separately at the HTTP boundary.
Fix: Open both checkpoints through os.OpenInRoot, retaining cancellation and
recovery semantics while rejecting links outside that directory. Preserve
relative in-directory links. Add a deterministic boundary regression and
bilingual release notes, and separate the sidebar Retry action from its note.
Verification: The escaping-link regression fails before the change and passes
after it. Complete storage/replay/catalog/scheduler suites, focused race and
HTTP boundary tests, 13 frontend tests, TypeScript checking, release-note tests
and repository/Go lint pass. Hosted CodeQL must confirm the new head separately.
An audit found that a fold which keeps the old block used to leave a
removal unannounced (the tombstone was gated on a bookkeeping flag the
fold cleared). Deriving delivery from the visible history fixes it; this
pins that a removed instruction is still announced while the model can
read the old block.
An audit found the same hole through two more entry points: /rewind
truncates the turn that carried the block, and /switch replaces the
transcript with another branch's prefix. Deriving delivery from the
visible history fixes both, so the audit's two providers-boundary tests
now guard that they stay fixed.
A standing projection was settled as delivered when a turn was handed to the
runner, so anything that refused the turn before the provider (an extension
block, a cancel, a failed send) or a /new left the debt settled although no
request had carried the block.
Delivery is now a fact about the model-visible history: owed compares the
newest block of its tag in the visible user records, so a LocalOnly record or
compaction summary never answers for it. A refusal, cancel, fold, rewind or
session rotation changes the answer by itself; nothing is recorded at the
hand-off.
Removed: projectionDebt.{pending,delivered,settle,forget,sent,inherit},
promptFailureDebt.{composed,settle}, settleTurnProjections and runSettled, the
CompactionDone→forget handler, InheritScratchFrom and the per-owner forget
hooks. One-time failure/refusal notes are filtered by visibility instead of
settled, so a turn refused before the provider still owes them.
internal/state/memory guarded its read-modify-write sequences with a
process-local mutex only, so two processes sharing a memory directory could
duplicate a subject key, lose a rename, roll a withdrawal over a newer
revision, or drop an index line.
Take one filelock per directory the store mutates (Dir and GlobalDir, sorted
by canonical absolute path) under the user lock root, bounded by a short wait
that returns a retryable StoreLockedError. The in-process mutex is still taken
first and the file lock second, one order everywhere, so the two cannot
deadlock.
CodeQL flagged the new snapshot parser for stat'ing a caller-supplied path
(store_snapshot_identity.go:44, 'This path depends on a user-provided value').
The parser now takes the name only; loadMemory keeps the stat where it always
lived and fills the missing timestamps itself.
snapshotMemoryRevisionInDir re-read the fact file and filed those bytes
under memory.Revision without checking that the file still carried that
id and revision. Another process publishing revision N+1 in that window
made slot N hold revision N+1's bytes: Restore(id, N) then returned a
body revision N never had, and revision N became unrecoverable. The same
file name re-created with a different id was filed the same way.
The bytes the function already reads are now parsed by loadMemory's own
parser (parseMemory, factored out rather than duplicated) and the snapshot
is refused with the existing "memory revision conflict: expected %d,
found %d" wording when the id or revision no longer match. The check runs
before any slot is created, so a refusal files nothing. SaveWithOptions
and RestoreArchived already return a snapshot error before publishing, so
no caller changes. Single-process behaviour is unchanged: the
process-local mutex makes the mismatch impossible there.
Tests cover the two cross-process probes (refusal plus nothing filed),
archive-and-recreate with a new id, an ordinary save/restore control over
several revisions, and a mutation that removes only the check.
Problem: PR #14086 conflicted with studio after the upstream listing-test repair.
Root cause: Both branches changed the same cache test. Upstream #14097 replaced
timing-dependent large fixtures with direct observation of cached replay, while
this branch added an assertion that derived summaries were persisted.
Fix: Merge studio at 58c80e3f9f, retain the upstream three-conversation corruption
fixture and preview checks, and assert the matching number of persisted entries.
Keep upstream commit history and the independent progressive-loading PR scope.
Verification: Complete sessionstore/sessionv4/historycatalog/backgroundwork
suites, focused cache/cancellation race checks repeated three times, real
resume/tree HTTP effects, 27 frontend interaction tests, and repository/Go lint.
TestV4ConversationsWithoutAListingCacheAreReplayedOnce called a second
listing a replay unless it ran five times faster than the first. A busy
macOS runner measured 411ms against 1.35s and failed a PR that does not
touch sessionstore.
Garble each conversation's log in place after the first listing, keeping
the size and modification time the remembered listing is keyed on: a
listing that remembered returns the same rows, one that replays loses
them. Three small conversations now do what sixty large ones did.
userTexts, the strings a copied command is exempt for because the user
wrote them, returned each user turn's provider-visible Content. That is
the composed turn, with the host's recalled memory, instructions and
policy around the person's words, so a page's command that reached the
turn only through a host block was taken for one the user typed and was
neither counted nor put to the person.
Read each turn through sessionstore.UserMessageText, the definition the
history readers share. This only narrows the exemption.
Problem: The workbench drag change failed the action census and sidebar
text-drop regression in the frontend CI suite.
Root cause: The new dragend listener had no action identity, and the drop
router read the workspace-path payload without checking its advertised MIME
type. Transfers returning unadvertised data could be routed as file paths.
Fix: Register file-drop.finish through listenAction, require the workspace
MIME type before reading its path, and cover plain text and URI-list drops.
Verification: Reproduced the action census and both MIME regression failures
before the fix. All 752 frontend test files and 6027 tests pass, including
77 focused action/drop/intake tests. Typecheck, census, census:verify,
production build, bundle fixture exclusion, gofmt, go vet, make lint,
builtin/boot tests, and make check pass.
Refs #12771
mechanicalFoldDigest, the stand-in when the summarizer cannot digest a
fold, listed each user turn's provider-visible Content. The oldest turns
a fold takes carry the host's date and standing-instruction blocks, so
the 240-character line went on <current-date> and
<project-instructions> and the person's words were cut off, and a turn
of host blocks alone was listed as something the user said.
Read each turn through sessionstore.UserMessageText, the definition the
history index and its snippets already share, and skip a turn with
nothing left.
Add the missing bilingual release-note fragment for PR #14082 so users
can discover that workbench files and folders can be dragged into chat.
Reference the related file-to-chat issue in both languages.
Verification: release-note rendering and Markdown reference tests passed
(12 tests), alongside the required pre-commit checks.
Refs #12771
Problem: the split listing and scheduling change needs its own user-facing release fragment.
Fix: add bilingual notes for progressive summaries, incomplete coverage and foreground priority.
Verification: release-note rendering and Markdown reference tests, make check and git diff --check pass; runtime code is unchanged.
Problem: the user-visible startup behavior had no release fragment.
Fix: document in Chinese and English that ordinary resource reads wait for the authorized connection, as requested in review of #14084.
Verification: release-note rendering and Markdown reference tests pass; make check and git diff --check pass. No runtime code changed.
Problem: Files and folders in the workbench could not be dragged into chat.
Root cause: Explorer and Git rows emitted no path payload, and composer
intake only recognized external files and ordinary text.
Fix: Emit a typed source-workspace path and route it through existing
reference validation. Preserve nested repository prefixes, clear cancelled
drag hints, and keep internal drags outside external-file deduplication.
Verification: Typecheck and 689 focused frontend tests passed, including
native-drop compatibility. Browser drag checks, make check, go vet, make
lint, builtin/boot tests, and both drop-handler Go suites passed.
Refs #12771
Problem: Sidebar discovery waited for full foreign transcript replay while
history indexing competed with interactive session restoration.
Root cause: Complete-list reads owned metadata replay, identity replacement
scanned growing message slices, and derived-data producers lacked shared admission.
Fix: Return explicit progressive coverage, share single-flight summaries and
validated caches, fold replay identities linearly, and retain cancellable scan
generations. Admit background work through one foreground-aware scheduler.
Apply foreground admission at the history route group so independent readers
inherit it without coupling their implementation to background work.
Verification: Storage/scheduler/catalog suites, real Boot/resume and tree/HTTP
effects, sidebar interactions, production frontend build, race checks and lint.
Keep benchmark cleanup registered even when its bounded deadline fails.
Split from PR #14077; no startup fixes or transcript pagination are included.
sessionstore.UserSaidText stripped only the host blocks prepended to a
persisted user turn, while UserMessageText also stripped the blocks the host
appends after the person's words (<execution-policy>, <memory-recall>). The
history index and its snippets therefore indexed recalled facts and the frozen
policy as if the user had said them, so a query for a fact matched a row whose
snippet could not contain it: the same divergence #14022 fixed for the leading
blocks, one definition still giving two answers.
Move UserPreviewText's pipeline down into userAuthoredText and let UserSaidText
add the plan-mode marker strip; UserMessageText delegates to UserSaidText for
both RawContent and Content. The appended cut keeps the existing byte-exact
suffix rule, so a complete block the person quotes at the end of their own turn
is still indistinguishable from a host-appended one and is stripped; the test
pins that limit instead of hiding it.
Bump historycatalog.TokenizerVersion to 4 because the indexed vocabulary
changes again. ensureTokenizerVersion wipes history_fts/documents/sources/roots
when the stored stamp differs and the manager re-registers its roots, so the
next open re-indexes them; Rebuild stamps what it built with. Covered by
TestTokenizerVersionMismatchClearsMixedProjection and
TestRebuildSurvivesTheVersionCheck.
Tests: TestFTSIndexesWhatTheUserSaidNotTheAppendedBlocks and the extended
TestIndexAndSnippetAgreeOnTheUserText / TestSnippetBuildersReadTheIndexedUserText
pins were red before the change. TestAutoRecallNormalTurnIsUnchanged and
TestUserSaidTextQuotedExecutionPolicy are controls, green before and after.
Problem: File delivery requires explicit opt-in even when cards are useful.
Root cause: The user-only configuration key defaults to false.
Fix: Default it on while preserving explicit off choices, read boundaries
and tool exclusions. Keep this policy separate from the settings UI PR.
Verification: Config round trips, complete config/settings/builtin/boot
suites, exact request goldens, schema stability, vet and lint.
The default request fixture gains 143 schema tokens. A newly opened session
may have one cold prefix; this does not mutate the live session's schemas.
Problem: The settings PR also changed the default tool surface.
Root cause: UI discoverability and default-on policy were bundled together.
Fix: Restore the existing default, registration fixtures and configuration
rendering, retaining the searchable user-only switch below Tool approval.
Keep UI wording and route tests independent of a future default decision.
Verification: Settings and transport tests, browser captures, Go suites,
frontend build, vet, lint and repository policy checks.
Problem: Preset modules could only be added one at a time, with two different actions both labeled Add module.
Root cause: A native single-choice picker shared its label with manual blank-ID insertion, and catalog choices did not carry source provenance.
Fix: Add a shared searchable grouped checkbox picker, authoritative catalog provenance, batch selection and parameter-safe draft updates. Keep manual IDs under an explicit advanced entry and document selection semantics.
Verification: 219 frontend tests, typecheck, production build, census, owning Go packages including boot and builtin, focused Desktop preset/composition tests, vet, lint, make check and documentation checks passed. Browser checks covered themes, responsive layouts and save/reopen with MockHub.
Problem: AI file delivery cards required a manual, default-off config edit.
Root cause: present_files had no settings surface or read/write endpoint, and
the config delta renderer treated false as the default.
Fix: Add a searchable user-only switch immediately below Tool approval.
Default the tool on, preserve explicit off choices, and keep registration
changes at session opening. Update wire contracts, documentation and the
provider-visible tool fixtures for the additional 143 schema tokens.
Verification: Config round trips, route grants, UI persistence/search/order,
real boot tool-surface effects, frontend tests/build, Go tests/vet/lint and
browser checks in light/dark wide and phone layouts.
Problem: A redundant arguments envelope makes usable tool calls fail required
field validation and can cause repeated model retries. The current CI security
scan also reports GO-2026-6809 in the pinned Go 1.26.9 standard library.
Root cause: Scheduling, policy checks, execution, and history had no shared
representation of effective arguments inside a schema-invalid wrapper. The
root and Desktop modules still selected the affected Go patch release.
Fix: Unwrap one unambiguous envelope only after full target-schema validation.
Use effective arguments for host execution, evidence, decision barriers, and
surface actions while retaining the original provider call. Select Go 1.26.10
for both modules so CI and packaged Desktop use the patched standard library.
Style the compatibility notice and original-input disclosure like adjacent
tool metadata, with a visible expand marker and keyboard focus outline.
Verification: The envelope regression suites, provider history, permission,
proxy, and interactive surface tests passed. With Go 1.26.10, govulncheck ./...
reported zero reachable vulnerabilities and go vet ./... passed. Frontend type
and interaction checks passed on the rebased PR. The final card style passed
frontend type checking, 16 relevant UI tests, make check, and local browser
inspection in light and dark themes with its disclosure closed and open.
reconcileRoot treated seen_generation<>generation as proof that a transcript is
gone. A row indexed by another process's persist event carries generation 0, so a
conversation indexed after this scan listed the directory was swept health=missing:
it vanished from history search and the incomplete-index warning stayed silent,
because the root's counts still looked complete. Measure absence with stat and
adopt live rows into this generation; only a transcript confirmed gone is swept.
Problem: the release fragment mentioned static HTML support without explicitly
calling out the change from automatic live rendering.
Fix: state in Chinese and English that HTML opens with scripts disabled and
desktop users select Interactive run to execute the page, as requested in review.
Verification: release-note script tests, make check and whitespace validation.
No runtime behavior, dependencies or test expectations changed.
Problem: frontend-next CI failed 25 assertions across action discovery,
orphan detection, HTML preview races and copy feedback consumers.
Root cause: TooltipButton was not declared as an interaction primitive; new
preview controls had no action identities, and the superseded image component
remained orphaned. Neighbouring tests still expected native title tooltips and
automatic HTML execution rather than the explicit Interactive run action.
Fix: certify and name the actual UI inputs, restrict tooltip blur to local
dismissal, remove the unused image renderer, and align consumer tests with the
new contracts while retaining copy failure/retry and browser race coverage.
The unnamed-input ceiling falls from 218 to 217 rather than being relaxed.
Verification: full frontend suite (749 files, 6010 tests); supplemental action,
tooltip and browser guards; typecheck and production build; go vet, make lint
and make check. No workflow skip, timeout increase or alert suppression.
Problem: delivery-card Add to conversation bypassed the workbench's reference
validation, allowing names with token boundaries or MCP ambiguity into drafts.
Root cause: Pane independently formatted @ references from display paths.
The kernel grammar preserves literal backslashes and only escapes space/tab,
so generic backslash doubling would change the selected file.
Fix: share one grammar-specific formatter, use structured relative paths for
workspace cards, disable unrepresentable references while keeping preview and
path copying, and remove the unused intake refToken helper. Keyboard navigation
and initial focus skip disabled menu actions.
Verification: 170 related frontend tests, production build/typecheck, real
browser insertion, kernel backslash round-trip and scoped-resolution tests,
go vet, make lint and make check. Four new rejection cases fail before the fix.
Problem: the presenter was registered for every install, and review requested
default-off model behavior, repository screenshot removal and PDF hardening.
Root cause: present_files used the unconditional builtin path rather than the
existing user-scoped conditional registration and provider-surface mechanism.
Fix: gate registration with tools.present_files, protect user scope across
project merges, preserve the setting on save, and expose the tool only after
opt-in. Restore the unconditional schema budget and remove PR screenshots.
Document PDF.js 6's removal of eval/isEvalSupported, keep scripting disconnected
and explicitly disable XFA rather than passing an unsupported no-op option.
Verification: boot.Build direct/proxy off/on/forbidden/disabled/excluded effect
matrix; user-scope and save/reload tests; document tests and typecheck; real
browser PDF navigation; go vet, make lint, make check and release-note tests.
Record the user-visible preview formats, responsive delivery cards and refresh fixes for PR #14031. Verified with make check and git diff --check; no runtime behavior changed.
Problem: AI-created documents could not be handed off as validated, durable
file cards, and binary files could not be read through the text editor route.
Root cause: delivery identity lived only in ordinary tool output, while file
reading, view selection and auxiliary actions lacked a shared preview path.
Fix: add a bounded root-confined reader, Controller and raw-byte preview APIs,
and host-confirmed present_files results projected through live and stored
history. Add lazy PDF, worker CSV and sandboxed HTML viewers, responsive file
cards, type icons, current-file state and accessible shared tooltips. Preserve
dirty drafts while surfacing deletion/revocation and avoid metadata reads per
keystroke. Keep the current workbench request-consumption contract intact.
Verification: root and desktop Go tests, go vet, make lint/check, frontend
typecheck, 498 related tests and production build. Browser checks cover PDF
navigation, CSV literals, menus, request consumption and responsive themes.
The builtin schema inventory grows by 143 measured tokens; boot effect tests
verify stable schemas within a turn for direct and capability-proxy calls.
Cause: candidateTexts.of loaded a candidate's transcript only to build a
snippet. When that load failed it set failed[path] and returned, dropping the
candidate with no error, no repair, and no status change. The row's ledger
digest and size/mtime still matched, so Indexed == Total kept certifying the
projection complete and the history tool answered "No saved session history
matched ..." for a query the session did match.
Fix: record a deterministic read failure with the shape the indexer already
uses for the same fault. RecordReadFailure flips the row from ok to corrupt
with last_error (existing columns), publishes the revision so Status reports
Indexed < Total, and re-enqueues the source. The periodic scan (#13923: a
non-ok source is always revisited) then re-reads it, so a restored mode heals
without a save.
Transient failures are not distinguishable from permanent ones at this layer,
so every load failure is recorded; retries stay bounded because a row that is
already corrupt is not re-queued per search, and the scan retries it once per
interval (five minutes by default). No wording, persisted field, schema, or
tokenizer version changed.
Tests: the audit's red test now passes (the tool prefixes the existing
"history index is still building" notice and status reports Failed 1,
Indexed 0/1); a readable control that genuinely matches nothing still answers
no matches with no alarm; and after chmod is restored the next ReconcileRoot
scan serves the content again. Mutation check: reverting only the fix leaves
the first and third red with the quoted false claim.
The FTS projection indexed raw user message Content while every display path
stripped the host's prepended blocks (<response-language>, <current-date>,
<project-instructions>, <available-skills>, <workspace>): a query for a
block-only phrase matched a row whose snippet could not contain it.
Move the strip into sessionstore.UserSaidText, the one definition of what a
user turn says, and call it from both sides: the indexer
(historycatalog/documents.go) and the snippet builders (history/search.go,
history/indexed.go). The history package's private stripComposePrefixes is
gone, so the two cannot drift again.
Scope is today's snippet behaviour: the leading blocks stripComposePrefixes
took, plus the plan-mode marker. Trailing <execution-policy>/<memory-recall>
stripping is deliberately not added; whether "the user said" also excludes
host-appended blocks is a product decision left for the PR body.
Bump historycatalog.TokenizerVersion to 3 because the indexed vocabulary
changes. ensureTokenizerVersion wipes history_fts/documents/sources/roots when
the stored stamp differs, and the manager re-registers its roots, which
re-indexes them; Rebuild stamps the version it built with. Covered by
TestTokenizerVersionMismatchClearsMixedProjection and
TestRebuildSurvivesTheVersionCheck.
Tests: TestFTSIndexesWhatTheUserSaidNotTheHostBlocks (red before the change),
TestIndexAndSnippetAgreeOnTheUserText, TestIndexKeepsPlainUserTextUnchanged,
TestSnippetBuildersReadTheIndexedUserText.
Problem: selecting a custom preset captures unused skill templates and
extension packages and waits for optional MCP startup.
Root cause: resource snapshot creation also served as the runtime permission
model, making selection cost scale with installed resource payload.
Fix: add live capability-grant bindings while preserving legacy snapshots.
Resolve current skill/MCP/extension/hook/command authority at use, defer
optional startup, retain stable provider contracts, and fence new event
and archive formats from older readers. Use lightweight status summaries
and per-instance extension call leases for safe concurrent replacement.
Verification: root and Desktop suites, full boot/builtin suites, affected
storage/controller/frontend tests, lifecycle race checks, frontend interaction
tests and typecheck, lint/vet/check, actionlint, and Windows cross-compilation.
Twenty-sample real switch measurements include build, persist and publication;
64 MiB unused templates remain outside the binding and stay below the local
500 ms P95 target. Native Windows execution is delegated to hosted CI.
#13954 let /resume delete a saved session with a confirming second
Ctrl+D. The merge of #13755 (d87d0d959) reverted it: its branch's
"Merge studio into a5/schedule-api" (ba48dfe0b) resolved files only
studio had changed to the branch's older copies, taking out the picker
code, its tests, the i18n catalogue and the release note.
Restore #13954's eleven files to their content on the studio side of
that merge; none has changed since.
#13956 gave user ! commands the login shell's PATH through
shellrun.CommandEnv, shared with the bash tool. The merge of #13792
(ac1a737ee) reverted it: its branch's "Merge studio into
feat/13111-origin-ask" (ed1dfebb9) resolved files only studio had changed
to the branch's older copies, deleting shellrun/loginenv.go, the Env
line in RunShellWith, the regression test and the release note, and
moving the probe back into bash.go. A ! command again ran with only the
host's PATH, so tools from nvm or Homebrew were not found when Studio
was started from a launcher.
Restore the nine files to their content on the studio side of that
merge; none has changed since.
Studio now requires every config key to be declared once with who may change it
and when it takes effect; the two keys this branch adds were not in that table,
so loading a config panicked with `setting.undeclared: network.dns`.
Both are `Guarded` / `OnOpen`, beside `network.proxy_url`: which resolver a
request goes through is a network boundary, so the model must not change it by
itself, and a new value applies on the next start rather than mid-session.
Also rebased onto studio, which the same push asked for.
Problem:
The native Windows link gate fails when capturing a junction source with
an internal junction, despite the ordinary symlink cases passing.
Root cause:
filepath.EvalSymlinks can retain a Windows junction alias and fail when
walking its child. It is not the repository's physical-identity resolver.
Fix:
Use fileutil.ResolveExistingPath for source roots, contained entries, and
single-file hook parents. Preserve containment checks, rooted content reads,
strict retained-directory verification, and filesystem error identities.
Verification:
Local source-capture and composition regressions and Windows amd64 test
compilation pass. Native verification remains owned by the Windows
composition-links gate, including the previously failing junction case.
The Windows runner's Git Bash has no shasum, and the grader's
`shasum -c ... || exit 1` turned "command not found" into the same exit 1 as
a changed file, so the reference solution failed there with nothing printed.
The SHA-256 manifest is now checked with hashlib inside the grader's Python,
over raw bytes, and a mismatch names the files that differ.
gradeSeed now carries the grader's output in its error: the CI log said only
"exit status 1", which cannot tell a missing tool from a failed check.
Verified locally: on a PATH without shasum the reference solution grades
clean; a CRLF Makefile and an edited Go file each fail and are named; the
pristine seed still fails. snapshot.sh --check, the corpus tests and
make check pass.
Problem:
The Windows smoke suite did not include agentpreset's snapshot tests and
symlink fixtures could be skipped without proving the reported platform.
Root cause:
The platform package list omitted agentpreset, and no fixture exercised
directory junctions. One portable assertion assumed Unix executable bits.
Fix:
Add native junction capture/restore, hook bundle, escape, cycle, and retained
snapshot rejection cases. Run nine focused source-link tests on Windows and
fail the gate for skipped or missing cases. Compare retained permissions to
the actual source filesystem mode.
Verification:
agentpreset and testenv suites, Windows amd64 test compilation, go vet,
make lint, make check, and actionlint pass locally. Native Windows execution
is explicitly pending the hosted composition-links step.
Problem:
Saved personas can fail when selected with inherited skills, extensions, or
hooks. MCP startup failures lack structured server identity and guidance.
Root cause:
Ordinary discovery accepts linked sources but composition snapshot capture
rejects them. MCP startup errors carry the server only in prose instead of
the module metadata consumed by the localized refusal boundary.
Fix:
Flatten contained source links through rooted reads, retain strict snapshot
verification, and preserve module identities and sanitized failure causes
through HTTP and Chinese/English rendering. Keep required MCP failures
blocking and preserve the original session on a failed selection.
Verification:
Full root Go tests, go vet, make lint, make check, frontend typecheck, and
198 relevant frontend tests pass. Browser fixtures cover failure, retry,
management access, and light/dark wide/phone error presentation. Regressions
cover source deletion and restart, prefix stability, revocation, containment,
snapshot tampering, and credential redaction.
The e2e fixtures finish in 5-15 requests, while a real task in this tree took
deepseek-flash 38. A change aimed at cutting round trips cannot show up on
the short suite, so this adds four long tasks whose workdir is a git archive
of commit 42636ddcb (pricing, ablation, agentgraph and five stdlib-only
leaves) plus a trimmed REASONIX.md, a Makefile, a module README and a small
benchctl consumer:
- rt-cache-write-rate: a CacheWrite rate threaded through cost, fingerprint,
card identity and the catalog, plus an Anthropic row that uses it
- rt-stale-skills: Markdown-only repair of three project skills with no
recognised verifier, the shape behind #13293's lingering stale_verification
- rt-rate-lookup-signatures: five lookups moved to (RateCard, bool),
CatalogKey and ModelRef across about 20 call sites, most in _test.go
- rt-plan-ablation-module: a five-step plan-first task signed off per step
snapshot.sh rebuilds every workdir from the pin and --check detects drift.
One pinned test compares CheckedOn with the wall clock and would fail every
workdir from 2026-11-20; base/ anchors it at the snapshot day instead.
Each task ships a solution/; the corpus is registered with the authoring
guards, which check that every seed fails its grader and every solution
passes. A deepseek-flash baseline (2 trials) solved 8/8 at 13-31 requests
once two grader false negatives in rt-stale-skills were fixed; the README
records it, along with a multi_edit prompt arm and a complete_step
description arm that did not move the request count.
The guard in perf/branch-alignment.mjs was not listed in the Studio workflow, so nothing ran it; the fix itself is user-visible and carries a fragment.
The unavailable refusal carried the module and detail only inside a nested
diagnostics list, so the window filled a generic sentence from a code alone.
Flat module/detail params now cross the boundary and both languages use them.
StartForSession called m.wg.Add(1) without a lock while Close could already
be inside m.wg.Wait() after cancelling the root: Add racing Wait at a zero
count is the misuse sync.WaitGroup documents. Under
`go test -race -run TestStartRacingClose -count=40 -cpu 1,2,8` on studio it
reports a data race and panics with "WaitGroup is reused before previous Wait
has returned".
The launch moves into launchJob, which decides under m.mu, the lock both
Close paths now cancel under, whether the job joins the wait group or is
refused. A refused job never runs: it ends Interrupted, its metadata is
written, and it is recorded and published like any other completion, so a
task recorder that saw it start sees it end. The goroutine body moves
unchanged.
Split out of #13947 at the reviewer's request so the jobs manager is
reviewed on its own.
Dirs resolves the path on the far machine and lists it; when the listing
fails but a stat says the path is a directory, the answer is the directory with
Unlisted set instead of an error. The picker then names why the rows are
missing and 'use this folder' works on the confirmed path. A path that is not
there, or not a directory, stays an error. Listing a directory this account
cannot read is fs.ErrPermission on the script layer, as it is on SFTP.
Probe took SFTP before any other step, so a server that refuses the subsystem
ended the test right after 'connection OK'. With the script layer behind
Client.Files it now completes, and the report carries sftp or exec.
pkg/sftp reports the refusal as an unnamed error string, so the subsystem is
now requested in sftpfs.New and a server reply of 'failure' becomes
ErrSubsystemRefused. Client.Volume prefers SFTP and, only on that sentinel,
serves the connection from execfs over the exec channel and remembers the
answer until the connection is replaced. Any other failure (dead link, refused
channel) is returned unchanged. The exec runner quotes the script and every
argument whole into one /bin/sh command line.
The WSL file layer was a fixed set of /bin/sh scripts taking every path as a
positional argument. It is now a package of its own that takes a runner, so
another transport can reuse it. Scripts name a missing program by exit status,
refuse a file over the transfer limit by type, and check the byte count before
publishing a write. List, Download and UploadAtomic are added, and the shared
scenario now covers names with quotes, spaces, a leading dash, newlines, shell
metacharacters and a path over 3000 bytes.
`flexString` existed byte for byte in the openai, gemini and responses packages.
One type written three times diverges at the next edge case, so it moves to
`internal/base/flexstr`, which is a leaf (it imports nothing under reasonix) and
is declared in `tools/repolint/layers.go` so the layering gate allows every model
package to use it.
No wire behaviour changes: a string code, a number, an absent field and null read
exactly as before.
Problem: The CUA readiness change expands #13720 beyond its reviewed
launcher-ownership scope.
Root cause: The native CUA startup race was repaired alongside the
launcher conflict resolution, despite having a separate runtime owner.
Fix: Revert cad3c58f8a on this branch.
The CUA fix and regressions are preserved on fix/cua-socket-readiness,
based directly on studio. Keep the original launcher fix and conflict
resolution unchanged.
Verification: The resulting tree exactly matches e67fe2ce96. Electron
tests pass (189 passed, one existing platform skip), as do go vet,
make lint, make check, and the builtin/boot package tests.
Document the user-visible startup and restart fix from #13985 in the unreleased Studio notes, in Chinese and English. Verified with make check and the Studio release-notes renderer tests.
Problem: Embedded CUA startup can fail while a valid driver is still
initializing its socket permissions.
Root cause: The driver binds its socket before chmod sets 0600, while the
host treats the intermediate permissions as a terminal startup error.
Fix: Wait within the existing readiness deadline, preserving socket-type
and identity checks and forbidding metadata requests before private
permissions are observed. Cover initial startup and abort replacement
through the shared launch loop.
Verification: Deterministic permission-ordering and deadline regressions,
file/symlink rejection, 188 passing Electron tests with one platform skip,
native macOS CUA acceptance, go vet, make lint, make check, and builtin/boot
package tests. Split from #13720 at the maintainer's request.
- The native bridge answers only the kernel's page and the one computer the
person scanned a code for; `openInApp` loads an origin once, and only that one.
- Non-web URLs are routed only for mailto: and tel: — an intent:// URL can start
any exported activity.
- The `reasonix://` filter that nothing handled is gone.
- A release with no key is unsigned instead of signed with the debug key, and the
debug build no longer wears the release key.
- The Gradle wrapper is pinned by checksum and the mirrors are out of the
repository.
- The scripts find the kernel and the page at the repository root, speak English,
and copy LICENSE from it instead of committing a second copy.
- The README is what a builder needs, and states what the permissions cost.
Problem: Native macOS CUA acceptance can reject a valid driver startup with
"Invalid private CUA socket".
Root cause: The pinned driver binds its socket before chmod sets 0600.
The host treats the intermediate mode as a terminal error. The previous
fixture creates its socket with private permissions immediately, hiding
the production ordering.
Fix: Treat non-private socket permissions as pending readiness within the
existing deadline. Keep non-socket rejection and identity validation, and
never request metadata until private permissions have been observed.
The shared launch loop also covers abort-triggered replacement.
Verification: Deterministic regressions force observation before chmod on
initial launch and restart, and cover deadline expiry, child cleanup, files,
and symlinks. Both permission regressions fail before the fix. Electron
tests pass (193 passed, 1 existing skip), as do native macOS CUA acceptance,
go vet, make lint, make check, and the builtin/boot package tests.
- The native bridge answers only the kernel's page and the one computer the
person scanned a code for; `openInApp` loads an origin once, and only that one.
- Non-web URLs are routed only for mailto: and tel: — an intent:// URL can start
any exported activity.
- The `reasonix://` filter that nothing handled is gone.
- A release with no key is unsigned instead of signed with the debug key, and the
debug build no longer wears the release key.
- The Gradle wrapper is pinned by checksum and the mirrors are out of the
repository.
- The scripts find the kernel and the page at the repository root, speak English,
and copy LICENSE from it instead of committing a second copy.
- The README is what a builder needs, and states what the permissions cost.
- The native bridge answers only the kernel's page and the one computer the
person scanned a code for; `openInApp` loads an origin once, and only that one.
- Non-web URLs are routed only for mailto: and tel: — an intent:// URL can start
any exported activity.
- The `reasonix://` filter that nothing handled is gone.
- A release with no key is unsigned instead of signed with the debug key, and the
debug build no longer wears the release key.
- The Gradle wrapper is pinned by checksum and the mirrors are out of the
repository.
- The scripts find the kernel and the page at the repository root, speak English,
and copy LICENSE from it instead of committing a second copy.
- The README is what a builder needs, and states what the permissions cost.
The kernel-side pieces this branch came with are their own PRs now, so this keeps
only `android/`. Rebasing onto studio also drops the mailbox delivery code and the
mailbox credentials that the review asked to remove.
Problem: PR #13720 conflicts with the current studio branch in the Electron
unit test suite.
Root cause: Both branches inserted independent regression tests at the same
location after the host startup failure tests.
Fix: Preserve the launcher ownership, relaunch handover, and closed-console
tests together with studio's credential-cookie restoration test. Retain the
automatically merged shell changes and updated Electron dependencies.
Verification: Electron unit and Linux packaging tests passed (189 passed,
1 existing skip). Formatting, go vet ./..., make lint, make check, and the
builtin and boot package tests passed.
TestEffectTruncatedSummaryOnPressureKeepsCodeAndRetries and
TestEffectModelSwitchKeepsTheCompactionProjection pin what happens turn
by turn around the compaction trigger, but the turn the trigger falls on
depended on how large the first request happened to be. Measured on
studio: about 550 more characters of prompt or tool schema flip the first
to "7 failed summaries" and the second to "switch spent 1 summarization
call", with no change to compaction. So any prompt or tool-description
edit, or a host whose tool surface is a few hundred characters larger,
failed them, and a change could pass on one runner and fail on another.
Each fixture now builds once, measures its first request the way the
prompt estimator does, and pads its system prompt up to a fixed size, so
the trigger and pressure turns are the fixture's. The sizes sit in the
middle of ranges where the outcome does not move: 30000-40000 characters
for the truncated-summary fixture (6 attempts throughout), 40500-48000 for
the model-switch fixture (3 folds before the switch, none after). With
0-5000 characters of extra first-request weight injected ahead of the
measurement, both now give the same result; before, both flipped near
550. The bounds are unchanged.
waitForDone returns on the turn-done event, which the controller emits before it drops its running flag, so ClearSession could answer "cannot clear while a turn is running". Under 8 parallel race-enabled processes at one CPU the unchanged test failed 3 times in 1200 runs; with waitIdle it failed 0 times in 1200.
Windows has a backend now, so Available() is true, but it enforces only after the
user turns on windows_integrity. Tests and the e2e bench that ask whether a default
configuration is confined use ConfinesByDefault (false on Windows), and a caller's
override to enforce (an observed run asks for one) is honoured on Windows only
when the user opted in, since enforcing edits permissions on their folders.
A relocation moves only the entries StateRootEntries names. The grant record sat in
sandbox/ outside it, so a move would have left the record behind while the
permission entries it lists stayed on the folders, with nothing to take them back.
The restricted token now drops every privilege but the traverse check, so backup
semantics cannot write outside the roots even from an elevated session. A SID is
synthetic only if no account resolves it. The launcher refuses to run under a
restricted token (a command's own launcher inherits it), grants only session temp
folders, hands the child just the standard handles, runs it in a job with UI
limits, and the default DACL and the grant mask are narrower. Links are not
followed at grant time and a link root is refused. REASONIX.md, the guide, the
Settings text and the release note list what is and is not bounded.
The host already confines bash's $TMPDIR to a per-session directory, the
file tools resolve a leading $TMPDIR to it, and the host deletes it when
the session ends. The model was never told the last two, so it verified
with `scratch=$(mktemp -d); ...; rm -rf "$scratch"` or wrote tests to /tmp,
and the host refused them (shell.delete_nonliteral, shell.delete_sequence,
write scope): 26 refused calls over 36 deepseek-flash benchmark runs, one
model round each.
A <session-scratch> turn block now says: put throwaway files at
`$TMPDIR/<name>`, spelled that way in bash and in the file tools, and do
not delete them. The host path is deliberately not projected: under bwrap
bash sees the directory at /tmp, and on Windows a backslashed literal does
not survive a bash command line, so $TMPDIR is the one spelling every tool
resolves. The block follows the dateAnchor/projectionDebt pattern: it rides
the turn and never the prefix, settles only when a turn reaches the runner,
is re-owed after a fold, retracts itself once if the directory can no
longer be created, is projected only for a manager the assembly supplied,
and is inherited across a rebuild. Its bytes carry no per-session value,
so two sessions over the same state project identically.
No shell-safety rule changes; the effect test asserts that a recursive
delete through $TMPDIR or a mktemp variable is still refused.
supersedeStandingState judged only the fold region's kept turns, so a
standing block the region held once survived beside newer copies in the
recent tail. A fold whose region kept a single user turn dropped nothing:
TestEffectFoldDropsSupersededStandingState went from 3 <workspace> copies
to 4 across such a fold once a turn block shifted the fold one turn
earlier. partitionFoldWith now takes the tail, and the tail's user-turn
blocks are marked seen before the kept slice is walked newest-first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every CompactionDone forgot what the model had been told -- the date,
the skills listing, the project instructions and rules -- so the next
turn sent them again. A truncated or refused digest also emits
CompactionDone, with Messages == 0: nothing was summarised and the turn
that carried those blocks is still verbatim, so re-sending them only
grew the prompt the next attempt had to fold, by one skills listing per
failed fold.
The producer already says which case it is: a successful fold reports
Messages = len(fold) and emitCompactionAborted reports 0. The sink now
reads that instead of treating every completion as a fold.
TestEffectTruncatedSummaryOnPressureKeepsCodeAndRetries goes from 5 to 6
failed summaries over its 10 turns: the prompt no longer grows by a
re-sent listing after each failure, so the pressure fold lands a turn
later. That is inside the test's bound of 6 and is the growth the retry
gate is meant to read.
Inside run_script, call("read_file", ...) returned the model-facing form:
every line prefixed " N→", CRLF folded to LF, the default 2000-line page,
and — past 32 KiB — a window with a "read on from here" marker. A script
that read a file, edited the text and wrote it back put all of that into
the file. With deepseek-flash on migrate-signature-3packages and
agent.code_mode on, one bulk rewrite corrupted 36 files and cost ~15 rounds
to repair.
Two layers each rendered for the model a result whose reader is a program:
- read_file: a call another tool made (tool.IsNested) gets the decoded text
verbatim — line endings and final newline kept, no default limit, offset
and limit selecting whole lines. The model's own read is unchanged.
- nestedInvoker: the calling tool reads what the tool returned, not the
bound fitted to the model's context. The nested card still shows the
bounded form; toolOutcome.keepFull records both originals in one place.
write_file already preserves the existing file's encoding, so a BOM file
round-trips once the read stops adding to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
model_rounds and round_outcomes treated every call run_script made as a
top-level dispatch, so each inner result reopened a model gap and each inner
dispatch closed one. One recorded code_mode run with 24 executor requests
reported a median of 87.5 rounds. Nested calls carry no ParentID — only an id
derived from the caller's, inside its dispatch→result window — so the digest
had nothing structural to tell them apart.
The nested id is now declared in internal/contract/tool (NestedCallID /
IsNestedCallOf) and the agent's nestedInvoker builds ids through it, so the
producer and the reader share one definition. e2ebench's callNesting tracks
open top-level calls and treats subagent calls (ParentID) and invoker calls
(derived id) alike: neither cuts a round, joins a batch or adds tool time.
roundEnds and splitAtCorrect use the same tracker. NestedCalls reports how
many there were, and ModelRounds' doc now says what it counts.
With -trials N every trial wrote <task>.trajectory.jsonl, so only the last
survived and every trial's digest read it. Trials now write
<task>.trial-N.trajectory.jsonl (legs: <task>.trial-N.segM...); a single
trial keeps the old name. sft mode resolves the task id through
trajectoryTaskID, which strips trial and leg marks; traj, serve and barrier
modes glob *.trajectory.jsonl and label each file by its stem, so trials
read as distinct entries.
Cause: the digest had no identity for tool-made calls; the trajectory path
had no trial in it.
Blast radius: cmd/e2ebench digest and file naming; nestedInvoker's id string
is unchanged (same "<parent>.<n>" format, now built by the contract helper).
Neighbouring behaviours tested: subagent ParentID exclusion, segment paths,
answer-isolation audit across legs, existing round decomposition tests.
Why this layer: the id convention is owned where nested calls are created;
the digest reads it rather than restating the format.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: Windows CI rejects the retained-hook source-deletion fixture as a
credential-bearing launch before reaching its restoration assertions.
Root cause: credential projection scans the complete quoted shell string as a
generic value, splitting a Windows drive colon inside a quoted file argument.
That creates an unmatched quote and falsely marks the command as sensitive.
Fix: reduce shell launches to static arguments before credential inspection,
preserve exec and legacy argv launches, and continue refusing literal token and
header arguments or shell forms that cannot be reduced safely. Keep source
commands and retention/restoration semantics unchanged.
Verification: regression reproduces both quoted Windows path failures before
the fix, while exec/legacy argv and token/header refusal cases stay covered.
Focused retention/security regressions and their race run pass; full builtin
and boot tests, gofmt, vet, lint and changed-file checks pass locally.
Problem: CodeQL reported new path and allocation alerts, and Windows repeatedly
failed the retained-hook restoration test.
Root cause: Checked selectors were still passed onward as raw request values;
some generated paths and capacity hints did not expose their bounds at the IO
owner. The HTTP DTO also carried an internal inherited binding. The hook fixture
fed Windows backslashes through Bash resource parsing.
Fix: Resolve import/export selections to registered values, forward checked
relative names for resume/migration, and guard the generated session slug before
joining it to state storage. Use rooted declaration/resource reads and copies,
bound resource reads, and avoid unchecked capacity addition. Pass inherited
bindings outside the wire DTO. Normalize and quote the hook fixture path, including
spaces, while retaining source-deletion and exact-binding assertions.
Verification: Hosted alert locations reproduced locally with the path-injection
and allocation queries; before/after query results and the base scan are retained
separately. Boundary regressions fail on the original head and pass with the fix.
Owning HTTP, store, config, hook, sidecar, plugin, specification and encoding suites;
full builtin/boot tests; gofmt, vet, lint and changed-file checks. Hosted Windows
and CodeQL results remain required for exact-head acceptance.
Problem: The composer exposed the new mode picker to users who had not opted in,
contrary to the maintainer's product-visibility decision.
Root cause: Removing the persistent visibility preference also removed its
settings/search action and the RunLine mounting gate.
Fix: Restore the original on/off storage key and default-off behavior. Add the
switch and concise explanation to Persona management, restore settings search
and the action, and gate both mounting and catalog reads. Preserve card defaults,
catalog/editor design, persona copy and current conversation bindings. Failed
storage writes retain the previous choice and report an error.
Verification: Missing/disabled/unknown and toggle regressions fail before the
fix. Focused Chinese/English navigation, persistence, storage-failure and binding
tests pass. Full frontend: 729 files / 5,857 tests; build/typecheck, UI census and
ten identical fixture reports; gofmt, vet, lint, make check, builtin and boot
suites. Browser visual acceptance remains unavailable under the URL policy.
Problem: Built-in preset names and descriptions read like configuration modes
rather than collaborators, and their relationship to Harness prompts was unclear.
Root cause: Technical copy was reused across the catalog, composer and persona
editor without explaining the existing short-identity prompt adaptation.
Fix: Rename the built-ins to Everyday partner and Lightweight partner, update
their bilingual descriptions and guidance, and derive the unbound picker label
from its canonical catalog definition. Keep IDs, persona bytes and selections
unchanged, retain historical translations, refresh revision metadata and document
the pinned official Harness reference and native prompt boundary.
Verification: Frontend 729 files / 5,852 tests and production build/typecheck;
contract, store and HTTP suites; gofmt, vet, lint, changed-file check, builtin and
boot suites. Fix a discovered worktree test race by waiting for the asynchronous
checkout state instead of the creation title shared by initial and checkout
phases. Browser visual acceptance remains unavailable under the URL policy.
Problem: Built-in presets use flat rows, choosing a future task mode requires
repetition, and persona fields are hidden behind an advanced disclosure.
Root cause: Management navigation and new-task launch intent have no shared
explicit preference; the editor groups basic persona fields with identifiers.
Fix: Match the selected two-column card hierarchy with badges, IDs, descriptions
and separate details, usage and copy actions. Persist an explicit built-in card
choice in a new machine-local key and inject it through the local/remote open
owners only for new tasks. Explicit request choices override it; restoring
history keeps its binding. Existing server preferences alone remain inert.
Expose both persona textareas, runtime context, scope and ID directly, rename
settings navigation to Persona, and keep literal persona fields authoritative.
Verification: Full frontend suite plus real MockHub launch effects, blank-pane
reuse, local/remote history, explicit overrides, storage compatibility/refusal,
cross-window refresh and independent footer actions. Production build/typecheck,
UI census/fixtures, format, vet, lint, changed-file and builtin/boot checks.
Browser-rendered visual comparison remains blocked by the URL policy, as
recorded in design-qa.md.
Problem: "After the first-party guidance" exposes prompt-construction jargon
without explaining what users should enter.
Root cause: Both persona editors use a position-based label; the composition
editor has no suffix helper text.
Fix: Label both suffix inputs "Additional instructions" and explain that the
text follows the built-in system instructions and may be left blank. Add the
Chinese/English copy and associate the helper with each textarea. Keep the
existing literal suffix binding and update affected interaction expectations.
Verification: 24 focused persona, preset and localization tests; production
build/typecheck; formatting, vet, lint, changed-file checks and builtin/boot
suites.
Problem: The catalog's create icon and label stack inside a cramped button.
Root cause: Studio icons render as blocks while the button lacks a horizontal
layout; the title and description also share a flex item beside the button.
Fix: Give the button an inline-flex row with no wrapping, a 36px minimum height
and 16px horizontal padding. Align title and action in one grid row and place
the description across the next row, retaining the primary theme colors.
Verification: 27 focused frontend tests, production build/typecheck, formatting,
vet, lint, changed-file checks, and builtin/boot suites. The local preview stays
running; browser observation remains blocked by its URL security policy.
Problem: Preset browsing, creation and the Standard persona were mixed in one
long Settings page, hiding the save flow and exposing configuration too early.
Root cause: Catalog selection and editable drafts shared one inline surface,
without an owner-level guard for Settings navigation.
Fix: Follow the selected full-content editor reference. Group the catalog by
scope; open a separate editor with common fields first, expandable capabilities
and advanced options, and a persistent Cancel/Save footer. Preserve literal
persona bytes, revision checks, import/export/delete and session bindings.
Guard dirty exits across return, Settings close, category and search paths.
Keep pending saves mounted and reject stale import results. Reusable switches
declare button type so toggles cannot accidentally submit the editor form.
Verification: Full frontend suite: 727 files, 5843 tests; focused editor,
navigation, pending/refused save, import ownership and binding regressions;
production build, typecheck, UI census and fixture verification; root format,
vet, lint, changed-file checks and builtin/boot suites. Browser visual acceptance
remains blocked by the local URL security gate and is recorded in design-qa.md.
Every open tab keeps its pane mounted, and a hidden pane was only
visibility:hidden, so it still took part in layout. The rail width is animated
per frame, so each frame laid out every pane's transcript again. With three
panes the median frame at 4x CPU slowdown went from 94 ms to 18 ms.
Problem: the mode picker required a separate settings toggle before users
could discover or use it.
Root cause: both the picker and its run-status row depended on a persistent
visibility preference unrelated to the conversation's selected mode.
Fix: render the picker directly, remove the retired setting, preference state,
search entry and action, and update the bilingual documentation. Standard
remains the default; displaying the catalog never selects a composition.
Verification: failing-first tests for absent and disabled legacy preferences;
default Standard with a saved Minimal preference; picker, settings, remote-open
and settings-search interaction suites; frontend typecheck, full suite, census,
fixture verification and build; browser preview; go vet, make lint, make check,
builtin tools and boot assembly tests.
Problem: Publishing Studio did not regenerate its website changelog.
Root cause: Product publication and the website deployment had no explicit handoff, and Pages only accepts runs from the website branch.
Fix: Dispatch the website workflow after stable publication with the required version and a unique request ID. Wait for that deployment and propagate failures. Register dispatch on the default branch without enabling deployment from it, and use the workflow commit when recovering old tags.
Verification: All 13 publication tests, shellcheck, signing workflow contracts, go vet, make lint, make check, and the required builtin/boot suites pass.
Problem: the custom-preset form grouped storage scope and ID under one label,
leaving their purpose unclear.
Root cause: two independent controls shared the Preset ID field container
without visible labels or usage guidance for each control.
Fix: give each control its own label and short translated description, and
associate the descriptions with the controls for assistive technology.
Verification: AgentPresets interaction tests, frontend typecheck, production
build, census and fixture verification; browser layout and console checks;
go vet, make lint, make check, builtin tools and boot assembly tests.
Problem: PR #13613 conflicted with the current studio branch.
Root cause: both branches extended session status, ACP configuration, and
project dispatch while the shared stylesheet and session-new function reached
their existing size limits.
Fix: retain composition bindings alongside tariff status, apply the ACP
approval ceiling, and reuse pending dispatch panes without dropping inherited
composition bindings. Move the picker styles to their own sheet and compose
the ACP config projections without increasing the ratchet budgets.
Verification: frontend typecheck, 5,830 frontend tests, production build,
census invariants and fixtures; go vet, make lint, make check; ACP, HTTP serve,
composition contracts/storage, session storage/control, delegation, builtin
tools, and boot assembly tests.
Merged studio 3d9f9ff74c into PR head
ae6a9de150.
The kernel refuses every request without it, and the cookie belongs to the
host rather than the port, so a removal left the window refused until a
relaunch. The shell holds the token and puts it back, logging the cause.
.gitattributes pins *.go to eol=lf but not *.golden, so a Windows checkout turns
the byte-compared goldens into CRLF and TestCompareOutputUnchangedWithoutUnaccountedRuns
fails there. Pin *.golden to eol=lf as well.
The macOS tray loaded the full-colour tray.png, so the menu bar showed the
whole app badge. A Template-suffixed black-on-transparent pair lets macOS tint
it for light and dark bars; Windows and Linux keep their files.
aggregateArm dropped an unaccounted run's wall before adding it to
s.WallMs, so compare's Solved/hour and global wall/task divided by a
smaller wall than the suite report uses for the same -json artifact:
report.go adds every run's wall, and a killed run's wall is measured
unconditionally in main.go (only its tokens never landed). The
per-class accumulator ran before the skip, so one section printed two
populations, and the sign of "which arm is cheaper" could flip.
WallMs is now the all-run wall and AccountedWallMs the accounted
subset: Solved/hour and wall/task divide the all-run total, while
"Wall seconds / solved" keeps dividing accounted wall by accounted
solves because that denominator is solved runs. No new persisted
state, and neither how Unaccounted is decided (segmentrun.go) nor the
suite report changes.
This is the residual of #13446 applied to a field that is NOT
unmeasured. That PR decided which runs are unaccounted and kept their
spend out of the token/cost ratios; it never touched wall, and this
change does not revisit that decision. It fixes the wall population
those adjacent rate lines divide, measured for every run including the
ones #13446 excluded elsewhere. Not a re-report of #13446.
Verified: both probes are red on 53a8db913 (compare Solved/hour 120.0
vs the report's 3.9; global wall/task -30.0s vs the class row's
+870.0s) and green after; a no-unaccounted fixture renders compare
byte-identically (goldens); "Wall seconds / solved" divides accounted
wall by accounted solves under either rate line reverted to the
accounted total, while the matching probe turns red; the suite
report's Solved/hour convention is pinned unchanged. gofmt,
GOOS=windows go vet ./cmd/e2ebench/, go test ./cmd/e2ebench (only the
pre-existing environment TestStateHomeIsOutsideWorkdirParent fails,
identically with this change stashed), make check (repolint: clean),
and golangci-lint run ./cmd/e2ebench/... (0 issues) pass.
Cause: a transcript that could not be read was stamped health=corrupt while its
root was left state=ready with the failing size/mtime signature. Both automatic
repair paths trusted that row: reconcileRoot returned before indexPath ran and a
restart reopened the same ready row. Equal size/mtime says nothing about
readability, so a restored mode or a remounted transcript was never revalidated;
status stayed Indexed 0 / Total 1 until a save or a manual `catalogs reindex
history`.
Fix: chose the scan's skip predicate — reconcileRoot only short-circuits when
state=ready, the signature matches, indexed==total, and no source of that root
is non-ok (health<>'ok' or missing_since<>0). That is the decision that
prevented indexPath from ever being consulted. The same rule is applied at
source granularity: sourceProjectionUnchanged now also requires the stored
health to be 'ok', because after the root is rescanned a legacy source with no
ledger digest (digest=="") would otherwise be certified healthy from its
fingerprints without being read.
Fast path kept: a root whose last scan was complete and healthy still returns
before any transcript is opened, so the five-minute tick remains a directory
walk plus stat, not a re-read of every transcript. A source that stays corrupt
is retried once per scan (one failed read per corrupt source per tick) and
scan_generation advances exactly once per scan, so a permanently unreadable
transcript cannot wedge or loop the worker.
Tests: the audit's TestCorruptSourceIsRetriedOnceTheTranscriptIsReadable (red
before: health stays corrupt after the transcript heals) plus controls for a
healthy unchanged root still skipping without a read (scan_generation, revision
and document ids unchanged; a now-unreadable transcript keeps health=ok) and for
a permanently corrupt source retried on every scan while its healthy sibling is
not re-read. Permission fixtures skip on Windows and root, and every catalog in
these tests points at a temp path.
golangci-lint's intrange rule rejects a C-style loop whose index is never
read. Both loops in the new cases just repeat an emit, so they take the
Go 1.22 form. Behaviour is unchanged.
Store.MigrateV2WithVersion returned on the first fact whose os.ReadFile
failed, so facts later in the same directory and every later directory
were never migrated. MigrationReport carried only { Migrated int } and the
boot caller discarded it, so nothing named the skipped files: the audit
observed {Migrated:1} with zeta and omega never reached.
The contract says otherwise. docs/SESSION_MEMORY_RETRIEVAL.md:259-261: a
read condition is "reported instead of returning partial lists or
calculating a revision from incomplete history". :268-269: "These
conditions ... do not establish data deletion." The pass therefore records
each failed basename in MigrationReport.Unreadable, sorted, and continues
with the rest; boot's warning names them instead of printing only the
first path.
A per-fact read failure returns a nil error on purpose: the field is the
channel for "skipped", and folding it into err would make a skipped fact
indistinguishable from a pass that could not run. Directory and index
failures still return an error, because those leave the pass's own work
incomplete.
Tests cover: the audit fixture (alpha readable, beta chmod 000, zeta after
it, omega in the second directory) now migrating alpha/zeta/omega with
Unreadable=[beta.md], cross-directory sort order, a fully readable control
with no error and an empty list, the repair path for an already-migrated
fact behind an unreadable one, and the boot warning naming beta.md.
Review asked for comments to describe what holds now and leave how the code
got here to the commit history. The behaviour is unchanged.
The history this replaces, kept where it belongs:
* Leaving Source in the zero-probe kept ToolProgress on the passthrough
path, so the first attempt at merging progress merged nothing in
practice. Source is identity — it names the emitting party — so it
belongs in the key, and keeping it there is also what stops a planner's
text merging into an executor's.
* The e2e run over a real kernel is what caught that; the unit tests did
not, because they emitted deltas with no Source at all. Both are now
covered: TestCoalesceMergesDeltasCarryingSource and
TestCoalesceSeparatesBySource pin the key's shape.
Cause: persistCompactionStateLocked reached SaveCompactionState, a blind
atomic overwrite carrying no writer identity, while transcript saves fail
closed with ErrSessionWriteAuthorityMissing. A controller in the
stale-authority window (rebind, controller replace, lease generation change)
therefore installed and published a fold whose transcript save was refused:
it replaced the owner's installed digest and paid for the same span twice.
Enforced in the store rather than in the agent: (*Session).SaveCompactionState
presents exactly the permit save.go presents (requireWriteAuthorityForSave,
including BeginSave's in-flight accounting that lease Release waits for), so
there is one adjudication of the permit and no second check re-reading
WriteAuthority() in the agent. Every write to the owner's sidecar already
funnels through the agent's single persistCompactionStateLocked, which now
calls that method. The raw SaveCompactionState remains for paths that write a
fresh recovery lane and for low-level CAS tests that never bind authority.
The CAS on (generation, projection_version, covered_prefix_hash) is untouched:
this is an additional gate, not a replacement.
On refusal the typed authority error propagates as
errProjectionNotPersisted: commitSummaryProjection restores the previous
projection, so nothing is installed or published, and the next attempt under a
valid permit folds once.
Tests: internal/runtime/agent/compaction_sidecar_authority_test.go proves the
no-authority refusal (sidecar and digest unchanged), the authorized control
(one summary, one digest), and that a refused publish costs one normal retry,
not a second summary.
reanchorPattern stripped the backslash from \!name and \#name before
handing a root pattern to the matcher, which then read the first as a
negation and the second as a comment. Files named !name or #name that git
ignores through the root .gitignore, .git/info/exclude or the global
excludes file were therefore searched and listed. Nested .gitignore files
were not affected, since their patterns are re-anchored under /dir/.
Keep the escape on root patterns when it guards a leading ! or #.
The global ignore file was taken from the first core.excludesFile found:
$XDG_CONFIG_HOME/git/config before ~/.gitconfig, and the first value in a
file. Git reads the files in that order and keeps the last value, so a
user's ~/.gitconfig setting lost to the XDG one. The value was also used
nearly raw, so an inline comment or escaped backslashes produced a path
that does not exist, and the user's global ignore rules were silently
dropped.
Keep the last value across files and within a file, and decode the value
like git: strip unquoted # and ; comments, remove quotes, and unescape
\\ and \". Other backslashes stay as written so raw Windows paths still
work.
useRevealed paces a streamed answer on the frame clock and keeps its cursor
in a ref. The effect returned early when the cursor had caught up and only
handled a shrink inside step(), which the early return made unreachable. A
replacement shorter than what was already revealed — regenerate, rewind,
history restore — therefore left the cursor above the new length, and every
later growth below it was short-circuited: the typing animation silently
stopped for that message while the text itself still arrived in one piece.
Handling the shrink before the early return fixes it. The cursor comes back
with the text, so the next growth is paced exactly like the first one.
The existing three tests did not cover this: they only grew the text. The
new case fails against the old code with "expected 0 to be greater than 0".
A shell emits one ToolProgress per output line, so a build log crossed the
coalescer as one frame per line. Downstream that filled a subscriber's
64-frame queue and the hub then dropped answer text beside it, because text
and progress are both droppable. Measured on a real 500-line command: 285
frames unrepaired, 2 with this.
streamDelta replaces isStreamDelta and returns the key a burst merges under
plus its payload, so progress joins text, reasoning and compaction progress:
* the key carries Source. Every progress chunk an executor emits sets
Source="executor", and leaving it in the zero-probe kept the event on the
passthrough path — the first attempt at this merged nothing in practice.
Keeping it in the key also stops a planner's text merging into an
executor's.
* the key carries the call id, so two tools reporting at once never land
in one another's card.
* progress carrying more than ID+Output — a sub-agent's status channel sets
Name, ParentID and a rate — fails the probe and still crosses untouched.
* progress without an id is not buffered under an empty key.
Verified against a real kernel over SSE: a 500-line command emits 2 frames
that concatenate to the same 500 lines in order, and a 3000-line command
emits 4. Alternating text and progress is unchanged (the key switch flushes
either way, so that burst still cannot merge).
The kill test proves the leftover is removed and the live fact is untouched, but
nothing pinned the branch itself: forcing an active id down the rename-back path
now fails because ListArchived lists the live fact as archived again.
RestoreArchived moved the archive entry away only after the active file
and its index line were published. A process killed in between left one
id both active and archived, so ListArchived reported a live fact and a
retry was refused as "already active".
The entry now moves to a hidden .restoring-<id>-<stamp> name before the
active file is written. The stamp is the archive time, so the name
carries both the id and the original archive name with no journal and no
content comparison. Every restore sweeps leftovers first: an active id
means the dead restore had published, so only the redundant copy goes;
an absent id means it never published, so the entry is renamed back. A
failed sweep reports its error and publishes nothing.
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
chmod 000 does not remove read permission on Windows, so the fixtures that make
MEMORY.md unreadable would fail their own self-check there. The classification and
aside behaviour are unchanged on Unix; the directory-named case stays cross-platform.
After an unreadable index is moved aside and the derived index republished, the
store must list exactly the fact files and the index must not name the aside.
MigrateV2WithVersion is the only entry that recomputes the derived
MEMORY.md, and it returned readIndexIn's error. Every mutation path reads
that same file first, so a restored store whose index has a foreign owner
or a changed ACL froze every memory write for good: boot only warned, and
the docs' promised re-index has no other entry.
Classify the read error by identity: only fs.ErrPermission (permission
denied, a foreign owner, an ACL) and a typed not-regular-file error (a
directory named MEMORY.md) reach the rebuild. Every other error -- an I/O
failure, a resource limit, a momentary lock -- still returns, because the
file may be perfectly good and republishing would overwrite hand-written
text a retry can still read.
Before publishing, atomically rename the unreadable entry to a stamped
name in the same directory (MEMORY.md.unreadable-<unixnano>), so the bytes
survive for a later repair; a rename needs write access to the directory,
not read access to the file. If the rename fails, return the original
error and publish nothing. flushIndexIn keeps the readable path, so
hand-written text is still preserved line for line when it can be read.
No new persisted state beyond the aside copy; the MigrateV2 signature and
boot call site are unchanged, and an unreadable store directory or fact
file still aborts the pass.
Tests: the audit probe is red before and green after; controls cover the
stale readable index, hand-written survival, byte-identical healthy boot,
missing-index rebuild, and a later save succeeding after the repair. New:
a transient read error leaves the index byte-identical (red against the
previous revision), the unreadable bytes survive under the stamped name, a
directory index repairs, and a failed aside rename publishes nothing. Each
new guard was reverted alone to confirm its control goes red.
The turn-start workspace snapshot was awaited with no bound, so in a very large
workspace (a drive root: 7 s on a warm SSD for the first turn, 0.5 s after) the
first tool could not start until the walk finished, and a command's own
timeout_seconds, which only starts when the command does, never covered it.
The wait now ends at snapshotWait, the bound the end-of-turn snapshot already
has: the walk is cancelled, the snapshot records itself incomplete, and the
bundle says the turn's changes were not compared.
The row sized the name, the model chip and the status to their content and gave
the task only what was left, so with the chip present the task was squeezed to
8 px at a 360 px panel and 0 px at 280 px. Below 460 px of the list's own width
the row now takes up to three lines: name and status first, the task second,
the chip third.
profileForSkill set only the name and count, so a skill sub-agent (explore,
locate, run_skill, read_only_skill) showed no task while a task sub-agent
showed its first line. The label helper moves to the event package, next to
the Profile it fills, and both producers use it.
A task text that names a file in the host's own .reasonix/attachments directory
now gives the delegate that image (judged by the resolved path, read through the
same checks an @-reference gets), on the vision role with the note that it rides
the message, including on a follow-up turn that carried no image. The
read_only_skill runner took the turn's candidates without routing the model or
saying so, so a text-only skill sub-agent dropped them silently.
Each delta did s += piece on the row's text, so a long stream cost time and
memory quadratic in its length: 20000 progress events of one command (4.5 MB)
took about 13 s of the UI's own time. The text now grows in a buffer kept beside
the row, trusted only while the row still holds exactly what it last handed out.
The product keeps its 15 second bound on one ssh -G; the source now carries it
as a field with that default, and the test that runs the installed ssh sets it
to testenv.Budget, a bound that only reports a hang.
internal/session/control/git_push.go measured 0 from the control package's own
tests, so declaring it sensitive recorded a floor of 0 that could never fail.
The tests cover the trust refusal for both calls, a repository that is not
there, a branch with no upstream, and the commit lock released after a refused
push; the floor is now 100.
Two project folders under one git repository share a writer domain on purpose
(the lock and the visibility of each other's claims), but an undeclared writer
claimed the domain root, so sibling projects waited on each other for bash and
MCP writes, and the refusal read held X, requested X. It now claims the
selected folder; a session on the repository root still overlaps both, and a
declared path that cannot be resolved still claims the whole repository.
Problem: trusting a publisher whose plugin source has a mistyped commit
reports a misleading local-or-unsafe-source installation failure.
Root cause: plugin source validation collapsed its refusal into the generic
bad-source class before the HTTP and presentation layers could explain it.
Fix: preserve a distinct invalid-plugin-source error through publication and
installation, translate its complete-commit requirement in both languages,
and align the shared client validator with existing registry path checks.
Verification: Go vet, repository lint, owning market/serve tests, the required
builtin/boot tests, 391 frontend market/i18n tests, and TypeScript checking.
The push is the one git action Reasonix performs that leaves the machine, and
the host-only route list decides which requests a paired device may make. Both
now demand review plus security_review and are held to the coverage they have:
gitpush 84.0, serve/git_push.go 77.0, serve/host_routes.go 93.0, and
control/git_push.go 0 (its behaviour is covered by the serve and boot effect
tests, which the per-package gate does not count).
An Auto Guard card and a call that asks before it writes now give the claim
back for the wait; the lease records granted claims so the resume can name
who took the ground, the model gets workspace.claim_contended and the receipt
workspace_claim_yielded. A blocked acquisition ends at a user-set maximum wait
with workspace.claim_timeout.
closeBootTestHistoryCatalog gave the history and usage catalog closes a
fixed 5 s. Closing a SQLite catalog, and waiting out an open still in
flight, can take longer on a loaded Windows runner, and the posture
effect test failed there in cleanup with "close usage catalogs: context
deadline exceeded". The wait only exists to report a hang, so use
testenv.Budget; a close that never returns still fails the same way.
A child that shares the parent's provider starts on the depth in force now, and one
that inherits the parent's model without an effort of its own is built for it, as a
rebuild would have given. The status line reads the depth the session is on.
A provider that exposes the depths it accepts in Request.EffortOverride
(provider.RequestEffortProvider; the OpenAI-shaped adapter does) lets a session change
depth without a rebuild: the controller sets a session depth every main request
carries, updates its running selection, and accepts the change mid-turn. Serve (and so
the terminal and the desktop) and ACP both apply it in place and keep their rebuild for
a depth the provider does not take per request. The level is still saved to the user's
config so a restart opens on it.
Problem: the advancing Studio branch conflicted with the mod-turn settlement
test and changed queue pause and child-dispatch reporting contracts beside the
reviewed composition work.
Root cause: both branches added turn-completion waiting, the combined inbox
owner exceeded the file ceiling, and the effort audit still expected concrete
runtime identity in dispatch profiles after Studio restored selection profiles.
Fix: integrate exact Studio da9fbe7307 normally.
Keep both queued TurnDone and durable idle settlement. Group pause operations
in inbox_pause.go without behavior changes. Assert requested selections on
dispatch events and concrete identities on status events, retaining real
provider effort, rejection and persisted-child checks. Preserve landed queue,
billing, phone and remote protection work and the default-off composition UI.
Verification: format/vet/lint/make check; all 229 root Go packages and required
builtin/boot; affected HTTP/storage/controller and focused boot races; pause
controller regression; Desktop/SDK; 699 frontend files and 5670 tests,
typecheck/build/census; 12 browser/eight Electron renderer scenes; Electron
units (177 pass, one existing skip); 13 official ACP SDK conformance tests.
No threshold or authority expansion; no hosted check result is substituted.
The vendor's published schedule already prices every request; the status bar
now says, live, whether the model is billed at the peak or the off-peak rate
and when that changes, where the model is billed at the vendor's own rate.
Problem: repeated New session reset a blank pane's mode, copied built-in
descriptions ignored the window language, and identifier fields missed theme
styles. The picker was always visible and native archives were parsed in the
renderer. Unrelated transitions also repositioned every open popup.
Root cause: pane reuse resolved an unrequested default; editor styles depended
on a container the identifier row did not share; interface opt-in was missing;
archive transport carried parsed objects; placement had no anchor scope.
Fix: change a reused pane only for an explicit selection and preserve focus on
refusal, translate built-in copies, share field tokens, add a default-off local
picker switch that preserves bindings and the original hidden-state layout,
upload opaque files to the existing host decoder with typed localized errors,
keep the prior JSON envelope, and scope transitions to each popup's anchor or
container. Show catalog unavailability and wrap the editor components.
Verification: failing-first regressions; format/vet/lint/make check; all 228
root Go packages, required builtin/boot, HTTP/storage races and exact-binding
archive transport tests; Desktop/SDK; 691 frontend files and 5608 tests,
typecheck/build/census; 12 browser and eight Electron renderer scenes across
languages/themes/widths; Electron units (176 pass, one existing skip).
No authority, schema, safety policy or verification threshold is widened.
A balance_url on a host that is not a vendor's own now names its format
(balance_format) or is not read, and the key is not sent to it. The connection
editor edits the address and format. Existing relay entries are declared as
deepseek once, and a format the 1.x line drops from the shared file is put back
at the next start.
Problem: ungranted HTTP clients could mutate presets or switch compositions,
and empty hook selections could remove configured safety guards. A paired
remote pane could also borrow the host credential, and a selected native
extension could override the host's denied read.
Root cause: preset routes bypassed request-scoped edit grants, proxying erased
device authority, and frozen automation was treated as the whole safety policy.
Fix: gate preset mutations before store/admission access, refuse paired preset
writes before remote credential forwarding, overlay current authorized guards
outside retained automation, update their shared runner on live edits, resolve
hook inspection/save/approval/reload against one home, and mark bound runtime
packages tighten-only. Preserve ordinary unbound extension behavior, immutable
bindings, optional retained automation and the accepted Standard/Minimal UI.
Verification: failing-first HTTP, proxy and real boot/provider regressions;
format, vet, lint, make check, full root and required builtin/boot suites;
affected package and focused boot races; Desktop/SDK; frontend 689 files/5598
tests, typecheck/build and census; browser eight/Electron renderer four scenes;
Electron units. Correct an inherited mod fixture's premature teardown using
the existing durable-idle notification, with 20 independent process runs.
No test threshold, approval gate or product Close behavior is weakened.
The queued prompt starts a turn of its own, still writing under the test's temp
directory when the test returned, so the directory's cleanup failed with 'directory not
empty'. Seen as 30 failures in 40 runs with eight test processes at once, 0 in 40 after.
The test gave a cold Git Bash two seconds to write the heredoc's file; on a shared
Windows runner the start can take longer, and the failure (4.25 s) was that wait plus
the cancel. The bound is now 15 seconds, only to keep a hang from lasting forever.
The test compared the host's measured thought to the provider's 60 ms sleep minus 10,
which a loaded -race runner breaks (42 ms against 50). The size of the measurement is
the thought clock's own unit test with an injected clock; this test keeps its claim that
a measurement is taken, kept with the turn and framed identically.
A summary request the provider rejects as larger than its context (the documented
context_length_exceeded code, nothing else) is retried at 70%, 50% and 35% of the
refused input, shortened by the same deterministic steps as before; a refused request
costs nothing, so the digest is not given up until three smaller inputs were refused.
A provider that refuses every size ends as summary_input_too_large.
The inbox manifest now records why a queue is paused, as a typed code beside
the old boolean. A message approved on its card is claimed over any pause; one
let in by a standing release or dispatch switch only over a pause the host set
to guard stale input, and stays queued over one the person set. The pause and
older queued items are left as they were.
#13812 made the dispatch-time profile the concrete runtime. The effort-switch audit
test (merged beside it) holds that profile to what the call selected (empty model and
effort when it inherits), so studio went red. The selection is audited as the model
asked it; what the child ran on is on its status events, which the window already
reads, so the dispatch profile goes back to the selection.
Problem: Studio's shared remote-open lifecycle conflicts with the composition
callback, and the HTTP adapter omits the selected composition from its body.
Root cause: composition selection was carried by an App-local path rather than
the shared open-ordering owner and the transport serialization boundary.
Fix: merge exact Studio 5b1c282fed, extend
useOpenRemotePane and SseHub with the selection, preserve newest-open ordering,
duplicate-click coalescing and refusal cleanup, and keep editor styles with
their existing catalog stylesheet without widening the shared CSS budget.
Align the child-effort effect assertion with Studio's concrete dispatch model
and effort reporting while retaining provider, refusal and transcript checks.
Keep Standard/Minimal cards, the mode-only left trigger and right run state,
default-off semantics and all existing source/workspace authorization gates.
Verification: failing-first remote Hook/HTTP body regressions; format, vet,
lint, make check, full root and required builtin/boot tests; affected package
and focused boot races; Desktop/SDK Go; frontend typecheck, 689 files/5598 tests
and build; census fixtures; browser eight and Electron renderer four scenes;
Electron unit suite (176 pass, one existing skip). Local review/security review
does not replace maintainer approval or hosted CI.
[tools].disabled removes a tool from every request and refuses a call to it with
the reason; tools another feature requires declare so and stay on with a note;
a project file can only add names. Settings lists every tool with a switch.
The panel row, the full-record header and the nested card of the spawning call show
provider/model · effort, with the full reference as the title. The card takes it
from the child's status events, so a failed or interrupted run keeps it; a batch's
items each carry their own.
The dispatch profile now names the concrete runtime a call resolves to (an alias
expanded, the parent's model when the call inherits), and each child's status events,
from queued to the terminal one, carry what its run actually started with, per item
for a batch. A failed or interrupted child therefore still says what it ran on.
Bumps the go group in /desktop with 1 update: [golang.org/x/mod](https://github.com/golang/mod).
Updates `golang.org/x/mod` from 0.41.0 to 0.42.0
- [Commits](https://github.com/golang/mod/compare/v0.41.0...v0.42.0)
---
updated-dependencies:
- dependency-name: golang.org/x/mod
dependency-version: 0.42.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: go
...
Signed-off-by: dependabot[bot] <[email protected]>
Two controls the audit's contract implies but nothing pinned: a staged copy whose
retired twin came from a different publish is left untouched (no pair, no writes),
and a fact created at the old name after the kill is never overwritten — the retry
refuses and keeps both artifacts for a later attempt. The refusal only named the
conflict once the exist check unwrapped the error, so it now uses
errors.Is(err, fs.ErrExist).
The approval carries the reason code origin_text, so Studio words it in the reader's
language, hides the full-access button (which cannot answer it), and shows the call
whole; the terminal wraps the subject and reason instead of clipping them; ACP
gets a fresh allow-once or reject. The label names up to three origins, then counts
the rest. The guide says headless YOLO refuses such a call.
PermissionLists carries askOnExternalOrigin; a save without it leaves it as it is,
the rules endpoint's rebuild applies it, and it keeps the endpoint's provider-edit
grant. The desktop type and mock mirror it.
A fold that loses the transcript race records FailContextChanged, but no later
turn could release it: retriesOnGrowth did not name the code, the receipt holds
until the generation advances, and the generation only advances on a commit the
held receipt prevents. One racing append then latched automatic compaction off
for the rest of the session, across restarts, and the next overflow failed the
turn instead of folding.
Name the code in retriesOnGrowth so the append that made the fold stale is the
growth that releases the receipt. The receipt already carries InputHash and
InputTokens, and every other code keeps its hold. A plan/provider refusal still
holds, and the retry buys exactly one fold: without further growth the next
prepare neither folds again nor re-records the block.
The recovery may either finish the rename forward or roll it back. Only rollback
keeps an ExpectedRevision from before the rename valid, so the choice is a
contract, not an implementation detail; forcing the forward path makes this test
fail with name=beta revision=2.
publishRename retires the old fact file, flushes the index, then publishes
the staged new file. A kill in that window leaves the fact's only copies as
hidden .retired-* and .staged-* files: Read/List/Revisions report nothing,
the save retry by id reports "memory id not found", and a retry by name
mints a second identity for the same name.
A save by id that finds no active file now looks for the hidden pair a
single publishRename call writes, matched by the nanosecond stamp in the
two file names, with the staged copy's frontmatter carrying that id. The
rule is disk-driven and rollback-first: readable retired bytes are restored
under the old name (same bytes and mode) and reindexed, so the caller's
expected revision still names the fact and the retried save redoes the
rename itself. Only when the retired copy cannot be parsed does recovery
publish the staged copy forward under its new name. With no matching pair
nothing is written and the id is still reported not found, so a retired
copy left by a committed rename is not revived.
Covered by the audit's kill probe, an uninterrupted-rename control, a name
retry after recovery, a failed (not killed) publish, an unpaired-artifact
guard, and a cross-scope move whose pair spans two directories.
With permissions.ask_on_external_origin on (user file only, off by default), a call
that is not a read and whose arguments repeat, byte for byte, text the task's
external results gave it is put to a person as a fresh approval when only the
writer fallback (Auto, YOLO) would have let it run. Explicit allow rules, session
allows, reads and authorized MCP servers are untouched; with nobody to ask the call
is refused naming the origin.
Review follow-up after rebasing onto studio: messages_en/zh/zh_tw.go sit at the
800-line ceiling, so the two new strings move into the embedded QueueMessages and
NotifyMessages catalogues, which have their own files. No call site changes.
session.read and session.submit are separate capabilities, so a mod that only
reads usage cannot put words in front of the model. One prompt a mod queues is
bounded by [mods] submit_bytes (8 KiB, 0 for none) and refused with a typed
identity that does not spend the per-turn count. The docs say the turn a queued
prompt starts is held to this session's own approvals and sandbox.
Problem: Studio browser guards failed on 17f816d11 when the ordering guard
counted SSH rows as conversations in its local project. The remaining quick
action fixture also expected two trailing Archive/Delete buttons.
Root cause: Browser acceptance scripts still encoded the former separate
machine lists, trailing Delete action and individual folding chevrons. The
English wording scan also lacked a newly visible demo conversation title.
Fix: Scope ordering to the named local project and independently assert SSH
rows and keyboard focus are preserved. Exercise default-off and explicit
quick-action choices for local and remote rows, including hit targets,
leading Pin/trailing Archive geometry and menu access. Load the real row
stylesheet in the compact fixture and match its existing 1px spacing while
retaining 34px rows, ellipsis and contained controls. Update manual tree
checks to use project-row folding and five-item previews.
Recognize the existing demo title as user content in the wording fixture list.
Verification: Reproduced the ordering, quick-action and fixture failures
before the repair. Ran all 55 CI browser-guard commands against the built
perf harness and reran repaired guards, the affected manual tree scripts,
gofmt, root vet, make lint,
make check and the required builtin/boot suites. This changes acceptance
scripts only; production UI, workflows and budgets are unchanged.
The reviewer's blocking item: rebindInbox (called from New) started a goroutine
that read c.sink while New was writing it. Capture the sink before starting the
goroutine, for the new unrecovered notice and for the pre-existing recovered one
(it had the same shape and the same race).
Also give the refusal an identity: ErrUnrecovered, wrapped by commitManifestLocked,
with a case in the serve switch so the frontend gets inbox.unrecovered instead of an
internal fault. repolint's refusal-path rule requires that case.
The store already told a caller that the queue's bodies could not be read
(InboxSnapshot.Unrecovered), but nothing user-facing consumed it: the TUI's
/queue line and the notice stream both dropped the field, so an unreadable
queue still read as "inbox empty" and the user could not tell it from a
genuinely empty one.
The TUI mirror and renderQueueList now carry and say the unknown state, and
the controller emits a distinct inbox_unrecovered warn notice (with an
InboxUnrecovered payload naming the queue directory) beside the existing
inbox_recovered one, so its sentence is not confused with "recovered N".
Cause: the frontend contract field had no consumer on the TUI or notice
surfaces.
Blast radius: TUI /queue line, TUI and desktop notice wording, and the two
controller inbox open paths; additive field, code and payload, no on-disk
schema change, recovered behaviour unchanged.
Neighbouring behaviours tested: empty-but-readable queue, recovered salvage
count, TUI mirror JSON decoding of the real GET /inbox body, and both the
serve and control boundaries.
Why this layer: the store owns "unknown vs empty"; the surfaces that render
a queue are where a user can act on it.
Provider-visible prefix: none
salvageOrphanBlobsLocked returned nil for both "no blobs" and "blobs
unreadable", so a corrupt manifest plus an unreadable blobs directory
committed an empty paused manifest (recovered=true, recoveredN=0,
items=null) over the quarantined one. The queued bodies stayed on disk but
were unreachable, the RecoveredN>0 notice never fired, and the queue looked
empty.
The salvage now reports the read failure, and that failure is not turned
into a manifest: the corrupt bytes stay in place as evidence, the store
comes up paused with unrecovered=true on the snapshot, and no commit can
overwrite the evidence while the bodies are unreadable. An absent or empty
blobs directory is still an empty queue, not a failure.
Cause: (items, err) collapsed to (nil, nil) for directory-level and
per-entry read errors.
Blast radius: sessioninbox Open/Snapshot plus the commit path; new
additive InboxSnapshot.Unrecovered field, no on-disk schema change.
Neighbouring behaviours tested: readable-dir salvage, absent/empty blobs
dir, healthy manifest untouched, symlink blob refusal.
Why this layer: the store owns the manifest/blob transaction, so the
"unknown vs empty" distinction belongs where the read fails.
Found while demonstrating the install through Discover: until the switch and
both components read Running there is no reasonix tool, and the model falls
back to running reasonix -p in its own shell, which only looks like a
delegation.
An @token in a peer message or a background-job notice was frozen into the
prompt as a file reference, so a sender could make the receiver read a file
into its prompt, and an unreadable one paused the queue behind it. Items the
host authored are data and are no longer scanned.
Problem: current Studio conflicts in the state-root inventory and adds a move
path that strips the system message containing an immutable composition binding.
That could silently reopen a bound conversation with ordinary capabilities.
Root cause: cross-project adoption intentionally omits project instructions and
permissions, but the new mover did not distinguish immutable composition metadata
from an ordinary system prompt.
Fix: normally merge exact Studio0363720bd. Retain both rules and agent-presets
state roots. Refuse bound conversations in the common MoveSession owner before
copy/delete, preserve the source, and project the typed refusal with bilingual
HTTP wording. Keep ordinary unbound moves, ACP authentication/new-conversation
contracts, directory grants, exact dispatch inheritance and the accepted mode UI.
The preceding source-authority fix remains intact.
Verification: failing-first store and HTTP move tests now refuse blank/started
bound moves and retain the original. All226 root packages pass: mandatory2 plus
the inventoried other224; boot340.765s. Affected race suites pass. The full boot
race invocation previously reached its10minute aggregate limit; final evidence
uses one compiled source snapshot and four disjoint groups covering all753
tests exactly once, without raising the timeout. Static gates, Desktop/SDK,
frontend675files5489tests/typecheck/build/census and browser8/native4 scenes pass.
Local review/security review recorded separately from required maintainer review.
The host keeps the text of a task's external results and counts, per turn, how
many were recorded and how many later non-read-only calls carried a verbatim
string from one. The numbers ride the completion-report audit; nothing is
blocked or shown to the model.
Problem: Review requested WSL boundary evidence and removal of the unrelated
design-qa.md diff after studio introduced non-SSH machines.
Root cause: The new WSL transport supersedes a blanket rejection at shared
remote endpoints. SSH construction must still reject WSL entries, while
generic probe, browse and open must select their configured transport.
The earlier QA cleanup also deleted a document already owned by studio.
Fix: Integrate studio through 0363720bd and cover the real Hub/remotehost/pool
route with disposable WSL and SSH factories. Assert WSL failures survive the
HTTP boundary and never construct an SSH client. Cover Windows, distribution,
empty and fallback default paths. Restore design-qa.md byte-for-byte from
studio so it no longer appears in the PR diff. Retain the upstream SSH form's
refusal to overwrite non-SSH entries.
Verification: Frontend 5529 tests and production build; full serve,
remotehost, remote, attach, wslconn, config, builtin and boot suites; gofmt,
root vet, make lint and make check. No live Windows/WSL qualification claimed.
Problem: PR #13638 became conflicting after studio introduced moving a
conversation to another project.
Root cause: HubPort acquired moveSessionTo beside the archive declaration
that this branch extends with an optional host. Automatic merging also
omitted the errors import needed by the upstream WSL edit guard.
Fix: Keep both capabilities and restore the required import. Retain the
upstream local-only move workflow and the host-qualified pin/archive routes.
Remove an inaccurate rename comment so the combined component stays within
its existing size ceiling without widening the baseline.
Verification: Frontend suite (5529 tests), production build, full serve,
sessionstore, config, builtin and boot tests; gofmt, root vet, make lint and
make check. Transport coverage exercises local project moves beside remote
flags; an interaction regression covers the shared keyboard menu and dialog.
An isolated development browser confirms menu access, project selection and
cancel with hover controls off. Hosted CI remains a separate observation.
The folder in a draft came from the request and went straight to Abs, EvalSymlinks
and Stat. It is now matched, as text, against the remembered projects the person
trusted (scheduler.Folders), and everything after the match uses the list's own
spelling. Anything else is refused as schedule.workspace_unknown, in the kernel,
the CLI and the board's folder list. A run re-checks trust before it starts.
Problem: selecting a repository preset could change an already authorized MCP
server's package arguments or destination and reuse its executable authority or
credentials. The same provenance issue applies to extension args and env.
Root cause: composition parameter validation checked shape and credential
references but did not distinguish repository declarations from personal ones.
Pinning an executable does not authorize the program selected by its arguments.
Fix: enforce source authority in the shared contract. Project MCP/extension
parameters allow only supported timeouts. Validate declarations, imports, old
revisions, and both definition/resolved halves of retained executable bindings.
Keep structural history validation separate so legacy unsafe bindings remain
readable without regaining execution. Align Mock save/import, document the source
table in both languages, and add the release note. Preserve Standard/Minimal,
unbound defaults, persona, additional directories and exact dispatch bindings.
Verification: failing-first boot tests prove changed args start a subprocess and
changed URL contacts another endpoint before the fix; both now refuse before
startup. User positive controls, scope/parameter table, store/revision/old-record
and event-log history tests pass. Format/vet/lint/makecheck, required builtin/boot,
full root, contract/store/sessionstore/serve/ACP race, Desktop/SDK Go and frontend typecheck/build pass;
671 frontend files / 5472 tests pass. Local review/security review recorded;
maintainer approval and new-head hosted CI remain separate requirements.
The full boot race invocation reached its existing aggregate 10-minute limit
as another test started; disjoint test-list groups are used to finish the whole
inventory without raising that timeout or skipping tests.
The index pins the commit that last changed the package; scripts/dsh-market-index.mjs
pins it and validates it with DSH's own validator. docs/DSH.md explains Discover
and the Studio release runbook gains a precondition that keeps the pin current.
A configuration-only package (no code) in integrations/dsh-plugin, installed
from this repository's subdirectory with pnpm's path: selector, plus the
integration guide docs/DSH.md.
How to wire dsh's subagent-acp provider to reasonix acp, what each permission
policy does to Reasonix, what the parent sees, and the limits, verified against
dsh 0.2.1-rc.2.
Problem: newer Studio changes conflict with the ACP session contract and HTTP
runtime opening after the previous assembly integration.
Root cause: Studio adds a new-conversation execution-mode flag where this branch
also owns explicit composition selection and immutable inherited bindings.
Fix: normally merge Studio 4d3c09211. Add NewConversation to the one SessionParams
definition and retain it in HTTP build options. Resolve moved transcript paths
before reading either branch settings or the original composition. Keep exact
dispatch inheritance, workspace refusal, candidate rollback, ACP directories and
the accepted Standard/Minimal composer and management design.
Verification: format, vet, lint, make check, mandatory builtin/boot, full root
Go, Desktop Go, frontend typecheck/build and census pass. Frontend 671 files /
5465 tests and eight browser/four native renderer scenes pass. Affected
boot/ACP/serve/control/remote/delegation race tests pass. Provider request bytes
equal actual Studio with and without persona. Local review/security review are
recorded separately from maintainer review. The unchanged macOS computer
coverage baseline remains 88.2% below 89.8%; no floor is changed.
session/new answered -32603 for a model with no API key or no configured
provider, although initialize advertises a setup auth method. The cause is now
a provider.ErrKeyMissing / ErrNoProvider identity and the frontend maps it to
the ACP schema's -32000.
Problem: Studio advancement made the Agent composition PR conflict in boot
assembly while adding trace export and a workspace-aware mod runtime source.
Root cause: wireMCP now has a composition error result, and extension preflight
filters immutable bindings where Studio introduced its package source.
Fix: normally merge Studio ed1c127d6. Keep OTLP cleanup before returning from
wireMCP and retain workspace-aware discovery before exact composition filtering.
Preserve upstream tighten-only dispatch, replay protection, ACP elicitation and
the withheld mod switch. Keep Standard/Minimal, opt-in binding, ACP directories,
exact dispatch inheritance and the accepted composer layout.
Verification: gofmt, vet, lint, make check, required builtin/boot, full root Go,
affected boot/extension/modhost/ACP/serve/control race suites, Desktop and SDK
tests pass. Frontend typecheck/build, census, 668 files / 5450 tests, Electron
unit tests and eight browser/four native renderer scenes pass. Raw provider
requests equal actual Studio with both persona and empty persona. Local review
and security review found no new issue. Coverage gate still reports only the
unchanged macOS computer baseline, 88.2% versus 89.8%; no floor is lowered.
Rule files under .reasonix/rules, .claude/rules and the user's rules folder are
read from disk each turn. Rules without paths: join the turn as a block owed
like the standing instructions; rules with paths: are owed once the session has
touched a matching file and retracted when they stop applying. Glob parsing and
matching stay with skills' paths: through one exported entry point. Neither layer
reaches the cache-stable prefix; capability diagnostics report what was skipped.
agent.default_mode (user file only, off by default) is applied by the assembly
for builds that open a fresh conversation: Studio panes without a saved
session, the TUI without a resume, and ACP session/new. A resume, a headless
run and sub-agents never take it. Settings gets the switch.
The unverified-change gap names the command the host declined, bounded
at 80 bytes by command[:80]. A check with non-ASCII paths, common in a
Chinese project, could be cut inside a character, and the delivery card
then showed a replacement glyph before the cut mark. Step back to the
start of the character instead, keeping the budget and the mark.
Subprocesses the host starts for the agent carry REASONIX_AGENT_CHILD=1, and the
person-only schedule commands refuse when it is set, so a pty opened by a tool
no longer passes the terminal check. add prints the whole plan and requires its
short id typed back; list shows how each schedule was confirmed. The Windows
preview test compares against the resolved workspace path.
Problem: Agent compositions still carried the persona prerequisite diff and
missed newly added session lifecycle contracts on Studio.
Root cause: the branch predated the landed persona, ACP additional directories
and project-dispatch assembly paths. Combining their independent additions
also exceeded existing file/scalar-state budgets.
Fix: normally merge Studio 896552483 after verifying #13588 actually landed.
Resolve the Persona/composition imports and keep one ACP SessionParams owner.
Clone vetted extra directories through composition switches. Pass the exact
private binding into dispatched runtime assembly; reject cross-workspace
bindings before opening, and reject fresh assembly errors without a history
fallback. Ordinary unbound dispatch remains unchanged. Extract the command
catalog and Mock memory projection, and place the MCP guard with catalog
refresh state. No budget baseline is raised.
Keep Standard/Minimal cards, the left mode-name-only picker, right run status,
default-off/unbound Standard behavior and the landed user-config persona.
Update bilingual behavior/API documentation and release notes.
Verification: format, vet, lint, make check, required builtin/boot tests, full
root count1 atomic-coverage tests, the complete CI race package group, Desktop
and SDK suites; frontend typecheck/build and 667 files / 5443 tests. Electron
unit176 pass with one existing skip. Browser8 and Electron4 controlled rendering
scenes cover locale/theme/width, selection, mode cards, locked/new flows and
left/right layout. Real HTTP requests match landed Studio byte-for-byte with
user persona29139 bytes and empty persona29110 bytes, with fixed probe PATH.
ACP/dispatch regressions fail before propagation and pass afterward. Local
review and security review cover MCP contracts and dispatch/ingress authority.
Known local gate: make coverage-gate reports the untouched macOS computer
package at88.2 below89.8. The exact command on landed Studio reproduces the
same result and uncovered lines. No floor is lowered; hosted Linux gates
remain required. Hosted results for this new head are reported separately.
One draft shape and one set of confirmation facts for every frontend; a
creation is accepted only with the token of the preview the person saw; only
the machine's owner may create or resume, any reader may pause or delete.
The host also answers 'schedule exec' by running the CLI's entry, so the
supervisor can start it as the child. One kernel at a time ticks, under the
machine's ticker lock.
Scheduler.Tick reaps dead runs, claims at most one due slot inside its grace
and hands it to the supervisor; the schedule add, list, pause, resume, renew,
rm and tick commands drive it. Creating, resuming and deleting need a
terminal.
$/cancel_request cancels only the named request and answers -32800;
agent message chunks carry an opaque messageId; an MCP server's form or
page goes through elicitation/create when the client advertised the mode.
Problem: Studio's new SearXNG connection probe conflicts with the persona
types in three transport import lines, blocking this PR again.
Root cause: independent settings features append types to the same line.
Fix: normally merge Studio 6f68a14a9, preserving the search probe and persona
contracts. Keep persona-only imports separate so subsequent upstream type
additions do not repeatedly conflict. Remove one upstream trailing space.
Retain the upstream MCP scope upgrades, ACP session directories, Gemini
provider, session ordering and rail-search behavior without modifying their
implementations. Persona prompt, user-only scope and guarded writes remain
unchanged, as do the approved composition UI and default-off behavior.
Verification: three-way simulations of future settings imports merge cleanly
in all three files. Format, vet, lint, make check, required builtin/boot tests,
full root Go tests with atomic coverage, full CI race package group and Desktop
Go pass. Frontend typecheck/build and 662 files / 5425 tests pass. Chromium
checks cover persona navigation in both locales, light/dark, wide/390px;
rail search, session ordering, web access, settings rows and font scaling.
Local review and security review check the integration and fresh MCP consent;
they do not replace maintainer approval. No gate or budget was relaxed.
Problem: the sidebar PR conflicts with studio's new per-session move actions.
Root cause: both branches extend the row key handler and the mock tree's
session projection. Sorting shortcuts also bubbled into tree navigation,
moving focus to a different session after a reorder.
Fix: retain keyboard context menus and session moves together, compose mock
placement with pin/archive decoration, and consume sorting keys at their
owning row. Keep the existing remote host and body-size guards unchanged.
Verification: 5478 frontend tests across 669 files; production frontend
build; full serve, sessionstore, builtin and boot suites; gofmt, root vet,
make lint and make check. A failing-first keyboard regression and isolated
browser checks prove reordered rows retain focus and keyboard menus still
expose pin, archive and move actions.
A 403 insufficient_scope from a signed-in server now stops the model and
tells the user; granting more is a fresh approval asked every time, from
the server row or reasonix mcp auth --upgrade.
Problem: the latest Studio session-reference settings overlap the persona
port imports and development proxy, leaving this PR in conflict.
Root cause: both features add settings types at the same transport boundary.
Fix: normally merge Studio 8c311b02b, retaining both Persona and SessionRef
contracts, Mock state, SSE methods, routes and settings panels. The upstream
dispatch-consent, bounded-fold and subagent-view implementations stay intact.
Persona prompt assembly, user-only scope, guarded settings and save timing
remain unchanged. The preceding test-only commit synchronizes cancellation
observation without changing the mock provider or relaxing error/close checks.
Verification: formatting, vet, lint, make check, required builtin/boot tests,
full root Go tests with atomic coverage, the full CI race package group and
Desktop Go tests. Frontend typecheck/build and 660 files / 5411 tests pass.
Chromium checks cover persona/settings navigation in Chinese and English,
light/dark and wide/390px layouts; upstream session-reference interactions,
settings rows, font scaling and explorer counts are covered as well.
Problem: the hosted race group failed the mock provider cancellation test
when the next received chunk was an already-pending tool call.
Root cause: cancellation does not prioritize the Done case over a send that
is also ready in select. The test canceled the context and immediately read
again without establishing that the producer had observed cancellation.
Fix: acknowledge cancellation through the test context's Err method before
draining the stream. Retain the strict cancellation-error and subsequent
channel-close assertions, plus the unchanged pre-canceled request test.
The hang guard uses the existing test budget. No mock/product behavior,
runtime prompt, UI, thresholds or workflow approvals change.
Verification: reproduced one failure in 10000 race-instrumented iterations
with GOMAXPROCS=2 before the change; 10000 final iterations pass afterward.
A temporary explicit-marker mutation that ignores cancellation is rejected.
The complete CI race package group passes, alongside gofmt, vet, lint,
make check and builtin/boot tests. Prior frontend/coverage evidence applies
to the unchanged product tree.
The prompt result carried a top-level transcriptPath the ACP schema does not
allow, and status_update / mcpStatus_update went to every client, which
standard SDK clients answer with a method-not-found error each. The two
notifications now need clientCapabilities._meta["reasonix.io"].statusUpdates
and the path moves under _meta["reasonix.io"].
Problem
A development Studio shell started with `pnpm start` (`electron .`) had two
ways to outlive the launch that owned it:
- with its launcher killed outright, the shell and its kernel kept running
with nobody holding them;
- with the launcher's console closed underneath it (a terminal gone, a
background job's reader exited), the next kernel stderr line the shell
echoed raised an EPIPE that nothing handled. Electron's modal "A JavaScript
error occurred in the main process" box then held the main thread, so the
shell stopped draining the kernel and ignored SIGTERM (still alive 30 s
after it, 2/2 runs). The kernel logs a request every few seconds, so this
followed within seconds of the console closing.
Root cause
main.js echoed kernel stderr with a bare process.stderr.write and attached
no error listener, and nothing in the shell watched for the loss of the
process that launched it. The kernel already ends with the shell through its
stdin lease; the shell had no equivalent towards its own launcher.
Fix
- src/launcher.js owns both: watchLauncher ends an unpackaged shell once its
launcher is gone (POSIX reparents an orphan; Windows keeps the dead pid, so
there it is probed), and consoleEcho drops output for a console that failed
or backed up instead of throwing or queueing. shell.log and host.log keep
every line.
- Both relaunch paths (the kernel's relaunch act and the X11 relaunch) hand
the next instance over first: the launcher exits with this process, so a
relaunched shell would otherwise read its own start as the launcher dying.
- shell.log records the shell's and the kernel's pids, which is how the live
check finds them.
Verification
- node --test test/unit.mjs test/linux-packaging.test.mjs: 181 pass, 1 skip.
The new real-pipe test fails (child exits 1 on EPIPE) without consoleEcho.
- pnpm launcher-live (new, real shell + kernel, macOS arm64): launcher
SIGKILL after and before the handshake, closed console then SIGTERM, plain
SIGTERM all pass; with the fix removed the first three fail.
- pnpm smoke: 62 ok, 1 fail ("the bridge exposes verbs and nothing else"),
identical on unmodified studio.
Problem: newer Studio settings UI and persona translations both appended to
the shared English catalog, leaving the PR conflicting. Its new setting-open
notices also lacked a destination for the guarded persona fields.
Root cause: translations for an independent settings domain shared an append
point, and the newly introduced frontend routing table did not know persona.
Fix: normally merge Studio 15de9ec33, preserve both sides' translations and
extract persona's unchanged entries into its own catalog module. Route the
three guarded persona setting ids to their editor and cover actual notice
clicks. Preserve the persona's user scope, supplemental prompt semantics,
save timing, settings navigation and existing authorization boundaries.
Verification: format, vet, lint, make check, complete root Go tests with
atomic coverage, Desktop Go, frontend typecheck/build and 657 files / 5400
tests. Verified all 25 extracted translations unchanged. Chromium navigation
passed in Chinese/English, light/dark and wide/390px layouts; settings rows
and font scaling pass. The later dispatch config/catalog combination also
passes an explicit-marker build overlay. No thresholds or approvals changed.
The final-readiness pause answered session/prompt with the non-standard
stopReason "error", so a client that reads only message text took unverified
work for finished. The turn now ends with end_turn, the typed outcome in _meta
and a host-written [paused] line at the end of the streamed text.
Problem: newer Studio introduces the model-facing setting tool across the
same catalog, boot assembly and controller used by the persona feature.
Root cause: validating only the earlier base would leave their combined
authorization and request contracts untested.
Fix: normally merge Studio 4840df982 and retain the existing guarded persona
entries, user-only loading and supplemental prompt assembly. Add a real
boot.Build effect test for all three persona fields: even in YOLO a model
proposal receives a guarded refusal, produces no approval card, preserves
the user file and discloses no value through refusal or settings listing.
Verification: gofmt, vet, lint, make check, full root Go tests with atomic
coverage, Desktop Go and SDK suites, frontend typecheck/build and 654 files
/ 5384 tests; Chromium settings navigation, row layout and font-scaling
checks in light/dark and wide/narrow layouts. Earlier Electron checks apply
unchanged. No baselines, thresholds, workflow approvals or draft state change.
Problem: the approved persona head failed hosted Go tests and coverage, and
the combined Settings/config files exceeded their line budgets.
Root cause: CI tested synthetic merge 6309314327 against newer Studio
5e53be32a. Its attachment ceiling key had no settings declaration, while
independent additions accumulated in the shared settings/config files.
Fix: normally merge Studio 667f2ca16, including the upstream guarded
attachment-key declaration and ProviderEntry method extraction. Keep all
three guarded persona keys and their user-scope/prompt behavior. Extract
the unchanged settings navigation into SettingsNav, retaining keyboard
navigation, status values and the new version dot. Assert that the
attachment storage ceiling stays guarded.
Verification: gofmt, vet, lint, make check, complete root Go tests with
atomic coverage, independent Desktop Go tests, Electron tests, frontend
typecheck/build and 654 files / 5381 tests. Chromium fs-scale and settings
row guards pass, with light/dark 1280px/390px navigation checks. No budgets,
test thresholds, workflow approvals or draft state are changed.
stopReason "error" is not in the ACP prompt-turn enumeration, and the cause
travelled only on a vendor notification, so a text-only client saw an
unexplained failure. The turn now ends with -32603, the redacted cause as the
message and data.code turn_failed.
Problem: remote sidebar mutations could dial an unconfigured SSH target,
accept unbounded JSON, and enable hover actions without a user choice.
Root cause: a missing host resolved to an empty workspace without an error;
remote JSON handlers lacked a shared size boundary, and the preference
treated every value except explicit off as enabled.
Fix: classify unknown hosts before link reuse or acquisition, share a
64 KiB single-body decoder across remote writes, and restore explicit-on
hover preferences with a safe storage-error default. Preserve configured
hosts, far-kernel refusals, menu access and declared workspace resolution.
Align synchronization fixtures and keep translated directory prose in the
interface font.
Verification: failing-first unknown-host and oversized-body regressions;
full serve, builtin and boot tests; 5389 frontend tests; production build;
gofmt, root go vet, make lint and make check; isolated browser interaction
checks for default-off, opt-in persistence, menu access and opt-out.
Problem: The SSH project menu re-derived a default from the project list when
the host response omitted workspace.
Root cause: A client fallback duplicated the kernel's configured-default rule.
Fix: Pass the host response's workspace directly to the menu. Align the fixture
with the server response and cover both an explicit default that differs from
list order and an omitted default that must stay unset.
Verification: The omitted-default regression failed before repair. All 17
remote project/worktree interaction tests and the production build passed.
gofmt, root go vet, make lint, make check and builtin/boot tests passed.
The key landed with the streaming attachment ceiling without a class, so the
settings package panics at registration and every test importing it fails.
config.go is over its file-size budget on studio after two merged changes,
which fails lint for every new pull request. The model-price/override and
API-key methods belong with ProviderEntry; the one function that reads the
process environment stays in config.go so no process-input debt moves.
Problem: the Studio browser guard on the persona PR measured file and
change counts staying at 10px when interface text was enlarged.
Root cause: both explorer headers share a small element without an explicit
font size, leaving the size response to the browser's smaller-text rules.
The stylesheet is inherited from studio; macOS Chromium did not reproduce
the Linux hosted failure.
Fix: give the shared counter rule an explicit 10px base multiplied by the
existing interface scale. Preserve the default appearance and the persona
and composition behavior. Add a bilingual release note.
Verification: gofmt, vet, lint, make check, builtin/boot tests, frontend
typecheck, production/perf builds and 651 files / 5370 tests; Chromium
fs-scale, text-size and explorer guards. In light and dark, both counts
measure 10px, 11.5px and 13px across the three interface size steps.
The metadata list mapped entries to an unkeyed fragment, so React warned about the missing key when Settings -> Extensions rendered a skill's spec. Give the fragment the entry key and add a regression test that fails without it.
Problem: the latest studio settings catalog must know the persona fields, and
opt-in Standard mode must retain access to the prerequisite user-persona UI.
Root cause: the new catalog landed during the composition review, while the
initial composition page had replaced persona editing with a default adapter.
Fix: integrate the corrected persona parent and current studio, reuse its
editor in Standard mode, preserve settings-search navigation, and distinguish
user persona edits from immutable composition bindings. Keep the accepted two
cards, compact left picker and right run information.
Verification: gofmt, vet, lint, make check, root Go suite including boot/builtin,
frontend typecheck/build and 656 files / 5,385 tests pass. Parent persona keys
are Guarded; the actual request-byte and old-reader probes remain unchanged.
Problem: the new studio settings catalog rejects config fields without an
explicit classification, including the three persona fields added here.
Root cause: the catalog foundation landed after the persona implementation.
Fix: integrate studio and classify persona prefix, suffix and optional runtime
context as user-owned Guarded settings. Plain file writes do not rebuild a
runtime. Reject model proposals before any persona value can be changed.
Verification: root Go tests including settings, boot and builtin pass; gofmt,
vet, lint and make check pass; frontend typecheck/build and 5,370 tests pass.
Problem: default selections bound every new session and migrated the user's
persona, changing the ordinary path and making downgrade impossible.
Root cause: entry points manufactured a selection, persona settings were
adapted into the preset store, and blank unbound rewrites were not persisted.
Fix: keep unselected and Standard sessions unbound, preserve the user persona,
require explicit non-Standard selection, and restore durable state on failed
publication from either origin. Reuse the corrected persona parent, preserve
Standard/Minimal cards and the compact unified picker, and remove committed
composition captures. Freeze both skill discovery paths without copying locks.
Verification: root Go suite, vet, lint, make check, Desktop/SDK suites, Electron
unit tests and focused frontend suites pass. Real Build HTTP bytes match the
persona parent; its original reader can continue a new unbound transcript.
Project authorization, rollback and skill discovery regressions pass.
Problem: project configuration could replace the user persona, and complete
mode could suppress built-in guidance. The UI misstated rebuild semantics.
Root cause: persona was absent from the user-scope hold, replacement was a
supported runtime option, and settings metadata described only new sessions.
Fix: hold and clone the user's persona across project decoding, retire complete
throughout config/runtime/API/UI, and describe next-build activation. Integrate
current studio without dropping its settings or transport contracts.
Verification: gofmt, vet, lint, make check, root and Desktop Go suites, boot
scope/prompt regressions, frontend typecheck/build and 5,369 tests pass.
Problem: studio's native-video projection refactor overlaps the local-only
Agent composition metadata filter at the provider boundary.
Fix: keep the shared projection predicate and include composition metadata in
its host-only fields. Preserve stored bindings/video references and remove
answered video payloads only from model requests. Add regressions for a binding
as the sole copy trigger and for binding/video projection together.
Verification: gofmt, go vet, make lint/check, uncached root Go tests,
provider/OpenAI/boot/builtin suites, Desktop Go tests, frontend typecheck,
production build and 5,348 frontend tests. Existing mode layouts are preserved.
Problem: studio's MCP-follow changes and session compositions both own the
publication of a server tool catalog.
Root cause: the new followed-tool metadata must travel with the same catalog
whose fingerprint the immutable session binding validated.
Fix: retain follow metadata and the composition check before catalog publication.
Preserve upstream account localization and MCP-follow behavior. Add refresh,
stale-adapter and reconnect regressions with following enabled and disabled,
plus a real boot/provider test showing old bindings block drift while new
conversations can bind the changed server.
Verification: gofmt, go vet, make lint/check, root Go and boot/builtin suites,
Desktop and SDK Go suites, composition-guard race tests, frontend typecheck,
production build and 5,347 frontend tests. Browser evidence remains in design QA.
Problem: the shipped catalog and composer exposed more mode choices and status
badges than the requested Standard/Minimal workflow needs.
Fix: ship two native compositions, with a full-configuration Standard mode
and a terminal/image Minimal mode using a concise persona. Preserve retained
bindings and report retired defaults explicitly. Separate built-in cards from
custom presets, keep only the mode name in the trigger, and place run status
at the right. Keep source/lock details in the accessible label and menu.
Verification: gofmt, go vet, make lint/check, required boot/builtin tests,
complete root and Desktop Go suites, frontend typecheck/build and 5,343 tests.
Provider-boundary and custom read-only fixtures retain capability-denial
coverage. Browser checks cover light/dark, 1440px/390px and locked controls.
Problem: Host reads could borrow another pane's workspace or reuse an existing
link after its host was disabled. Review also requested explicit defaults,
boundary regressions and removal of development notes from the tree.
Root cause: remoteLink returned the first host pane before reading current
user configuration, so neither workspace identity nor the disabled policy
governed the reuse path.
Fix: Resolve the user-host default before reuse and accept only a matching
host/workspace pane. Share the declared-default-first rule with host views,
show actual and default connection paths separately, and document the rule
in both GUIDE languages. Remove three root QA notes and seven captures;
preserve their useful context in the PR discussion.
Verification: Reproduced both incorrect read behaviors before repair. Added
HTTP regressions for declared defaults, fallback, empty configuration,
reordered extras, explicit opens, project injection and disabled hosts with
existing panes. Focused frontend tests (80), production build, owning Go
serve/config and builtin/boot tests, gofmt, go vet, lint and make check passed.
Problem: the session-composition branch conflicted with the latest studio
settings, composer, peer-thread and runtime-construction changes.
Root cause: both branches extended shared port declarations, provider wiring
and the composer status row; failed-build cleanup also gained a separate
cancellable construction lifetime upstream.
Fix: retain both sets of settings and providers, preserve the top-row unified
Agent menu and responsive anchoring, and combine lifetime cancellation with
composition resource cleanup. Keep upstream accessible status labels and
text-size scaling. Add provider-boundary coverage showing the new image tool
cannot exceed review/research tool selections, plus updated visual evidence.
Verification: gofmt, go vet, make lint/check, required boot/builtin tests,
root and Desktop Go suites, frontend typecheck/build and 5,342 tests, Electron
unit tests, and browser light/dark checks at 1440px and 390px.
A stored reference carried a path the adapter opened as written, so a session
file that was edited, restored or imported could name a file no tool had
checked. read_video now records the path it verified under the file's hash in a
process-local registry; the adapter resolves through it and reports an
ungranted reference as video.file_unavailable.
Problem: The unified sidebar conflicted with newer Studio worktree grouping
and text scaling changes.
Root cause: Both branches evolved project identity and row presentation from
the same baseline, including helpers extracted by the sidebar change.
Fix: Preserve workspaceContains across grouped local and SSH projects, keep
isolated session directories in open and palette routes, retain isolation and
peer-thread markers, and preserve font scaling. Include reviewed synthetic UI
captures and a release note for the relocated controls.
Verification: The integrated frontend suite passed 5381 tests. Four focused
grouped SSH/palette regressions and the production build passed. gofmt, go vet,
make lint, make check and builtin/boot tests passed without widening baselines.
Problem: Session composition controls competed with task actions in the
bottom toolbar, and the adjacent ellipsis did not explain how to manage
presets.
Root cause: Composition selection and management were rendered as separate
controls below the input. Floating menu placement also missed fold changes
published after window resize and the end of layout transitions.
Fix: Place one Agent presets menu beside the run status, show sources and
capability notes, and group management below a separator. Preserve locked
bindings and explicit new-conversation actions. Reuse the existing Picker
with per-choice action identities, disabled-row keyboard navigation and
current-state semantics; refresh shared floating anchors after reflow.
Update bilingual guides, release notes and before/after screenshot evidence.
Verification: All 633 frontend test files / 5,225 tests pass, including
selection/management, keyboard focus, pending/failure, lock/new-session,
source identity and responsive positioning regressions. Typecheck,
production build, gofmt, go vet, make lint, make check, and required
boot/builtin suites pass. Browser light/dark, wide/390px and resize recovery
were verified with synthetic fixtures.
Account caught with String(e), so a coded refusal such as
account.signin_disabled printed "Error: " and the kernel's English
fallback instead of the wording kernel.ts already has, and its
expired-request message was set without t() and had no English. The
port's accountPoll also hand-rolled its fetch and threw the raw
response text, a JSON envelope with no code to word.
Catch with reason(e) like every other panel, put the expiry message
through t() with an English entry, and send accountPoll through post0.
Problem: Machine-grouped navigation made project switching cumbersome, and
session hover actions had inconsistent selection and keyboard behavior.
Root cause: Local and remote lists owned separate presentation and opening
flows. Remote defaults, paused stream costs, mutation deduplication and focus
restoration did not consistently survive asynchronous navigation.
Fix: Present one project list with source metadata, a common project picker,
fold-all control and bounded recent-session previews. Share neutral selection,
leading pin and trailing archive actions across local and SSH conversations.
Route new sessions and quick switching through the active environment, retain
latest navigation intent, and guard pending mutations and keyboard focus.
Forward remote session flags to the owning kernel and preserve typed errors,
configured workspaces and dedicated-stream connection costs.
Verification: 5263 frontend tests passed, production TypeScript/Vite build
passed, and focused browser and real SSH navigation checks are documented in
the QA reports. Owning Go serve/remotehost/attach suites passed. Pre-commit
gofmt, go vet, make lint, make check and builtin/boot tests passed without
widening the repository lint baseline.
Problem: ordinary binary uploads can consume unbounded workspace disk, and the
review needs direct coverage of file versus directory grants at read consumers.
Root cause: the streaming saver copies until EOF without a byte policy; the
project-config merge must not become an authority for a user disk safeguard.
Fix: enforce a named 1 GiB default while counting real bytes, retain rooted
cleanup on overflow, and return typed ceiling/source facts. Allow user config
to set the limit or explicit zero, hold it across project merges, render it
only at user scope, and strictly read it without degraded config fallback.
Cover selected-file siblings and directory link escape through actual reference
and tool consumers. Incorporate current Studio without rewriting history.
Verification: the former 8-byte/9-byte acceptance fails first and now refuses
cleanly. Full config race and focused control/serve/builtin races, frontend
tests/build, actual Electron rejection/retry, native Windows ARM64 boundary
and cleanup regressions, pinned lint and repolint pass.
Problem: the attachment PR conflicts with Studio's extracted composer helpers
and the newer worktree setup surface.
Root cause: both branches changed the Composer's chip type and helper ownership.
Fix: merge origin/studio, use its shared composerChips owner and worktree setup
flow, and retain cancellable attachment uploads and native path references.
Remove the redundant local chip type instead of duplicating the shared owner.
Verification: TypeScript checking, 155 focused frontend tests, production build,
attachment/CSRF/cloud/control/fileutil Go regressions, 169 Electron unit and
packaging tests (one existing conditional skip), pinned lint and repolint pass.
Review follow-up: the audit helper used unix.Flock with no build tag, so lint's
typecheck and test (windows-latest) failed with 'undefined: unix.Flock'. Take the
lock through internal/base/filelock's TryAcquire, which has the same unix and
windows implementations the store uses, so the held-lock tests run on every platform.
saveNetwork, uploadWallpaper and clearWallpaper hand-rolled their fetch
and threw a plain Error, so the code the kernel sent never reached
reason(): an unsupported wallpaper read as the kernel's English fallback
in a Chinese window, a failed clear as a path and a status, and the
existing wording for network.rejected and wallpaper.* went unused. They
now go through post0 and del, whose fail() keeps it.
SaveNetworkSettings also stops prefixing its refusal with a Chinese
sentence: the frontend frames network.rejected itself, and the prefix
would read twice in one language and as a fragment in the other.
SsePort still declared exportPlugin, dryRunHook, trustMcp, reconnectMcp,
parseMcp and probeProvider after they were split into its faces, and a
method on the outer class hides the face's. So the faces' dryRunHook and
probeProvider, which go through post0 to keep a refusal's code, never
ran: probeProvider threw the raw response text, and onboarding showed a
refusal envelope as its message and could never offer the vendor-console
link it keys on provider.probe.unauthorized. exportPlugin was the other
way round, the face holding the stale copy.
Drop the six copies, move the current exportPlugin into SseExtensions,
and guard the chain so no face redeclares one below it.
renderSolveProfiles folded every run that reached a correct checkpoint into
the boundary medians, including runs whose trajectory was never read. With
-checkpoints and no -trajectories those runs carry zero round and
verification tallies, so the report printed "rounds before 0 · rounds after
0 · verifications after 0" as if they had been measured.
Only runs whose split was actually recorded now fold in, and a suite where no
run recorded one says the boundary was never split instead of printing zeros.
The all-measured medians are unchanged.
Problem: an admitted turn could fail before adding its user message and unlock
its composition. Controller-owned continuations could strand ACP config changes,
and MCP disconnect could remove code needed by a subsequent reconnect.
Root cause: the started latch followed message insertion, ACP drained only its
own prompt returns, and connection cleanup owned session package resources.
Fix: durably latch composition admission before executing or acknowledging a
turn and refuse work while the latch is pending. Notify owners at real idle
transitions and drain ACP changes through its existing serialization domain.
Separate transport state from package lifetime, retain script sibling bundles,
exclude VCS metadata, reject linked transcript exports and freeze late tool names.
Add ACP exact-binding/history tests, actual old-reader evidence, bilingual notes
and the complete light/dark wide/390px before-and-after screenshot set.
Verification: deterministic async/sync admission and failed-save regressions,
ten staged background-continuation/config-switch runs, real MCP reconnect cycles,
shared lifecycle race tests, package coverage, vet, lint and repository checks.
Frontend full suite and independent Desktop/SDK/Electron suites remain green.
Refs: #13613
Problem: creating a worktree dropped keyboard focus when the composer became
inert, returning to editing did not restore it, and terminal pages still
reported waiting or mapped cleanup failure to a confirmation state.
Root cause: draft restoration attempted focus before React removed inert;
the preparation page had no focus owner, phase fallbacks conflated terminal
states, and details/edit actions shared a configuration identity.
Fix: focus the preparation heading on entry, recover focus when a removed
phase action leaves the body active, preserve surviving detail focus, and
restore the composer only after the stage-clearing commit. Use distinct
view action IDs and explicit terminal labels, and show elapsed waiting only
for active phases without changing existing typography or button recipes.
Verification: failure/cancellation interaction regressions, phase/disclosure
unit tests, real-browser focus and draft-retention checks, English dark-page
labels and shared button hierarchy, frontend typecheck/build/census, and
repository lint/check; full frontend: 634 files and 5,257 tests passed.
No Git or backend behavior changed.
setShellKillTree on Windows ran its own taskkill /F /T inside
exec.Cmd.Cancel with no time limit. #13323 bounded the taskkill in
proc.KillTree because Wait does not return until the cancel hook has,
but this hook never went through KillTree, so a PDF text extraction past
its 8 s deadline could still hang on a taskkill that never answers.
Delegate to proc.SetCancelKillsTree, the helper proc.RunCommand already
uses, so the only taskkill left is the bounded one in proc.
Problem: Studio persona settings cannot select an independent tool and module
combination for each conversation, or preserve that combination across reloads.
Root cause: discovery and runtime construction follow mutable shared configuration
without a versioned session composition or a shared admission boundary.
Fix: introduce versioned scoped definitions, retained resources and authoritative
session bindings. Assemble isolated runtime selections, enforce tool ceilings,
pin MCP contracts, and share the lifecycle gate with first-prompt admission.
Expose independent HTTP, CLI, ACP and Studio management and selection controls,
with migration, native import/export, diagnostics and read-only history recovery.
Verification: composition mechanism tests, provider-wire cache tests, lifecycle
rollback tests, sensitive-boundary regressions, frontend typecheck and all 5,219
frontend tests; gofmt, vet, lint and repository checks. Browser light/dark wide
and 390px screenshots and real Electron backend interactions are included.
Refs: #13588
Review follow-up: Partial now accumulates across legs (a killed first leg plus a
complete second leg is still a lower bound), and the meter comparison skips a
Partial run the same way it skips an unaccounted one, since its missing tail is
not a divergence. Rebased onto current studio; the stub tests use requireShellStub
so test (windows-latest) can run.
Problem: hosted atomic coverage intermittently lost the MCP resource tool,
and CodeQL reported an unchecked filesystem probe during worktree cleanup.
Root cause: a queued cache callback read clients after Host.Close cleared
handshake metadata. Cleanup accepted a result path without independently
checking the trusted managed root used by its construction owner.
Fix: snapshot advertised capabilities and instructions before releasing the
deferred spawn; keep only persistence asynchronous. Capture the managed root
once for creation and rollback, validate a local relative descendant before
probing the filesystem, and retain registration and unchanged-head checks.
Verification: deterministic shutdown/cache and wrong-boundary regressions;
plugin atomic coverage and race tests; boot atomic coverage; worktree/serve
race checks; native Windows rollback cases; local CodeQL path query; root
lint, Windows lint and changed-file repository checks.
A leg whose final record was missing but whose <metrics>.partial snapshot was
read was marked Unaccounted, which gatherSuiteStats skips outright. That
contradicted Partial's meaning — real numbers that stop at the last snapshot
and belong in the totals — so the recovered tokens, cost and lower-bound hint
were all unreachable. Only a leg with neither record is now Unaccounted; a
recovered snapshot stays accounted and sets Partial, matching the semantics
swebench mode already uses.
renderMeterAccounting also treated PromptTokens+CompletionTokens == 0 as an
absent harness measurement, inferring state from a value. A persisted record
saying zero is a valid zero, so the guard is now the explicit Unaccounted flag
alone.
Tests: the copied audit case that asserted unaccounted=1 for a sidecar-recovered
run encoded the wrong semantics and now asserts Partial=true, Unaccounted=false
and that 1200/80 and the cost reach the aggregates; the divergence fixture that
relied on zero tokens sets Unaccounted explicitly, and a new case pins that a
persisted zero is priced on both sides. The record-less case and the healthy-run
control are unchanged in intent.
Problem: failed or cancelled worktrees remain registered on Windows, and the
cleanup-failed screen leaves users without a concrete recovery procedure.
Root cause: native backslash paths are compared directly with Git porcelain's
slash paths. Cleanup uncertainty deliberately fences replacement creation.
Fix: normalize both registered and expected paths while preserving symlink
resolution and the unchanged-head guard. Cover native rollback and changed
branches, wire the Windows regressions into PR CI, and explain draft retention,
manual reconciliation, runtime closure and retry from the source folder.
Restore the existing design QA content and metadata, append only workspace
acceptance, and remove all eight screenshots from the final PR tree.
Verification: native Windows regression fails before the fix; the fixed worktree
package passes 36 tests with 6 platform skips. macOS worktree suite, focused
serve/worktree race tests, Windows lint, workflow contracts, 5,245 frontend tests,
typecheck/build, census and repository lint pass. Hosted CI still needs approval.
Problem: Windows smoke reported an empty live catalog in the disabled-MCP
effect test's fresh always-loaded phase; the fixture expected one read tool.
Root cause: Build returns while cold discovery and always-loaded preconnection
can still be in flight. The fixture immediately issued an explicit connection,
leaving competing handshakes and initial catalog publication in its setup.
The adjacent always-load effect tests already await the boot-owned connection.
Fix: Wait for that connection in cold and always-loaded phases before explicit
registration. Keep cached deferred startup explicit and retain all filtering,
restart, catalog and provider assertions. Report held tools and server catalog
diagnostics if the allowed tool is still missing.
Verification: Related disabled/always-loaded/held-definition effect tests pass
normally and under the race detector. go vet, make lint and make check pass.
Full builtin and boot suites pass as well.
Native Windows CI must confirm the replacement candidate.
Problem: an explicit source refresh can repopulate the palette after switching
workspace, port or session, and an older initial read can overwrite a later read.
Root cause: only the initial fetch had a lifetime fence; manual reads published
without a shared generation. The browser guard still awaited a retired hover card.
Fix: put initial and manual reads behind one latest-read generation, invalidate
it on scope changes, and verify the unified palette before and after task delivery.
Verification: four deferred-response regressions fail without the fix and pass
with it; 633 frontend files / 5,245 tests pass, typecheck/build, census invariants
and fixture verification, repository checks, and browser palette interactions.
Upstream workflows remain pending repository-admin approval, not passed CI.
Problem: the PR overlaps the current Studio font-scaling update.
Root cause: both changes replace the workspace stylesheet, and hub additions
bring its combined source over the repository file-size ceiling.
Fix: retain the new palette with shared, scalable typography tokens; preserve
all upstream behavior and extract hub close/shutdown ownership together.
Verification: 633 frontend files and 5,241 tests; typecheck/build; affected
worktree, serve, control and boot suites and race tests; Desktop Go suite;
make lint/check; fresh production browser palette with no console errors.
Problem: workspace configuration disappeared after the first task and creating
an isolated worktree left the composer without visible progress or recovery.
Root cause: workspace selection, Git branch switching and runtime construction
had separate UI lifetimes, with no cancellable preparation/publication boundary.
Fix: keep one searchable workspace palette across conversation states; expose
real preparation phases with operation-ID reconciliation, retry and cancellation.
Serialize cancellation against publication, terminate checkout descendants and
clean unpublished resources before acknowledging cancellation. Preserve task
and attachment drafts, and reuse existing typography throughout status screens.
Verification: 5,231 frontend tests, typecheck and production build; root and
Desktop Go suites; affected backend race tests; lint and repository checks;
real Git browser checks for switch, failure/retry, cancellation and handoff.
Include light/dark and wide/narrow before-and-after screenshots and design QA.
Problem: CodeQL still reported a path-injection alert at the worktree
registration read, and a registration symlink could read outside its metadata
directory before assigning the checkout to a project.
Root cause: Validating checkout identity and the registration backlink did not
constrain the filesystem operation that reads that backlink. The plain
os.ReadFile call followed symlinks outside the selected Git directory.
Fix: Validate the checkout metadata path relative to the shared repository,
open that directory through os.Root, and read the fixed gitdir entry through a
second root confined to this checkout's metadata. Preserve relative backlinks
and internal symlinks while refusing escaping registrations.
Verification: A real-Git symlink-escape regression fails on the original code
and passes with the fix. Worktree/workspace identity suites, grouped-project
HTTP regressions, focused race tests, Windows test compilation, go vet,
make lint and make check pass. Local CodeQL CLI 2.27.1 with go-queries 1.6.11
reproduces the exact remote alert before the change; afterward that alert is
removed and the same path-injection query introduces no new results.
Builtin and boot integration suites also pass.
Problem: Checkout labels could push a conversation title beyond its breadcrumb
at phone widths and high interface scale.
Root cause: The fixed-width directory badge consumed the title's reserved
space even after project and folder controls were hidden by the narrow layout.
Fix: Group project and checkout context in a shrinkable clipped flex child,
allow labels to ellipsize, and preserve the title's existing four-em floor.
Document the shared layout constraint without weakening browser guards.
Verification: The eight failing light/dark width-and-zoom cases now pass in
perf/narrow.mjs. Title spacing and overflow, intrinsic layout and hover guards,
12 related interaction tests, typecheck, production build, vet and repository
checks pass. The Go implementation is unchanged by this layout correction.
Problem: Grouped projects could open the source after an explicit worktree
selection, hide source-alias history, omit inferred launcher exclusions, or
remain unusable when their source folder disappeared. Copied Git metadata
could also claim another checkout's project identity.
Root cause: Display identity was reused as execution and catalog authority.
Project inference duplicated an unbounded marker walk without validating
managed checkout containment or the reverse Git registration.
Fix: Preserve selected directories and all catalog members, choose a surviving
project directory, and use complete member roots for editor discovery and
launch. Reuse shared checkout identity and validate its managed boundary,
local prefix and registration backlink. Merge studio, move root resolution to
its project owner to preserve the file-size budget, and align the title guard
with the intended checkout label while retaining spacing and overflow limits.
Verification: Failing original-head HTTP/path regressions pass after the fix.
Frontend tests (629 files, 5,221 tests), production build, focused race tests,
go vet, make lint and make check pass. Added a 100-root tree benchmark and a
deterministic delayed-header test; repository suites and browser guards cover
adjacent behavior. Hosted checks must validate the replacement PR head.
TestCredentialSinkRegistry walks internal/frontend/serve, where serve's
own tests create and remove extra-* folders through tempAside. Run in
parallel with those tests, the walk can list such a folder and fail when
it is gone a moment later, the same race that failed the storage
state-path guard. Skip a vanished entry with testenv.VanishedMidWalk.
Problem: Studio lists a managed worktree and its source checkout as two
same-named sidebar projects, scattering one project's conversations.
Root cause: Sidebar ownership, session catalogs, and project operations use
checkout paths as project identities even when they share a Git repository.
Fix: Resolve project ownership from managed checkout Git identity while
retaining each transcript and runtime working directory. Merge session
catalogs before paging and keep registration, ordering, capacity, and removal
consistent with the project group. Show the checkout type in the chat header
and mark isolated history without migrating persisted data.
Verification: Serve/worktree/workspace identity and builtin/boot suites,
focused race tests, Desktop Go tests, frontend tests and production build,
go vet, make lint, and make check passed. Browser interactions and an installed
macOS package verified grouping and original/isolated conversation navigation.
GET /workspace/file takes an optional root, checked against the grant list and re-vetted on read through the decision the picture route uses. The workbench carries the folder, never saves, and says why.
A queued message that froze an image cannot be admitted as mid-turn guidance. The manifest now records that (images), and the queue row says so instead of 'not delivered yet'.
recordStalled emitted a notice with no code, so the frontend showed the kernel's English sentence as is. It now carries job_stalled and a JobNotice payload with the quiet span, like the other job notices; the English text stays the fallback.
Hosted Web enables the multiplexed event stream for every pane, but a
Studio kernel without GET /events/mux answers it with the SPA shell. The
mux client waits for the stream's open frame to name its connection, so
no pane ever subscribed and none fell back to its own stream: the page
kept loading and stopped receiving events until a reload rebuilt it from
a snapshot.
RemoteEventSource is a polled stand-in that cannot carry that protocol,
so a hub reached over the relay now reads each pane's own /events. A
local hub keeps the shared stream.
The event contract is unchanged: attach, resume, cursor, watermark and
the pending-prompt attach all keep the code paths PR #13499 fixed.
Problem: CodeQL reports uncontrolled paths in streamed upload cleanup and
attachment creation. A replaced storage directory can redirect failed-upload
cleanup to an external same-name file; a linked .reasonix parent can escape too.
Root cause: filename validation and path-based writes/removals do not preserve
directory identity across filesystem changes during I/O.
Fix: use os.Root for workspace-contained directory/file creation, share an owned
file lifecycle between streamed and legacy byte writes, and retain the directory
handle for failure cleanup. Publish the existing untracked marker atomically
through the same rooted directory rather than reopening its pathname.
Verification: full control/serve/fileutil suites, focused race tests, vet,
pinned linter and repolint passed. Native Windows 11 ARM64 regressions passed
with directory junctions and protected cleanup, plus large bounded streaming.
The independent replay test failure also reproduces with the PR's Go changes
removed through a base-source overlay; no replay behavior or CI gate is changed.
Fold the fragments merged after the first notes commit into 2.43.0.md and remove every consumed fragment, so the release commit leaves release-notes/studio/unreleased empty.
Problem: Review requested a user-visible release fragment and clearer memory
bounds. The live regression also assumed new-session activation must create
a second runtime, which is false when an existing blank pane is reused.
Root cause: A workspace may already own a reusable blank pane, particularly
when native Windows roots have matching canonical spelling. The old setup
waited for a POST and a new pane identity instead of editor readiness.
Fix: Wait for boot completion at a stable viewport, allow a blank-pane reuse,
and preserve assertions for the captured workspace, one shared intake route,
saved upload size and four ready attachments. Allow an explicit test host
binary. Add bilingual release notes and state that memory, not total disk
consumption, is bounded by streaming.
Verification: Windows 11 ARM64 control, stream, HTTP upload/image-limit and
exact-file read-tool regressions passed. Electron 44.2.0 exercised picker,
paste and drop of a 111 MiB file and a 65 MiB streamed File with four ready
attachments. Release-note tests, repository checks and script syntax passed.
Problem: The full frontend suite rejects the upload abort listener as an
unresolved interaction registration.
Root cause: The syntax-based census does not infer a Request.signal member's
AbortSignal type and cannot distinguish it from a possible user-input target.
Fix: Keep an explicitly typed upload signal and teach the shared receiver
certifier to recognize scoped AbortSignal bindings. Preserve unresolved
receivers and refuse borrowing an outer signal type across shadowing.
Do not add a file exception or raise any census or lint ceiling.
Verification: Abort receiver, actions census, cloud upload and attachment
regressions, frontend typecheck, production build and repository checks pass.
Problem: Large ordinary file attachments cause avoidable whole-file
allocations and exceed the legacy JSON attachment transport limits.
Root cause: Picker and paste intake Base64-encode files even when the shell
already knows their native paths. Pathless uploads also pass through JSON.
Fix: Share native path references across picker, paste and drop. Stream raw
ordinary-file bodies to bounded disk writes and clean up failed or cancelled
uploads. Grant external-file access only to the selected file. Preserve
image, legacy JSON and cloud relay limits; bound cloud intake before
materializing an oversized body. Document the transport and live regression.
Verification: Owning Go package suites, focused race tests, go vet, lint,
repository checks, frontend typecheck, production build and attachment
regressions passed on the original tested baseline. Real macOS Electron
exercised native picker, paste and drop with a 111 MiB file and an exact
65 MiB pathless upload. Windows native runtime verification remains open.
A window that gives each pane its own EventSource spends one HTTP/1.1 connection per pane, and a browser origin holds six, so the sixth stream starves every stop, submit and read behind it. GET /events/mux is one stream for all panes of a window; PUT/DELETE /events/mux/{conn}/panes/{id} subscribe a pane with its own cursor. /events and /events/replay are unchanged.
RemoteEventSource now leaves the attach cursor empty so the kernel hands a late client the pending prompt. The tree stream shares that class but reads the tree rather than history, so an empty attach for it re-read the tree on every poll and failed tree_events.test.ts. The attach query is now chosen once from the path: the main stream attaches with no cursor, the tree attaches past the watermark as it always did.
Verified with the whole frontend suite: typecheck, 5139 tests, census, census:verify, build, and no fixtures in the bundle.
Home declares no entries, so the survey treated it as sole occupant of its
directory and counted the sessions and archive that state declares in the
same folder (one folder on a default Windows install).
The cost chip drew a literal "¥" default, so a fresh session read
"本轮 ¥0.00" in a currency nothing had priced, whatever the display
currency was. Leave it out until the first priced round or restored total.
The polled attach took its pending-prompt snapshot under promptEmitMu but read the broadcaster watermark after that lock was released. A prompt raised in between was numbered at or below the watermark the client then adopted as its cursor, so the next resume skipped it — the original bug in a narrow window.
The watermark is now read in the same critical section as the snapshot, and before it: a prompt raised after that point is numbered above the cursor the client adopts, so the worst case is a prompt named twice rather than one lost. writeEventsReplay reads the watermark once and passes it in, instead of the writer reading it after the lock.
The shell's no-store comment is trimmed to the comment budget so the lint gate passes.
The argument completion of /goal, /memory, /mcp, /skills, /plugins, /theme,
/currency, /reasoning-language, /ask-timeout and /feedback returned English
literals; read them from the i18n catalog like /language and /effort do.
TestEveryTopLevelStatePathIsAnOwnedEntry walks every package under
internal/ and failed the moment WalkDir could not open an entry. The
serve, control and config tests take their extra folders from their own
package directory (tempAside), so when go test runs packages in
parallel the guard can list one of those folders and find it gone a
moment later:
open .../internal/frontend/serve/extra-3867033970: no such file or directory
The config package's state-root walk already tolerates this through
vanishedMidWalk, including the sharing-violation and access-denied
errors Windows gives for a directory being deleted. Move that helper to
testenv as VanishedMidWalk, unchanged, and use it from both walks.
TestCreateFromBranchKeepsSourceAndSeparatesTasks required the checked
out README to be exactly "base\n". Git for Windows ships core.autocrlf
true in its system config, so the worktree holds "base\r\n" and the
test failed on Windows CI. Trim it as the sibling dirty-source test in
worktree_test.go already does; the check still tells the selected
base apart from the current head and the dirty source.
TestAuditLegacyCacheUpgradeReportsOnlySuccess blocks the legacy-key
upgrade write by chmod-ing the cache dir to 0555. On Windows that only
sets the read-only attribute, which does not stop new files in a
directory, so the upgrade reached disk and the test failed at its own
fixture check on every Windows CI run since it landed.
Deny write to Everyone with icacls there instead, the mechanism
internal/contract/config's makeDirReadOnly already uses.
Problem: Studio CI caught ArrowDown selection returning to the default row
before Enter could confirm the selected reference.
Root cause: candidates rendered before a passive effect reset selection and
keyboard ownership. That later update could overwrite navigation after the
rows had already committed.
Fix: store the completion answer, active row, and keyboard ownership in one
state value. Publish each answer with its default selection and remove the
deferred reset. Guard navigation against an answer that has become empty.
Verification: four deterministic reference/slash publication and early
navigation regressions fail on the old hook. The PR merge snapshot with this
patch passes 616 frontend files / 5,132 tests; 92 completion/composer cases
pass with --repeats 10. Typecheck, build, census and census verification,
go vet, make lint, make check, and builtin/boot tests pass. Chromium confirms
ArrowDown plus Enter accepts the selected reference and slash command.
Problem: Review requested proof that production completion removes exact
reference candidates independently of the frontend MockPort fixture.
Root cause: The frontend interaction tests and the existing control unit
test did not exercise the registered GET /complete route together with its
controller data, scoped filesystem lookup, and UTF-16 boundary.
Fix: Add seven real HTTP cases using the production handler and disposable
workspace files. Cover partial paths, exact filenames, prose and emoji
prefixes, escaped nested paths, retaining a genuine longer alternative,
and an exact final alternative. No production filtering behavior changes.
Verification: The completion HTTP tests and full serve package pass, along
with go vet, make lint, make check, and the builtin/boot pre-commit suites.
Removing only the existing control.dropTyped call via a temporary Go build
overlay makes six HTTP subtests fail on the retained identity insertion;
this checks regression sensitivity and is not a production defect claim.
Brings the three Web fixes onto the current studio line: the hosted page's own-fetch stale check, the pending approval/ask handed to an attaching polled client, and the no-store shell.
The one conflict, internal/frontend/serve/web_entry_test.go, was both sides appending a test at the same point; both are kept.
Problem: Review requested coverage for Enter after a manually completed
reference, including the case where another candidate is still available.
Root cause: The kernel already removes an insertion equal to the typed
token, but MockPort kept that row and manufactured an extra confirmation
step in development fixtures.
Fix: Keep MockPort's candidate filtering aligned with the kernel and add
four composer regressions. A full path with no remaining candidates sends
in one Enter. An open menu containing a genuine alternative still confirms
that alternative before submission, as required by the interaction contract.
Verification: The three exact-path cases failed against the old fixture.
All 5099 frontend tests, typecheck, build, make check, go vet, and make lint
pass. The existing kernel dropTyped regression passes. Real-browser checks
confirm single-Enter submission for exact paths at wide/light and phone/dark
sizes. The builtin and boot pre-commit suites pass.
A read-only tool that turns a video into a few time-labelled pictures for a
model that reads images. The container is named from the file's own bytes and
ffmpeg is forced to it with file-only protocols; failures carry typed codes.
It is reached through use_capability and stays out of the schema prefix (#13399).
The polled transport attached past the watermark and dropped what came back, so a page opened after a prompt was raised never saw the approval or ask the run was waiting on. An attach now carries the pending prompt to that client alone, and the client reads the frames it is sent.
A hosted page checked for a newer build through the global fetch, which remoteHub has already replaced with a tunnel to the kernel; it compared its own build against the kernel's and reported itself behind forever. The check now reads through the page's own fetch, bound before the swap.
The shell names which build is current and was served with no cache policy, so a reused copy left an open page behind and reloading landed on that same copy. It is now served no-store; the content-hashed bundles under /assets keep their long cache.
With an open-session limit set, opening one more session reuses or closes an
idle pane. A calm note now names the conversation, the limit, and that it is
still in the list; a note is a toast tone with no report button (#13419).
Problem: Pressing Enter while an @ file or directory candidate was visible
could submit an unfinished prompt instead of confirming the reference.
Root cause: The menu highlighted its first item but only owned Enter after
explicit navigation. Directory acceptance also shared the descent path.
Fix: Let the visible selection own Enter and distinguish confirmation from
Tab descent. Finish accepted references with a separator, preserve existing
whitespace and trailing text, and clear the old completion before drafting
continues. Update both locale hints.
Verification: Twelve new composer regressions reproduce the old behavior
and cover files, directories, nested Tab descent, pointer acceptance,
escaped paths, send preferences, Shift keys, IME, and Escape. The full
frontend suite passes (609 files, 5095 tests), alongside typecheck, build,
UI census, make check, go vet, and make lint. Browser interaction was
verified with the local MockPort workspace.
A button on a finished tool card repeats that call's own recorded card in a
section at the end of the transcript, as a view only: nothing is stored or
sent, and the original stays where it happened (#13394).
The transaction-free fallback loaded the disk manifest without the RunID
isolation the locked path applies, so a queue another run (or a previous run)
left behind could be dispatched while the lock was momentarily unavailable.
Refuse a foreign manifest with ErrForeignRun, which the dispatcher already maps
to a retry, and give the frontend a coded refusal for it.
NextQueued returns (InboxItemMeta, bool, error) and, when the disk
transaction is unavailable, reads the published manifest without the lock
(commits land by atomic rename, so that snapshot is complete). The
dispatcher maps a read error to inboxDispatchRetry instead of reporting
the inbox idle.
Narrowed per review: the permanent 1s long stop (armInboxDispatchLongStop,
its constant, its arming and its stop/reset wiring) is removed, so the
retry schedule past the existing bounded burst is exactly what it was
before this branch. The dispatcher audit case is rewritten to assert the
in-scope contract - a failed queue read schedules a retry and does not
report idle, and the durable item survives - rather than delivery after
the burst was exhausted, which was the removed policy.
NextQueued discarded beginDiskTransactionLocked's error and answered from
the in-memory snapshot, so a transaction that could not run — another
process holding the lock, EACCES, ENOSPC, a transient EIO — looked exactly
like an empty queue. dispatchInboxOnce read that as inboxDispatchIdle and
stopped with the durable item still queued, permanently.
NextQueued now reports that failure as an error, and before it will claim
the queue is empty it falls back to reading the published manifest without
the transaction: commits land by atomic rename, so that snapshot is
complete, and repair work stays behind the lock. A genuinely empty or
paused queue is still idle.
The bounded burst (three attempts, ~750ms) used to abandon work its own
scans had seen. Past the burst the dispatcher now parks on a slow
production timer that keeps re-checking until the item advances or the
queue reads empty; reset and Close cancel the pending timer.
A read error maps to inboxDispatchRetry, so an unreadable queue retries
instead of ending dispatch.
When an install already has frontend-next/dist beside the binary, upgrade
reads the page out of the verified archive, validates it in memory, swaps it
in before the binary and puts it back if the binary does not land (#13406).
ListSessionOrder keeps a row whose branch-meta sidecar cannot be read, so one
broken sibling cannot hide a folder, but every column of that row is then a zero
value. recoveryArchiveLineage consumed that row as 'not a recovery copy of this
conversation' and SetSessionLineageArchived reported success while a copy of the
row stayed active and listed on its own. The lineage walk now reads each listing
row's sidecar itself and refuses the transaction, so the archive endpoint
reports the read failure instead of a partial success.
ListBranches dropped a branch whose sidecar could not be decoded while a missing
sidecar built defaults and kept it; interruptedTurnContinuedOnRecoveryBranch
read that absence as 'the turn did not continue on a recovery branch' and
stripped the interrupted turn's messages. An unreadable sidecar is now handled
exactly like a missing one.
Problem: release notes did not mention the focus behaviour shared by all
narrow composer toolbars, making the compact empty state look accidental.
Root cause: the behaviour and pointer stability rationale were documented
only in the internal style contract.
Fix: add matching Chinese and English release-note entries explaining that
empty narrow toolbars remain compact on focus and expand for draft text,
attachments or an open menu, keeping pointer clicks on the same button.
Verification: 12 release-note rendering and Markdown reference tests passed;
make check and git diff --check passed. Documentation-only change.
The cli job builds the page once and packs it beside the executable under
frontend-next/dist, where serve.FindPage looks, through a packer and a checker
that are run by the job and by tests (#13406).
Problem: the five composer menus appeared oversized beside the application's
compact toolbar and other menus.
Root cause: the shared composer menu style imposed 15px labels, 13px notes,
380px panels, 20px corners and generous spacing on every menu. Workspace
toggle and note borders also produced adjacent divider lines.
Fix: use 13px labels and 11px notes, 12px corners and 8px panel insets; choose
280-340px widths by content. Tighten rows, headings, search, chips, checks
and the workspace switch together, leaving one workspace note divider.
Keep all changes scoped to composer menus and update their style contract
and bilingual release note.
Verification: 22 related suites / 226 tests; production build; make lint,
make check and git diff --check. In-app browser checks cover all five menus
in light/dark themes, 390px and 320px viewport bounds, model/branch search,
Escape dismissal and focus restoration. Mock data only.
FindPage now also looks beside the file an executable link resolves to,
after the directory it was invoked from, so a package manager that links the
binary onto the PATH still finds the page next to it (#13406).
Problem: the workspace PR conflicted with the current studio composer, and
restoring its toolbar slot exposed narrow-pane overlap and pointer failures.
Root cause: the new side-question control shares the same toolbar; a fixed
model minimum could overrun its flex group. Focus-dependent phone folding
also moved buttons between pointer press and release.
Fix: retain both worktree setup and the upstream side-question button; allow
model shrinking and internal wrapping at the narrowest widths. Keep empty
phone toolbar geometry stable on focus and extend browser guards to cover
both workspace variants, pointer opening and nested button overlap.
Verification: 5,021 frontend tests; affected suites 126 tests after CSS edits;
production and perf builds; make lint, make check and diff whitespace checks;
hoverradius, branch-menu, modelrows and extended narrow browser guards;
light/dark worktree selection, Escape/focus, menu bounds and first send.
Run analysis and Workbench become icons on the window bar for the focused
pane; the Conversation / Run analysis / Workbench row, the plan peek and
their styles are removed (#13369).
Problem: workspace and branch controls were moved outside the composer, and
the footer-only 8px override broke the shared toolbar radius browser guard.
Root cause: the workspace slot bypassed the existing branch toolbar layout
and overrode the common mode button geometry.
Fix: restore both controls to the original toolbar slot, reuse branch layout
and pill styling, remove the footer overrides and keep runtime statistics
below the card. Cover new and existing conversations in the browser guard,
load the production menu stylesheet in the perf fixture and align the docs.
Verification: reproduced both layout assertions and the light/dark radius
failure before the fix; 4,831 frontend tests passed; production build,
make lint and make check passed; hoverradius, branch-menu, modelrows and
narrow browser guards passed.
A sticky bar of groups (all, new replies, needs your reply, open, closed)
with counts sits at the top of My feedback; rows that do not match are
hidden rather than unmounted so open threads and reply drafts survive (#13388).
The composer's plan fold now carries the /goal text when there is no plan,
and the three pane views get Ctrl/Cmd 1-3 shortcuts and palette entries.
Prerequisite for removing the top plan peek and view strip (#13369).
#13366 bounded the failed tool results a fold keeps, with component tests. This
adds the effect test through the real Build: 60 turns of failing commands fold
seven times, and the request right after a fold must not carry more failed
results than the retention budget plus the recent tail allow (4 with the bound,
9 without it, where the 50% ceiling is the only thing that clears them).
Refs #13148
runSegments read only the final metrics file. When a child was killed or
timed out it folded the missing record in as an accounted zero, deleted the
<metrics>.partial snapshot the sink had been writing, and left Unaccounted
unset, so every published total omitted what the run spent.
Read the sidecar when the final record is absent, keep the file it came from,
set Partial, and mark the run Unaccounted when neither exists.
renderMeterAccounting no longer prices a run whose harness reported no tokens
against the meter, which invented a -100% divergence.
LoadCachedSchemaForSpec rewrote a legacy-keyed entry in memory, discarded the
SaveCachedSchema error, and returned the hit with the new CacheKey. With a
read-only or full cache dir the file still held the legacy key, so boot was
handed a key that a later read cannot reproduce and pinned the session's MCP
tool identity to a key with no on-disk counterpart
(internal/assembly/boot/mcp_registration.go:76).
Keep the hit but claim the upgraded key only once the rewrite reached disk; on
failure the returned object keeps the legacy key the file holds, which matches
the package's best-effort, silent-degradation policy. A successful upgrade is
unchanged, and no caller can now observe a key the file disagrees with.
checkpointImages reported "no such image" for a checkpoint it could not
inspect, so refuseDegradedCheckpointRewrite read a stat failure, a read
failure, a non-regular file, or a file past the replay ceiling as "nothing to
protect" and let a transcript missing an image replace the only copy of that
image — the exact case where the guard cannot know.
Return the inspection error from checkpointImages, and have the guard fail
closed under ErrSessionImagesUnavailable for any checkpoint that exists but
cannot be read whole. A genuinely missing checkpoint still proceeds: there is
no second copy to protect. The restore path stays best-effort and now logs why
it could not inspect the checkpoint.
recordDispatcher.run discarded Writer.Append's error, so Flush — the only
success/failure signal the stats pipeline has — returned nil even when every
queued append failed. The dispatcher now keeps the first failure since the last
flush sentinel and hands it to the waiter through that sentinel, so a caller
that gates a deterministic read on Flush cannot mistake a failed write for a
durable one.
The failure is consumed by the flush that reports it, so a later flush over a
recovered directory returns nil, while a still-broken one keeps reporting. A
full queue may still drop a record; that stays documented and unchanged.
Tests: the audit red test plus controls for a healthy flush persisting records
and for failure-then-convergence.
A fold keeps failed tool results since the latest digest. The projection places
them right after that digest, so the next fold read last fold's survivors as
"since the latest digest" and kept them again: retention grew by every round's
failures and only stopped when the candidate crossed the 50% checkpoint ceiling.
Failed results now have their own retention budget (5% of the window, like kept
user turns), newest first; the rest fold into the digest, whose coverage guard
still names them. [[keep]] messages stay outside the budget.
Refs #13148
The covered-prefix hash includes the system message, so editing the system
prompt between two visits retires a saved compaction. #13336 added the notice
at the agent and control layers; this pins it at boot.Build, and pins that an
unchanged prompt stays quiet.
Refs #13295
Problem: concurrent tasks need separate code directories rooted at a chosen
branch, and composer configuration menus use inconsistent visual scales.
Root cause: isolated workspaces only started from HEAD and their entry was
outside the new-conversation composer. Menu surfaces had independent styles.
Fix: stage worktree and exact base-ref selection before the first submit,
carry draft attachments through confined roots, preserve session settings,
reject stale preparation and roll back unpublished worktrees on failure.
Keep existing conversations and older servers on the branch control.
Share composer menu surfaces, moderate 15px labels, 13px descriptions and
neutral selection states across mode, workspace, permissions, model and effort.
Move workspace controls below the input card beside runtime statistics, retain
branch labels in narrow panes, and wrap model/effort at the smallest widths.
Verification: 4831 frontend tests; TypeScript and production build; real Git
worktree/source/refusal and attachment confinement tests; serve, control,
builtin and boot suites; go vet, make lint and make check. Browser interaction
and light/dark responsive captures are kept outside the repository.
Fixes#13264
A file restore the checkpoint covers only partly stops for one more
answer, taken with Enter or y (the panel's own hint says so); the key
table in both guides listed neither the step nor y. The new test pins
that step: it does not commit before the answer, Esc steps back without
restoring, and Enter or y commits.
agentpreset.Normalize maps "light" and "economy" to balanced, and All()
lists balanced and delivery; light's own policy no longer exists. The CLI
still put light forward as a choice, in seven hand-written strings: the
--preset help of run, tui and serve, the --profile deprecation warning of
each, and parseRuntimeProfile's refusal ("want light, balanced, or
delivery"). The guides went further and described a Light policy (direct-
first planning, targeted verification) that nothing enforces, so a user
who picked it got balanced without being told.
The seven strings now come from agentpreset.All() through livePresets in
preset_flags.go, and the docs (GUIDE, GUIDE.zh-CN, CLI.zh-CN, TOOL_CONTRACT)
offer balanced and delivery, drop the Light policy bullet, and say the
retired names select balanced. light and economy still parse.
TestEffectThinkingTimeIsKeptWithTheTurn failed in the race job on studio
(run 38635511666): "thought kept 46 ms, framed 46 ms; want both at least
50". The scripted provider wrote the reasoning chunk into a buffered
channel and began its 60 ms pause at once, while the host times the
thought from when it takes that chunk; a host that reads the first chunk
late measures less than the pause. Returning the stream 30 ms late
reproduces it (29 ms measured).
The channel is now unbuffered, so the pause starts only after the host has
taken the reasoning chunk and the measured thought cannot be shorter than
it.
acquireLocal waited on the in-process token and ctx.Done in one select.
When both were ready (a lock nobody held, and a deadline already spent)
select picked one at random, so half the time Acquire refused a free lock
with the context error. The file-lock half of acquire already tries once
before it looks at ctx; the in-process half now takes an available token
first too. A held lock still fails at once once the deadline has passed.
This is the failure behind the unsealed evidence bundles on Windows CI.
TestPlanCriterionIsVerifiedByItsStepsCommands (studio run 38664267053)
reported "bundle not sealed: failure trusted_state.unwritable, contract
failure """ after 3.09 s: sealShadowBundle's two-second sealWait covers
the contract seal and object writes as well as the lock wait, so slow
writes spent it, and the bundle's own Append then met a free stream lock
with an expired ctx. With the first write slowed by 2.2 s, 1 of 6 turns
ended unsealed with that exact signature before this change, 0 of 6 after.
Keeping the decoded prefix across ANY decode error dropped every record after
a mid-file break, and that truncated prefix then competed as a checkpoint that
extends the replay. Damage before the last record now refuses the checkpoint.
recallTail had three outcomes and only two reached the audit channel: a
carried memory note and a normal recall each recorded a content-free
decision, while the retrieval ablation switch returned silently and left
lastRecall pointing at an earlier turn. A trajectory could not tell
"retrieval switched off" from "recall found nothing" from "suppressed by a
carried note".
All three now record a RecallResult and emit one memory-recall audit. The
identity travels as a typed memory.SuppressionCode on its own field, from
RecallResult through event.MemoryRecallAudit into the trajectory record's
suppression_code. The prose sentence stays exactly as it was, for readers
only: consumers match the typed field and never split the code out of the
text, so wording cannot become load-bearing again. The new field is additive
and omitempty, so existing records and batches decode unchanged.
The ablated turn also records lastRecall, matching the note path: the
diagnostic surface is defined as the last real turn's decision, and that
turn did decide not to retrieve.
reportBoundaries folded every row into byKey, so a run that never produced a
transcript re-entered the cue-boundary table as a zero measurement after the
funnel, dose and reformulation exits had excluded it. Skip failed rows, count
them, and say scored=0 with the failed count when nothing is left to compare.
A run that failed before its first model turn was appended as a zero-valued
contextMetrics, so results.jsonl gained an empty row, summarize counted it in
Scored and its stage tables, and batch.json reported it as completed with an
empty task id. Stamp such a run with its task, arm and NoEvidence stage, mark it
failed, and keep it out of every statistic while counting it in the manifest as
failed_runs.
load() dropped a turn file that existed but would not read, so a resumed
session numbered the next turn below it and Begin overwrote the only copy
of that snapshot. The store now reserves those turn numbers in memory:
NextTurn() counts them and UnreadableTurns() names them, while Bounds()
and List() still expose only readable checkpoints. No new on-disk state.
A persisted turn-N.json that cannot be read is dropped by load() exactly
like an absent one, so NextTurn() restarts below it and the next Begin
overwrites the only copy. This test operates on a real truncated file
written by the store's own persist path.
checkpointExtendingReplay read the .jsonl checkpoint with the strict loader
and folded a decode error into "this checkpoint does not extend the log".
The in-place append in appendSessionDisplayReadModel is exactly the write a
crash leaves half done, so the torn checkpoint's own intact lines were
discarded with it: resolveReplayedMessages then returned the shortened
replay with err == nil, and the next save rewrote both the checkpoint and
the log from that shorter transcript. Every turn the checkpoint still held
was lost permanently.
Decode tolerantly the way transformAndCopyJsonl already does, and keep the
existing prefix checks, so the tolerant result is adopted only when it
still extends the replayed prefix. A checkpoint that cannot yield a usable
prefix (unreadable, shorter, contradicting) keeps today's "does not extend"
behaviour; there is no third state.
Two gaps in the delivered-fact re-check this branch added:
- applyWrite kept the pre-reload stamps of the facts the model already
holds, so a change that one reload had settled was re-detected by every
later recall: the snapshot reloaded and the whole store was re-tokenized
on each turn until a recall returned hits. Re-stamp the kept facts
against the snapshot just loaded, dropping what it found gone, so the
steady state stays one read of each delivered fact's own file. The
pre-branch code cleared the set on reload, which is why this is not a
new promise.
- The remember tool's typed Memory was discarded at the queue: the note
carries prose and the controller must not parse it back into memory, so
a fact the model wrote itself was absent from the delivered set and an
external edit before the next recall served the stale session snapshot.
memory.Queue now carries the typed facts the tool wrote, remember passes
result.Memory, and the controller records it as delivered. The model's
own write still queues no turn-tail note.
Adds the delivery/change matrix (100 cells over delivery x change time x
change shape) with the reload-count companion, and the provider-boundary
regression for the remember path.
Problem: a fact saved through Controller.SaveMemory and then edited outside the
session is still recalled from the stale snapshot on the next turn; the per-turn
re-check that catches external edits compared each recalled fact with a stat, so
a rewrite that preserved the file's byte length and modification time stayed
invisible and the model kept reading the stale body; and the identity read behind
that stat, Store.Read, resolves a name by listing the whole store directory and
parsing every fact in it.
Root cause: the re-check set held only the previous recall's hits. A save queues
a turn-tail note, and a note suppresses recall for that turn, so a just-saved
fact was never a hit and so was never re-checked. Its check also rested on size
plus modification time, which is not semantic equality -- the repository's
context-projection rule says freshness detection must not rest on mtime.
Fix: baseline the facts the previous turn handed the model -- the previous
recall's hits, plus the facts a write note carried -- with a digest of each
fact's own file, read once at the canonical path its scope and slug name. Before
a recall tail is built those files are re-read directly and their digests
compared: different bytes reload the snapshot, identical bytes never do. The
stamp carries no size and no modification time, so no stat result can decide
that a fact is unchanged; the bytes are the semantic version because they are
exactly what the renderer turns into the model-visible fact. Store.Read stays as
the fallback for a fact whose file is not where its name says.
Cost: one read of each delivered fact's own file per recall, plus one when it is
baselined and, on that baseline, one parse of the same file to confirm the stamp
describes the delivered fact; a snapshot reload only when a delivered fact's
bytes differ or its file is gone. A formatting-only external rewrite re-reads the
fact into the snapshot even though the parsed fact is equal -- the safe
direction. A fact edited into relevance that the previous turn never handed over
stays invisible until the session reloads; no full scan, which
docs/SESSION_MEMORY_RETRIEVAL.md now states, along with the no-hit retention and
the identity fallback.
This extends the merged fix in 99fe3dbbf, which covers the recall-hit path only.
Review round 1 fixed two regressions in this baseline:
- A fact edited between the snapshot load and its first recall was tracked with
the edited file's own digest, so the stamp described bytes the turn never
carried and SameContent() then called the stale snapshot body unchanged
forever. stampFact now marks a fact tracked only when the one file it just
digested re-parses to the very fact being delivered (memory.FactAt + sameFact,
the comparison the untracked fallback already used); otherwise it records the
fact untracked and the identity read decides. The probe stays on that one file,
so no listing and no second scan come back.
- recordDelivered overwrote the delivered set on every recall, so a turn that hit
nothing erased the facts the previous turn was still holding and the next
recall had nothing left to re-check. It now keeps the previous set when this
recall returned no hits, while still appending the facts a write note carried.
Verification: a same-size/preserved-mtime rewrite is asserted first at the
Compose boundary (red on the stat-based check) and then at the true provider
boundary, through the real Build stack with a recording provider that shows the
refreshed body in the request the executor actually sends. The tracked path's
freedom from directory listing is asserted by arming the memory package's
read-dir seam to fail while stamping and while re-checking; FactAt is pinned to
the same single file. Both new tests go red with the stat-based conclusion
restored behind the same API and green with it removed.
TestRecallReloadsAFactEditedBeforeItsFirstRecall (seed, load, rewrite, Compose
twice) and TestRecallKeepsCheckingAfterATurnWithNoHits (recall A, an unrelated
no-hit turn, edit A, ask again) are red on the pre-fix behaviour and green now.
Each review fix was reverted alone with both tests kept: the first test goes red
and the second passes, and the reverse for the other. Go tests, vet, gofmt,
golangci-lint (0 issues) and make check (repolint: clean).
Refs #12948
An explicit expiry is a hard boundary: the fact index and automatic recall drop
the fact past it, and the memory tool paths report it as expired. Pinned guidance
did not check it, so an expired body kept riding the stable prefix, an expired
project fact kept shadowing the live global guidance it used to override, and an
expired body kept consuming the pinned budget that bounds what the prefix sends.
Both places now skip facts that are past their expiry, so the prefix, the shadow
rule and the budget agree with the index and recall. This covers facts that are
already expired when the session loads them; withdrawing a pinned body that
crosses its expiry mid-session is a separate prefix question.
An unqualified save whose name was active in two scopes was refused with a bare
sentence. A code caller could not tell that refusal from a write failure, and
the model received neither the two ids the name could have meant nor a way to
say which fact it wanted.
The refusal now wraps ErrNameActiveInTwoScopes, so errors.Is separates a
cross-scope name collision from a write failure, and its sentence renders that
code, both active ids with their scopes, and the two ways out: update the fact
you meant by its stable id, or save with a scope-qualified name. The remember
tool returns this error verbatim, and the agent renders it as the model-facing
tool result, so the identity and both ids reach the model without a second,
drifting sentence.
Tests cover the identity and both ids at the store and at the tool, the refusal
leaving both facts at their own bytes and revision, and the two unambiguous
saves (stable id, scope-qualified name) still succeeding. A neighbouring test
pins that a name active in exactly one scope is still updated, not refused.
An unqualified name that was active in another scope was archived after the save
was published, so a failure there left the same name active twice, and the
archiving itself removed a fact the caller never named.
The collision is now reported before anything is written, and the post-commit
archiving is gone.
git diff --numstat -z writes a rename or copy as the counts, a trailing
tab, then the old and new paths as their own NUL fields. Trimming that
record removed the tab, so the parser treated it as a short line and
dropped it. Status and Summary then reported no line counts for the
change. The counts stay on the new path, which is the name status lists.
git diff --numstat -z emits a rename or copy as the counts, a tab, NUL,
the old path, NUL, and the new path. Status and Summary need those counts
on the destination, beside an ordinary edit in the same diff.
The CLI/TUI shortcut tables in docs/GUIDE.md and docs/GUIDE.zh-CN.md still
described four 1.x behaviours the 2.x terminal UI does not have (#12144
lists them as missing):
- Up/Down "navigate queued follow-up feedback" while a turn runs; in 2.x
they recall history there too (recallAtEdge has no running case), and
queued items are managed with /queue.
- Esc "un-sends a just-submitted turn before any reply"; escape() cancels a
running turn, clears input, or leaves ! shell mode, and nothing un-sends.
- Composer text selection by drag with replace-on-type; onMouse ignores a
left click below the transcript viewport, so only transcript text is
selectable, and Ctrl+C copies only that.
- The rewind picker's f (fork); rewindActions has b c d s u.
Two tests pin the rows as now written: Up recalls while a turn runs, and
Esc on an empty composer leaves shell mode.
TestAuthorizeHTTPMCPDoesNotHoldStateLockDuringBrowser failed on Windows
CI (studio run 38416026313) with "removal during browser flow blocked on
OAuth state lock" after 1.09 s. The test runs ReconcileHTTPMCPOAuthAfterRemoval
from inside openURL and gave it one second. AuthorizeHTTPMCP holds no lock
there (captureMCPOAuthGeneration releases its lock before the browser
opens), so a slow removal on a loaded runner read as a held lock. Starting
the removal 1.2 s late reproduces the message.
The wait is now testenv.Budget(t). A lock really held across the browser
is still caught: the removal's own lock wait gives up after five seconds
and returns an error, which fails the test.
TestSaveSnapshotSucceedsWhenCrossProcessFileLockReleasesBeforeDeadline
failed on Windows CI (studio run 38502171680) with "lock session file:
session file lock held" after 1.28 s. A goroutine releases the competing
lock after 30 ms and the save waits at most 500 ms for it; on a loaded
runner the release came later than that. Releasing after 600 ms
reproduces the message.
The save returns as soon as the lock is free, so the wait now is
testenv.Budget(t). TestSaveSnapshotBoundsCrossProcessFileLockWait keeps
its 40 ms, since it asserts that a lock never released makes the save
fail.
TestHostContextCarriesTheExtraWritableFoldersToLocalServersOnly and
TestHostContextCapsHowManyWritableFoldersAreTold fail on every Windows run
since e6f70622b (studio run 38537039922): their roots are written as
"/data/shared" and "/data/000", which have no drive and so are not
absolute on Windows, and metaWritableRoots drops every root that is not.
The key is never sent and both tests see nil.
The roots are now built from the temp directory's volume, the repo's
usual way to spell an absolute path on any OS. The same change makes
TestHostContextSaysNothingOfWritableFoldersWhenThereAreNone exercise the
workspace exclusion on Windows: its "/w" was dropped as relative there,
so it passed whether or not the workspace was excluded.
Add a separately selectable naming model and adjustable reasoning preferences
for chat roles, with typed roles/efforts responses and user-only role settings.
Keep title generation fixed off with temperature zero and a 60-token budget;
reject explicit title effort instead of storing an unused setting. Use the
configured startup chat model when no naming model is selected, including
servers without a running controller.
Integrate the on-demand naming and rename paths from #11591 with grouped title
provider state. Preserve default-model persistence, per-profile model overrides,
busy-save notices, and inherited effort validation against the executing model.
Share the model-preference row's existing layout declarations to keep the CSS
line count below the studio baseline without changing rendered layout.
Preserve each provider's saved effort when a role has no stored preference;
use the model default only for explicitly selected auto. Add a real boot
assembly effect test for planner and guardian-derived reviewer/evaluator
providers, while leaving subagent effort inheritance unchanged.
The title-bar Browser button and the Workbench tab opened the same column but
the button also asked for a blank browser page. The tab is now the only entry.
The buttons sit left of the rail, and the shut dock's tab sits flush with the
window edge instead of over the rail's ticks. The browser guard asserts no two
of the buttons, rail, tab and composer intersect at four widths.
Windows answers "not found" for a file where a directory should be, or
beneath one, so the not-exist shortcut hid the very case the directory read
error exists for. The stat of the entry and its parent tells them apart.
Problem: Studio user-message and composer images only showed thumbnails,
while tool screenshots used a separate enlargement overlay.
Root cause: The three entrances did not share a preview interaction owner.
Fix: Use a common image trigger and body-portaled viewer with original
sources, keyboard access, focus restoration, safe double clicking, explicit
closing and unavailable-image handling. Remove the tool-only overlay and
document the restored attachment parity with main-v2.
Verification: 541 frontend test files and 4611 tests pass; typecheck,
production build, census verification, make lint, make check and go vet
pass. Builtin and boot Go tests pass. Chromium checks cover light/dark,
wide/narrow windows and 150% interface zoom. The browser guard is wired
into Studio CI. Correct the perf fixture to await committed language state.
The wire log records whether a surface frame was dropped; the surfaces route
answers 200 with an incomplete mark only then, and an unreadable history has a
code of its own.
Problem: permission and storage failures can hide existing memory revisions,
make restores report them as missing, and compute recovery revisions from an
incomplete history.
Root cause: directory discovery conflated every ReadDir error with absence.
Fix: unify all eleven store walks behind a typed reader that treats only
missing directories as empty. Propagate errors through reads, save guards,
recovery, recall, controller commands, and HTTP responses. Complete recovery
and legacy duplicate discovery before writing. Preserve persisted formats.
Verification: failing-first permission fixture; eleven-site permission,
I/O, and non-directory regressions; unchanged files on failed recovery or
save; missing-directory compatibility; tool/controller/HTTP and bilingual
error coverage; Go tests, vet, lint, repository checks, frontend tests and
typecheck, and Windows memory test cross-compilation.
Fixes#12950
Problem: the pending icon restoration was missing from the next-release
notes, so users would not be told why the application artwork changed.
Root cause: the original restoration commit only included icon assets.
Fix: add a bilingual release-note fragment covering the application,
taskbar/Dock, launcher and tray icons without changing any artwork.
Verification: the 16 release-note rendering/publication tests, repository
lint, Go vet, changed-file checks and diff whitespace checks pass.
Problem: Studio 2.39.0 replaced the expected black-and-gold application,
taskbar, Dock, launcher, and tray artwork with blue-and-yellow icons.
Root cause: the shared SVG and all generated platform resources were
recoloured together in #12897.
Fix: restore the black-and-gold SVG and its ten generated icon files,
preserving the platform crop, ICO/ICNS sizes, and Windows tray DPI scales.
Verification: regenerate all assets and compare their bytes with the
pre-#12897 artwork; decode and visually inspect all 26 raster sizes;
run the Electron unit suite, Go vet, repository lint, and changed-file
checks, plus the builtin and boot suites. No application or packaging
logic changes.
The guard only saw byte-periodic blocks. A second structural reading of the same
tail - many short lines from very few distinct ones - catches a degraded stream
that never repeats a block.
The program find runs decides: a bare read-only name whose own arguments do not
write. Anything else, -ok, -delete and the -fprint family stay writers, so a
read-only find no longer takes the workspace write lease.
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.
A shadowFloat token with a strict px/hex shadow grammar maps onto
--shadow-float, and onto --shadow-pack for the light menus that kept their
own default. Unset, nothing paints differently.
Two tests for the path #12966 settled: removing every provider saves an
empty list that loads back empty, and adding one afterwards replaces it.
Over HTTP, removing the last provider moves the runtime to the stand-in
and lists no provider.
Both fail without the providers = [] render line from #12966.
Refs #12915
Declares import_web_image's snip stance (a small JSON receipt takes the default) and regenerates
docs/TOOL_CONTRACT.md, the two contract tests that landed on studio after the branch point.
move_file now asks for read access to its source on both paths (a forbidden-read source is refused
before the rename, not only when the copy fallback runs), and four agent tests built after the
studio branch point supply the permissive target-access callback the agent now requires.
Keeps the row-level double-click rename from studio (and drops the title-level handler it replaced),
declares the dialog's title input as session.rename.title, and expects the inline mode in the
double-click test now that onRename carries it.
Keeps the new summary text, names the advanced-appearance disclosure in the settings catalogue (the catalogue test requires a titled block to name itself) and folds three selector lists onto one line each so app.css stays inside its recorded size.
Folds the studio's retired-surface rows into the identity merge of a rebuild (rebuild.ts), and lets two
fixtures report the turn as running while it runs, since a status read now vouches for it.
The same HttpError check and the same note block were copied into Compaction, Rules, Shell, Context,
Settings and EditConn. savedNotApplied lives next to SAVED_NOT_APPLIED and SaveNote draws the note once.
Mantine's gray primary filled colour is #868e96, which is 3.4:1 against the button's white label.
The scoped provider now maps it to the app's text token.
In a narrow settings column the extra control pushed the scope and switch of the longest rows past the card edge. Next to the name it keeps the other columns where they were.
Keeps one /answer handler that serves both question engagement and surface answers, and
carries the computer driver and native-surface options through the workspace switch.
repolint's process-input rule flags a kernel package that reads SystemRoot. GetSystemDirectory
is also the stronger answer: a changed environment cannot stand in for System32.
List answered "no distributions" for any wsl.exe failure, so a probe that hit its
deadline or was cancelled read as a machine without WSL. The caller's ended context
is now returned as such.
Persist undo retirement before admitting new turns or tool writes, and refuse new activity when that persistence fails. Keep post-tool checkpoint-recording warnings separate from truthful tool results. Preserve the preceding undo after fully compensated rewind failures, and block activity until incomplete recovery settles. Reuse the existing Studio notice, availability route and change event; retain the failure-path tests.
A conversation whose log still lives in the 1.x store is read here and never
written, so it shares the sidebar with this build's own rows and cannot be told
apart. The tree now reports which store each row came from (the kernel decides
it through IsForeignSessionLog) and the row shows a small "Old" mark at its end,
which yields to the hover actions.
The phone-access panel renders data-action="share.remember" and reads
hub.shareRemember(), but neither reached the action registry nor the test
hubs that build one by hand: actions.test.ts failed with "rendered but not
in src/actions.ts" (PhoneAccess.tsx:194), and PhonePop.interaction /
PhoneAccess.port failed with "hub.shareRemember is not a function".
Register the action beside the other share.* entries and give both test
hubs the hook the panel now reads.
A phone pairs by scanning a QR code, and the credential it keeps rides a
cookie scoped to the origin it scanned. The host held that pairing in memory
only: closing the share or restarting the process threw every device away, so
the next launch asked for a fresh scan.
[serve] remember_paired_devices, false by default and the user's alone (a
project reasonix.toml that sets it is ignored and reported), keeps three things
when it is on: the phones that paired (digests only, written to
<state>/device-trust.json and adopted at startup), the address the share last
listened on, and whether the share was open at all. The persist hooks read the
key when they run, so the panel switch takes effect without a restart, and
turning it off forgets on the spot: everything paired is revoked and both files
are removed. A snapshot taken before a later change is dropped by revision, so
a concurrent pairing write can never overwrite a revoke. Address ordering keeps
its upstream preference. A phone that paired never expires: the point of the
switch is that one's own LAN phone connects again next time, and that is
intentional.
Closing the share still unpairs everything; that is the person withdrawing it.
Only the process going away keeps the phones. The state directory is the
kernel's, outside every workspace, and agent-run commands have no path to it:
sandbox writes are confined to the workspace and ~/.reasonix, and the trust
file never enters them. A restart reopens the listener only when the switch
was on and the share was open when the process left — the state of things the
person chose, not a new one.
Fixes#11834
Refs #11878
Problem: frontend CI intermittently asserted that Files was hidden before a
fresh browser-tab follower had committed its selection.
Root cause: retiring the preview's request record does not settle the separate
Promise.allSettled callback that updates the visible workbench selection.
Fix: wait for the asserted workbench DOM state, as the sibling preview test
already does, while preserving the selected-tab and newer-intent guarantees.
Verification: all 49 HTML preview and neighbouring browser race/rejection tests,
frontend typecheck, and repository lint/check pass; prescribed Go preflight
checks run before commit. CI is monitored on the pushed head.
TestE2ESessionLoad failed on Windows CI on studio (run 38179453323) with
"session/prompt: timed out" after 7.64 s: drainPrompt gave up when five
seconds passed without a notification or the prompt's response. A full
prompt crosses the ACP server, controller, agent and transcript
persistence, and a loaded Windows runner can stay quiet that long without
anything being stuck. Pausing the scripted tool for 5.5 s reproduces it.
drainPrompt now waits rpcCallBudget(t), the testenv.Budget wait the same
file's other RPC helpers already use.
Problem: The latest activity-header note conflicts with this PR's sidebar
entry at the end of the unreleased notes.
Root cause: Both branches append their independent entries at one anchor.
Fix: Preserve all upstream notes and place the sidebar entries alongside the
session-list entries. Retain the narrow-only drawer control and the sticky
activity header in the auto-merged stylesheet.
Verification: Sidebar/divider, responsive layout and activity-fold tests;
the Chromium sticky-header guard in light and dark; production build,
repolint, release-note/reference tests, typecheck and UI census.
TestOnlyChangedPathsAreAcceptedAndTheyAreLiteral fails on every Windows
run since 40e097879 (studio run 38383059323): it writes a file named
"*.txt", and a Windows file name cannot hold "*" ("The filename,
directory name, or volume label syntax is incorrect.").
The name now is "[ot][nw][eo].txt". As a git pathspec glob it matches
one.txt and two.txt, as "*.txt" did, and every filesystem accepts it, so
the test still proves Stage passes each path as :(literal) on all three
platforms.
Problem: The latest studio session-list release notes conflict with the
sidebar-control entry in this PR.
Root cause: Both changes prepend their notes at the same unreleased anchor.
Fix: Preserve the upstream session-list and theme notes, append the sidebar
entries in the existing flat-list format, and retain narrow-only drawer
controls alongside the upstream spacing changes.
Verification: Sidebar/divider, session-list, remote-title and spacing tests;
production build, typecheck, repolint, UI census and fixture verification,
and release-note/reference tests.
A turn that already carries a memory note skips automatic recall, because the
note says the same thing better. That decision was recorded for the session's own
diagnostics but never reached the audit channel, so a trajectory could not say
that a real recall was suppressed and why.
The suppression now goes through the same content-free audit as a recall that
returned hits. A turn whose retrieval is disabled by ablation is unchanged.
The plain save path, which covers a new fact and an update that keeps its name,
wrote the fact and then published the index. A failed publication returned the
error with the fact already written, so a retry of the same intent either
advanced the revision again or was refused by the revision guard the caller had
just satisfied.
The write is now withdrawn when the publication fails: a new fact is removed, an
update is rewritten from the bytes and mode that were on disk — not re-rendered
from the parsed fact, which would silently drop frontmatter this code does not
know, comments and hand-written formatting. If the withdrawal itself fails the
error carries the state it left.
Problem: the brand patch conflicted with Studio and failed the sidebar and
type-scale contracts; the empty-session symbol replayed an entrance on mount.
Root cause: the presentation change retained old fixture expectations, typed
subtitle sizes directly, and treated a mounted brand as a new event.
Fix: integrate the current Studio base, retain its sidebar controls, update the
wordmark assertion, reuse shared size tokens, keep the hero symbol still, and
document the final brand contract without stylesheet commentary.
Verification: frontend typecheck/build/census, affected tests, Chromium guards,
go vet, make lint/check, and builtin/boot tests passed. Full frontend validation
is recorded in the PR; CI is evaluated on this merge's head before delivery.
Problem: Studio packages need payload and installed-shell acceptance, and a
complete artifact gate before publication. Running the Electron and matrix
contracts on Windows also exposed host-dependent test fixtures.
Root cause: #12914 supplies native Linux ARM64 packaging but checks only Go
payload architectures and selected artifact names. The macOS bundle fixture
used the host OS's temporary path, and the matrix fixture relied on PATH
lookup for a mock git command instead of binding it in the tested shell.
Fix: Keep upstream native runners and system FPM, consolidate ELF checks,
verify both Linux package formats, exercise the installed shell, and require
all twelve nonempty release artifacts. Guard installation acceptance with
GITHUB_ACTIONS=true. Use platform-specific workspace fixtures and a shell
function for the matrix's git fixture, keeping all Windows test coverage.
Rebase on current studio and preserve adjacent release notes.
Verification: Electron 143 tests and Linux/script 39 tests pass, along with
workflow contracts, desktop Go tests, builtin/boot regressions, vet, lint,
repository checks, shell syntax and Actionlint. Previous-head hosted native
Linux package verification and installed acceptance passed for amd64/arm64;
replacement-head Windows CI remains the platform acceptance gate.
Refs #12906
Problem: The sidebar-control change conflicts with the theme release notes
added to studio after the PR was opened.
Root cause: Both branches append their next-release entries at the same
location in the unreleased notes.
Fix: Preserve both sidebar and theme entries, use the accepted release-note
heading, and keep the bilingual sidebar entries within the prose budget.
The auto-merged stylesheet retains narrow-only drawer controls and current
theme foundations.
Verification: Full frontend tests, typecheck, production build, repolint,
UI census and fixture verification, and release-note/reference tests.
Problem: PR #12935 became conflicting after the Studio palette changes landed.
Root cause: Both branches appended bilingual entries to the same unreleased
notes, and both touched the shared Studio stylesheet.
Fix: Merge studio while preserving the session-list and palette release notes.
Keep the upstream theme tokens alongside the compact session-row rules.
Verification: gofmt, go vet, make lint and make check passed. Frontend typecheck,
production build, UI census, all 92 focused session and theme tests, and the
light/dark 34px row geometry guard passed. Builtin, boot and theme Go tests
passed. Hosted CI still requires upstream workflow approval.
Problem: The session sidebar mixes one- and two-line titles and reserves too
much vertical space. The persistent local-selection entry adds another blank
row between the filters and the machine tree.
Root cause: Studio clamps titles to two lines with a 42px minimum row height
and vertical margins; SessionBatch renders its entry even outside selection.
Fix: Use 34px single-line rows with full-title hover hints, move local batch
selection into the machine menu, and restore focus to that menu on exit.
Keep batch archive/delete and neighbouring row actions intact. Add browser
geometry coverage to Studio CI, interaction regressions, parity/release notes,
and before/after screenshots for light/dark wide and narrow windows.
Verification: Go formatting, vet, lint, builtin/boot tests, frontend typecheck
and build, UI census, browser geometry and affected interactions passed.
The frontend full run passed 4156 of 4157 tests; the unchanged HTML-preview
failure passed all 26 tests on isolated replay. The optional translation scan
reports the same two missing test-only keys on the base and this branch.
Three control tests failed on Windows CI at a fixed one-second wait for
something the controller does asynchronously:
- TestInboxDispatchKickDuringEmptyScanIsNotLost (run 38178550657) and
TestRepeatedJobCompletionIsIdempotent (run 38283959118): "timed out
waiting for inbox dispatch" from waitForInboxDispatch.
- TestEditPromptPersistsOriginalPrompt (run 38367692250): "edited prompt
was not persisted before deadline" after its one-second poll.
Delaying the scripted runner or the provider by 1.2 s reproduces all
three messages. These waits only catch a hang, which is what
testenv.Budget is for; every one-second wait in inbox_dispatch_test.go
and the edit-persist poll now uses it. The 150 ms waits that assert
nothing happened are unchanged.
Problem: Studio offers no plain built-in alternatives matching the white/blue
and warm/tea-green directions requested in #12909.
Root cause: the default swatch inherits active pack colours, plain packs lose
the default reading surface and weight, and legacy Studio rules override
semantic accent colours on selected controls and dark send buttons.
Fix: ship two light/dark token-only packs, share canonical default preview
colours, preserve plain reading styles, and close the affected controls over
theme tokens. Add optional accentInk, localized labels and backdrop grouping.
Verification: 209 frontend tests, production build/typecheck, theme and HTTP
server tests, go vet ./..., make lint, make check, builtin and boot tests.
Browser checks cover system-following, default restoration, and light/dark
workbenches at desktop and phone widths; screenshots are included.
TestSurveyReturnsPartialCountWhenAWalkStalls failed the coverage job on
studio (run 38341178074) with "the partial count must survive: 0 bytes,
0 files". It gave the walk a 50 ms budget and expected it to count the
one file before running out; the walk's callback checks the budget's
context first, so a walk goroutine scheduled late counts nothing. A
100 ms delay before the walk reproduces it, and the same delay fails
TestTruncatedRootKeepsTheErrorFoundBeforeTheBudgetRanOut, which relies on
the same 50 ms.
Both now use stalledWalkBudget, 500 ms: ten times what the failure needed,
while Survey's three stalled roots still return well inside the test's
5 s guard.
TestReaderIntentRefusesDispatchAfterSafetyDrift failed the race job on
studio (run 38334190859): the test replaces rt.schema at
plugin_test.go:1765 while the handshake's detached cache write
(saveHandshakeSchema -> cacheableToolsOf, cache.go:44) can still be
reading it. Nothing in the product writes a remote tool's schema after the
handshake; only this test does.
The test now drains host.bgWrites, which every detached writer joins,
before replacing the schema. With the cache write delayed by 300 ms, the
cached schema was the test's replacement before this change and the
handshake's own after it.
The commit button only records what is staged, and Studio had no way to stage:
an empty index sent people to git add. The changes list now shows each file as
staged, partly staged or not, ticks and unticks them, and stages everything at
once; the kernel accepts only paths git itself lists.
Problem: The desktop sidebar has a redundant top hamburger button alongside
its divider, making the sidebar affordances ambiguous.
Root cause: The drawer entry is mounted in all layouts, although desktop
layouts already expose a divider that toggles and resizes the workspace rail.
Fix: Mount the top entry only under the scene fold, remove its desktop chrome
spacing, and preserve the existing divider and remembered width. Document
the intentional control removal and include light/dark responsive screenshots.
Verification: 488 frontend test files and 4141 tests pass, including divider
interaction and responsive boundary coverage. Typecheck, production build,
repolint, UI census and fixture verification pass. Browser interactions and
an isolated macOS Electron window verify the retained controls.
Refs #12917
Add linked maps, comparisons, dashboards and workspace-backed photo acquisition
and viewing for desktop task surfaces.
Certify Mantine viewer interactions and isolate Windows command-line and direct
DNS test fixtures from host process/proxy behaviour.
Cache impact: shortened write_file schema changes tool/prefix hashes once on
upgrade (5572 to 5524 schema tokens); system prompt stays unchanged. Update the
boot goldens for that deliberate contract change. Image discovery stays hidden.
The release builds linux/arm64 on a native ubuntu-22.04-arm runner and requires
all four Linux packages by name. electron-builder's own fpm is x86_64-only, so
the arm64 leg installs one from RubyGems. The package job checks that the Go
binaries inside the .deb are built for the architecture the package is named for.
The status channel of a child's progress rides ToolProgress, which the
broadcaster treated as droppable. Its terminal phase has no successor, so a
backlogged client lost it and kept showing the child as running.
A click on a Windows toast grants the right to take the foreground to the
process it activates, which is the kernel host; the window to raise is the
shell's. The host now grants the right before forwarding the click, and the
shell lifts a window the platform refused to focus. A toast that could not be
delivered is written to the host log instead of being dropped.
Machine rows become small section headers with counts in words, project rows keep
the strongest line with a numeric pill, scope filters hide zero counts and read at
12px, and the revealed + and ... buttons are outlined and share one hover, press,
expanded and busy state.
The icon keeps the R-and-whale silhouette and drops the gold gradients for flat
colour: blue tile, yellow R, a blue whale with a pale belly, eye and cheek, and a
band of waves. The sidebar brand mark is the same file instead of a drawn glyph.
A reply to a fixed report means the fix did not hold. feedback-sync reopens an
issue closed as completed after mirroring the reply, and Studio offers the
reply box on fixed reports.
Switching views ran a view transition over the whole pane, so the tab bar was
cross-faded with it: the old tab's box faded out while the new one faded in,
read as two separate animations. The highlight is now one element that sits
under the chosen tab; during a switch the tab bar is not cross-faded and the
highlight slides and resizes to the new tab, with the labels above it.
MigrateV2 rewrote a legacy fact as V2 and then published its index line. If that
publication failed the migration reported the error with the fact already
rewritten, and the next run saw a fact that already had an id and a revision and
skipped it, so the index stayed stale with no path back.
A fact that already looks migrated is now reindexed instead of skipped, so the
second run converges on the index the first run could not publish.
The kernel answers POST /browser/close with 204 and no body, and the port read
it as JSON, so every successful close reported "Unexpected end of JSON input".
wait now echoes the jobs' new output, as whole lines and capped at 64 KiB,
through the call's live progress, starting from their latest lines. The
frontend folded a progress frame like a dispatch, so the frame's empty name
blanked the step and each chunk replaced the last; it now appends the chunk
and keeps the call's name.
--dock-w was a registered, inherited custom property tweened on .app, so every
frame of opening or closing the workbench restyled every node in the window.
It now does not inherit and lives on the pane whose grid reads it; the gutter
writes it there while dragging. Style recalculation while switching halves,
and the long frames on a throttled long transcript mostly go away.
With the display currency left automatic, the server adopted the wallet's
currency for its own ledger only: the quotes on usage events and the frontends
were never told, so the status bar showed this turn in US$ beside a CNY wallet.
The controller's Balance now adopts it and announces it as an operator notice,
the same path a chosen currency takes, so every quote and frontend follows;
a chosen currency still wins and nothing is written to the config.
The folder picker also shows hidden folders, where tools keep their state.
Enforce workspace read policy before previews, diagnostics, hooks, and file execution.
Provide admitted previews to permission approval and Auto Guard, then capture fresh preimages after PreToolUse. Share target access checks across execution, planning, review, and delegated agents.
Keep genuinely write-only targets available, report existing-file overwrite refusals with write-specific messages, and avoid hook-write coverage gaps for read-only calls.
Preserve network-path protection before filesystem lookup while allowing authorized network roots and registered folder references.
Include baseline-compatible disclosure regressions, production target-access wiring checks, and localized tool-card tests.
Windows 11 draws a frameless window's border from the frame's scheme, which
Electron takes from nativeTheme. Studio never set it, so a dark theme chosen in
the app on a light system kept a light frame, seen as a light line around the
window. The frame now takes the stored theme (auto still follows the system),
at launch and whenever the preference is saved.
A project row put its session count on a second line, so every project took
two rows. The count now sits where the row's actions are and gives way to them
on hover or keyboard focus, so a row is one line and the name keeps its width;
touch screens show the actions. A tag still takes a second line. The two add
buttons share one size and hover, and the filter labels and counts are larger.
The docked workbench card sat 8px into the pane with a 39px strip, so its
bottom line ran 7px below the conversation's tab row and the two read as
unrelated bars. The strip is now the shared tab-row height less the card's
inset and border, so both lines meet; the card keeps its corners and edge.
Settings, feedback, a full screenshot, a checkpoint preview and a sub-agent
transcript open in veils that start below the title bar, so centring inside
them put each dialog half a bar below the window's middle. Each veil now
carries a transparent bottom border as tall as the bar, which makes the box it
centres in symmetric; the phone layout keeps settings full screen.
[agent] host_verification = "off" in the user file drops the check, review and
sign-off a turn that changed files owes before it may finish. The turn's own
unfinished plan and a capability the user required still hold it, and the
receipt still says what was and was not checked. A project file can neither
turn it off nor undo the user's choice.
Escape stopped a running turn at once, so a stray press ended a long task. The
first Escape now arms the stop and shows a hint for 2.5 seconds; a second one
inside that window stops. Appearance gets a per-machine choice to stop on the
first press again. The stop button is unchanged.
openWorkspace now opens a folder only when it is a directory and, on macOS,
not an application bundle; anything else is selected in its parent as before.
With the icon rail off or collapsed the whole sidebar head was indented 44px
further so the toggle had room beside the wordmark, which left a blank strip
beside the new-session, search and quick-entry rows. Only the wordmark row
makes room now; every other row sits at the same inset in all three modes.
The toggle was declared before the nav column, the sidebar head and the
chrome, all of which are window-drag regions in the desktop shell. Where drag
regions overlap, the one declared later wins, so a click on the toggle moved
the window instead. It is now the last child of the app; its z-index drops one
step so the settings sheet still covers it.
A selection reaching into blocks that are not mounted mounted every block in
between in one synchronous render, so a drag across a long conversation froze
the window (22s at 1200 turns). A block the selection only passes through, with
neither end inside it, is now held as the conversation's own text at its
placeholder height; blocks with an end inside stay whole.
A tool whose prefixed name exceeded 128 characters made the provider refuse
every request in the session. toolName now shortens only the tool part,
keeping the server prefix and a hash of the full name so siblings stay
distinct and the name is deterministic for permission and hook rules.
The refresh listed and built its own catalog, so it reached the model with
definitions the pin judgement #12597 added had never seen: a server could wait
until the session was running and announce a tool nobody approved. The judged
path now has one entry point for both listings — read, then publish — and the
refresh goes through it.
readToolCatalog takes no lock and publishes nothing, so the refresh keeps
building its candidate off the lock and swapping it in as one piece, and
publishToolCatalogLocked moves every field together: what was withheld was the
one a caller could forget, and it is the one that makes the judgement visible.
The digest covers the held set, so approving definitions the server had added
is itself a change the next listing publishes.
What the session does with that follows: the registry's held set is replaced
from the same judgement, as at every other registration site, and a pinned slot
whose tool is held says so instead of claiming the server dropped it.
hostNotices moves to sessionRuntime, which is its lifetime: a withdrawn slot
outlives the conversation that saw it. That also keeps agent.go inside its
file-size budget, and the catalog path moving to tool_catalog.go keeps
plugin.go inside its own, with no baseline spent.
Refs #11957
A server that drops a tool it had pinned into the provider-visible array was
rewriting that array mid-session: the cache-stable prefix moved and every token
behind it was spent again. The entry now stays until the session ends, the call
it still invites fails with the live catalog's not-found, and the model is told
on the turn tail — the one place an append leaves the prefix byte-stable.
Registry.ReplacePrefix swaps a server's namespace under one lock. The removal
and the re-registration were two calls, so a turn composing its request between
them was built from a server that appeared to have no tools.
A server that declared tools.listChanged may add, rename or drop tools after
startup and says so with notifications/tools/list_changed. All three transports
dispatched notifications/progress and dropped every other notification, so the
catalog stayed as first listed: a tool the server added was unreachable, and one
it removed was still offered to the model.
The transports now surface a notification by method, the client re-lists once a
burst settles, and the Host publishes a catalog that actually changed. The
session's registry re-registers it, so what the model can reach follows without
moving the cache-stable prefix.
Registrations live on the reconnecting transport and are rebound onto a
replacement connection, and a backend rolled in behind the stable proxy is
watched like any other, so a connection that dies does not leave the server
unheard for the rest of the session.
The unrecognised-write effect test asserts on what the turn's commands
changed, but left the sandbox at its default. A runner that has no bwrap
refuses to run them at all, so the case that keeps the waiver owed the check
for a reason the test is not about: green on the `test` jobs, which install
bubblewrap, and red on `coverage`, which does not.
Every other effect test that needs a shell writes `[sandbox] bash = "off"`;
this one now does too. Reproduced with a PATH that hides bwrap, which fails
with the CI's exact line and passes with the fix.
A turn that wrote code and then ran any command the classifier could not read
as a check was released as verified. The waiver was keyed on one fact — "not a
check" — while the receipt carried a second one it never read: whether the host
watched the command write. `echo … > generated.txt` after a code write is
graded MutationProven from the redirect itself, and the turn still finished
with the change unverified.
The waiver now also requires the grade the host already recorded, and requires
it of every unrecognised command after the write rather than the most recent
one: a quiet `ls` beside a writing command says nothing about that command. A
turn whose unreadable commands all left the tree as the host found it keeps the
waiver — that miss is the classifier's blind spot, not the turn's. A write the
host watched, and a change whose extent it could not establish, leave the check
owed: an incomplete observation never establishes scope, and unproven_mutation
is settled by observation rather than by a check that ran afterwards.
Ordinary turns only: Delivery gates verification through a corroborated
citation and never had this waiver.
Refs #12567
Two tests that read a turn's sealed bundle have failed on Windows CI with
nothing that says why the bundle was missing:
- TestContractCriterionIsSatisfiedByItsOwnCheck (runs 38185765331 and
38199681271): "trusted state tampered: malformed digest """, because the
test read the empty Record of an unsealed audit as a digest.
- TestEffectSupportingWritesWithoutChecksMayFinish/markdown_only (run
38252045191): "missing sealed evidence".
The audit already carries the store's typed cause. sealedRecord fails with
FailureCode and ContractFailure when the bundle was not sealed, and every
agent test that reads a sealed record goes through it; the boot test prints
the audits it found.
TestSessionViewedToleratesASidecarCaughtMidReplace failed in the coverage
job (run 38264909143) with "a view that met a torn sidecar failed instead
of waiting it out" in 0.20 s. The view retries a torn sidecar for 20, 50
and 100 ms; the test heals it from a goroutine after 30 ms, so the heal had
140 ms of slack, and on a loaded runner the goroutine started later than
that. Delaying the heal to 250 ms reproduces the failure locally.
The test now appends a 5 s last retry to the product's schedule, the way
the meta-ledger tests already replace it, so a late heal is still waited
out. A reader that does not retry at all still fails the test.
Pinning a conversation only fed the Pinned filter and the menu label, so in the
full list nothing moved and nothing showed it was pinned. Pinned conversations
now come first within their project, the kernel's order holding inside each
group, and each carries a small pin.
CopyButton gained both a menuitem role and an aria label on studio, the
actions list gained reply.branch, and the new selection tests build a reply
action set that now also needs the branch pair.
A replacement published beside the old tree keeps the old tree, so an
uninstall that removed only the active root left the managed directory
behind. Removal now also deletes the canonical copy and earlier staged
copies, skipping any directory the registry still points at.
rewind_changed is a content-free invalidation a reopened pane replaces with
a read of /rewind/undo, so the wire log skips it, and the new close button
declares its action id.
A server whose tool definitions differ from what the user approved has those
tools withheld from the model, and the kernel already had an approval route
for them, but the settings row never said so and offered no way to approve.
The row now carries the withheld tools with how each differs, and a button
that sends back the digest of exactly that set, then reconnects.
The page's iframe now exists only after a navigation and the browser opens
from the workbench launcher, so the tests open the launcher and compare the
address bar, which stays mounted across the toggle.
Archive moved the active file into .archive and left the index flush to its
caller, so a failed flush kept the fact archived while MEMORY.md still named it,
with no way for a retry by stable id to tell which state the store was in.
The move is now undone when the flush fails, and a retry by stable id finds the
archived copy and drops the stale line. The test seam this needs is the one the
rename fix already added to the mainline.
RestoreArchived wrote the active file and reindexed it before removing the
archived copy, so a failure in either later step left the same id active and
archived at once, and the identity guard refused every retry.
Both steps now undo the restored file, so a failed recovery never leaves a double
identity and the same call succeeds once the obstruction is gone. If the undo's
own index write fails, a line naming the fact can remain; the retry rewrites it.
The quiet phone composer hid the branch pop along with the refine entry,
so the no-repository chip, which explains why git features are missing,
was unreachable at 390px and the branch-tooltip browser guard failed on
studio. Only the refine entry is hidden now; the composer guard asserts
the branch pop stays.
The perf page imported fewer stylesheets than main.tsx, so controls such
as the changed-files pill and the session row actions rendered unstyled
in anything captured from it.
The sidebar no longer slices each project to a fixed 30 rows: it asks the
kernel for pages and draws every loaded row. Batch selection now takes
all displayed rows, the selection pruning moves into a hook beside the
toolbar so Workspaces.tsx stays under its ceiling, and the two tests that
assumed the old 30-row cut assert the loaded rows instead.
A folded thread hid the maintainer's question while the status said
the report needed an answer. A report with needsInput now starts open
and is acknowledged as seen like any opened thread. The existing
entry-badge test opens the threads it expects to be read, and the
needs-input test no longer clicks a thread that is already open.
The six rules move out of studio.css into phonecomposer.css, without
the two !important the budget does not have room for: the attach
button's display no longer forces itself, so the quiet-state hiding wins
by specificity. The quiet toolbar also stops wrapping its model group.
The composer guard clears the draft left by its earlier steps before the
phone section and the guard page imports the new stylesheet.
The toggle was disabled whenever the settings sheet was open, so after
pressing any icon in the rail the top-left button looked live and did
nothing. It now opens and closes the workspace column in settings too.
The streaming summary line wrapped to a different height on every token
while the transcript was pinned to the bottom, so the card and everything
above it moved up and down for the length of the fold. The line is now
always drawn, one line high, truncated with an ellipsis.
The conversation, run analysis and workbench tabs sat on their own lighter
band with a divider under it. The row now takes the page's tone and drops the
divider; with a wallpaper the existing wallpaper rule still gives it a stable
surface.
Switching away from a built-in browser tab and back drew a white page until the
window was resized: a view parked offscreen and moved back with setBounds alone
keeps a widget that does not repaint. The shell already knows that attaching a
view again wakes its widget (it does so on minimize and restore); a page that
was parked now gets the same when it is shown.
#12340 added 18 lines to studio.css, which must not grow. The drop-hint rules
move to styles/sidebardrop.css, imported with the other feature sheets, and
the sidebar-drop fixture gains the Sidebar props that became required after the
branch point (navShown, paging) so the file typechecks again.
#12318 added 26 lines to studio.css, which must not grow. The quick-action
rules move to styles/sessionquick.css, imported with the other feature
sheets, and the paging test finds its filter chips inside the filter group so
the new archive and delete buttons, whose names also start with 归档, are not
mistaken for them.
Workspaces.tsx held the session row's menu inline, 63 lines of JSX that only
report which action was chosen. It is its own component now, with the same
markup and the same handlers, so the sidebar file has room for the quick
actions, folder drop and batch selection that are waiting to land in it.
SaveWithOptions wrote the new active file, then removed the old one, then
repaired the index. A failure in either later step left one id readable as two
active facts or left MEMORY.md naming a file that no longer existed, and a retry
could not tell which state it was in.
The rename path now stages the new content, retires the old name out of the
active namespace, flushes the old index line, publishes the staged file, and
reindexes; every step before publication is undone from the retired copy. The
helper that removed the old file in place has no callers left and is deleted.
Every writer that declared no extent (bash, MCP, a sub-agent given no
write_paths) held the whole workspace, so two sessions on one project queued
behind each other even when nothing they wrote overlapped. [agent] write_lease
= "optimistic" in the user file makes such a writer claim nothing; a writer
that declared paths, including paths that cannot be resolved, still excludes
other sessions. The mode is carried by each session's lease owner, so sessions
in one process keep their own, and a project file cannot change it.
Studio had no click entry for branching: /branch copied only the current tip and
swapped the pane onto it. A finished reply now carries a 'Branch into a new
conversation' button that copies the conversation up to the end of that reply's
turn into a new session, titled '<title> - branch' so it never reads as a second
copy of its source, and opens it while the original stays as it was.
Controller.BranchAt cuts at a live turn boundary and shares the file-and-meta
writing with Branch. POST /branch/at answers with the new path and refuses with
typed codes (running, unavailable, failed).
The catalog can now pull a published release, but a user already running it
saw an ordinary update. The version hub carries a withdrawn note for the
running version, mirrored on the desktop side, and the version panel leads
with it: which version, the publisher's reason, and where to move to.
A report that needs a log (it will not start, the kernel keeps restarting) had
no way to say where the log is: Studio writes shell.log and host.log under the
user data folder but nothing opened it, and the Windows and Linux builds have
no menu bar to put an entry in. The feedback form now offers 'Open logs
folder' when the shell can open it; the shell names the folder, the page does
not.
A 2000-turn conversation drew 2000 buttons per rail, 0.27 px apart, and every
pointer move rewrote all of them. Marks closer than 2 px are not told apart by
eye or pointer, so only one of each cluster is drawn; the rest stay addressable
through their positions, so the pointer and the keys still land on every
message.
The first screen fetched the 7.8 MB full Noto Studio font because every Chinese
character resolved to it. A core face with the common simplified-Chinese set,
Latin, punctuation and symbols now comes first, and the full font stays as a
later family limited to CJK ranges, so a character the core lacks still finds
its glyph there and only then pays for the download.
TestGoalResumeCommandContinuesTheGoalAndSaysSo failed in the race job on
studio (run 38188087939) with "resuming the goal said nothing" in 0.00s,
while the turn ran and the goal completed. The test built the expected
notice from c.Goal() as it read each event, and completing a goal clears
its text; the buffered events channel lets the controller send the notice,
finish the turn and complete the goal before the test reads anything, so
the real notice was compared with one built from an empty goal.
The expected line is now built once, before /goal resume is submitted.
TestResolveJumpHostsUsesConfiguredAliasesAndSSHConfig bound two things:
that ResolveJumpHosts reads ssh_config aliases and configured hosts, and
that this machine's real `ssh -G` answers. The second failed on Windows CI
again, twice on studio (runs 38179453323 and 38193303449), both at 15.0 s
with `ssh -G "bastion": exit status 1`: the product's own subprocess
timeout. The test's comment asked to split the two if it recurred rather
than shorten that timeout.
The resolution test now supplies the `ssh -G` answer through the source's
resolveOpenSSH seam, as the sshconfig tests already do, and keeps every
assertion. TestResolveJumpHostsThroughTheInstalledOpenSSH runs the same
alias through the real client, so a starved ssh fails under its own name.
Reviewing #12562 left three points open in this code.
differentialPerTest checked the base side before registering the defer that
restores the source, so a failure from that check returned with the tree still on
base - the state the restore exists to prevent. The defer is now registered
before the check, and treeMatches is a variable so a test can inject the failure;
TestDifferentialPerTestRestoresWhenTheBaseCheckFails asserts the PR's content is
back after that failure, and deleting the reorder makes it fail.
The report printed one sentence for every analysis failure, claiming the pin and
mutation figures were both absent even when the pin table had already been
produced and only the mutation sweep failed. analysisFailureLine now says which
figures are missing, and a unit test pins both cases.
The comment above the restore helpers described what the code used to do; it now
states only the constraint it enforces.
1.x closed an open slash or @ menu on Esc and, while a turn ran, cancelled
the turn as well. 2.x's menuKey consumed the key after closing the menu, so
a user who had started typing a follow-up during a run had to press Esc a
second time to stop it, while the thinking line said Esc stops it.
menuKey now cancels the running turn after closing the menu, except in vi
mode, where Esc never interrupts a stream. Idle, Esc on the menu still only
closes it and keeps the draft.
The sponsor logos link out, but the paragraph beside them read as plain text,
so the entry point was easy to miss. Each paragraph now ends with a visible
link to the same registration address its logo uses.
A release found broken after publication could only be fixed by shipping a
newer one, and installs kept being offered it meanwhile. The catalog now
carries a withdrawn list: a withdrawn version leaves the list old clients
read, a new client never offers or installs it and names it when it is the
one running, and a rerun of its own publication cannot list it again.
scripts/withdraw-version.sh edits the catalog; a manual workflow runs it
against the Studio catalog and keeps the GitHub release off the latest slot.
A tape is recorded as three files per provider exchange, and every write
discarded its error. A recording that lost a file therefore looked recorded, and
replay could not tell the difference: with the recorded request missing,
requestDiff compared against nothing and reported a divergence, blaming the
harness for a tape that was never complete. Nothing in the run's own report
mentioned it either, so a run whose tape can never be replayed read as a success.
save now returns its error and writes request.json last, so that file's presence
marks an exchange complete; load reports a missing recorded request as an
incomplete tape instead of a divergence; and the meter counts failed writes and
names the first error, which the report prints.
Two review findings in the same boundary are fixed here too. The run path read
the meter through record, which snapshotted before the final handler's deferred
save could run; stopping the meter is what waits for those handlers, so record
now drains first and the last exchange cannot be missing from the report. And a
re-recording over an existing tape left that tape's completeness markers in
place, so a failure partway through read as an old request beside a new partial
response; recording now clears those markers before it starts.
The healthy path is pinned as well, so the failure cases are not the only ones
covered: a complete recording reports nothing incomplete and prints no line.
Deleting any of the four checks reproduces the old behaviour.
A batch printed its own provenance to the terminal and kept the per-run records
on disk, so the two could not be read together afterwards. The fingerprint
(model, endpoint, window, generations, commit, start time) existed only in
scrollback, and every aggregate the report is read for — funnels, dose response,
pooled deltas — was printed rather than stored. Reconstructing a batch after the
fact, or attributing a figure in the README to the runs that produced it, meant
trusting a terminal transcript; the 24-run and 36-run totals in the last batch
are exactly that failure.
Each paying mode now writes batch.json beside results.jsonl: mode, start and
finish, the checkout's commit and dirty state, model, window, generations, the
task ids and arms the batch covered, planned against completed runs, the summed
usage of the runs that reported any, and the sha256 of both artifacts so a
manifest and the records it describes cannot drift apart.
Two things stay honest. Usage totals are absent when nothing reported any,
because an unmeasured cost must not read as a measured zero, while
usage_reported_runs is written even when it is zero, because that is a fact. And
a checkout git cannot describe is recorded as an unknown commit with no dirty
flag at all, rather than as a clean tree.
The icon rail kept its own icon set, so the same destination showed one mark
on the rail and another on the page it opens. Entries that open a section now
read that section's mark from the settings table; only the conversation item
keeps its own.
A history can carry a tool call whose arguments were recorded as an empty string
(a no-argument tool on a gateway that sends ""). Replayed as-is, a strict
gateway that parses every function_call.arguments as JSON refuses the whole
request with HTTP 400 ("Assistant tool call arguments must be valid JSON").
The repair that already closes truncated arguments now also turns empty or blank
arguments into {} on the way to a provider; a saved session keeps what it
recorded. The repair moves to its own file.
The baseline had diverged from studio's. Took studio's baseline.json and
re-ran repolint -update, which only lowered budgets for the code this change
removes (70 lowered, 14 entries removed, none raised).
reasonix upgrade overwrote the prebuilt binary inside node_modules, so
npm kept recording the old version (npm outdated -g, npm ls) while the
binary reported the new one. An executable that lives in an npm package
(bin/ next to a package.json naming reasonix or @reasonix/cli-*) is now
refused before any download, with the npm command to run instead.
The no-op main builds on every platform but Linux so Windows no longer has an
empty package, fixes its typo, and drops two comments that still described the
Windows helper.
Studio on Windows runs the downloaded installer directly (Line.RunInstaller);
nothing builds or starts reasonix-update-helper.exe there. Delete its Windows
sources and tests, the WindowsHandoff staging code, the Windows payload
manifest and its sign subcommand, and move the repair/installlayout entry
points that only that helper called into test support.
The process-input gate counts a direct os.UserHomeDir call; folder grants now use
the package's injectable osUserHomeDir like the other home lookups here.
A Build handed Options.Home still put three storage roots under the
process's home: isolated and best-of-n worktrees, browser profiles, and
the missing-reasoning recovery state. Each now resolves through the
build's config.Roots, which is the process binding whenever no home is
stated, as in every production frontend.
The new process-input repolint rule ratchets direct reads of the process
environment, working directory and user directories below
internal/frontend, so later slices can only move that count down.
40 boot tests now state their home and workspace instead of mutating the
process, and run in parallel. The isolation effect test runs two
sessions with different homes side by side and fails without the
binding fix.
The provider registry now holds a read-write lock: parallel Builds
register and resolve kinds concurrently, which -race reported as a
race and could crash with a concurrent map write.
Refs #12531
Refs #12521
The path is a manifest inside the source the person asked to install, and the
read is capped at the fetch limit. The CodeQL alert on the open is a false
positive for this call; say so where the open is.
The rebase onto studio put the unnamed-input surface three over its ceiling and
left provider.preset unregistered. The search, name and key inputs now carry the
provider.draft action with a value, and the gallery and install form buttons are
registered.
POST /providers/presets/install builds the entries a preset declares, whole
(catalog, windows, vision models, thinking shape, PresetID and version) with the
person's name and key, through the same storeProviderEntries the manual add
uses; the add form lists the kernel's presets grouped by host, searchable with
the form's own input style, and installs through that call.
The model cannot read the host clock, so a session left overnight keeps
reasoning from the date it started on. The date (with weekday and UTC offset)
is now a turn block owed through a projectionDebt: delivered on the first real
turn, absent on later turns of the same day so their bytes are identical,
re-owed on the first turn after the date changes and after a fold. It never
touches the cache-stable prefix. The clock is an explicit input
(control.Options.Clock); the boot assembly supplies the system clock and a
controller built without one tells the model nothing.
/tree sent "sessions": null for a workspace with no saved session, because
workspacePage returned a nil slice both when the listing failed and when
there was nothing to list. Studio iterates that field, so a fresh install
or a new project opened to a blank window.
Fixes#12743
RemoveProvider now returns which roles it handed to another service and which it
emptied, and also removes the name from desktop.provider_access. The remove
endpoint carries that report and the provider detail pane shows it, so a
removal that switches vision, advisor or decision off says so. A failure after
the removal was already written is shown where the service used to be instead of
in the detail of whichever service is selected next.
A job the session started and has not collected writes into the tree
without any receipt, so prose after a passing check was waived as if no
writer could still change files. The contract now counts a running job of
the turn's session beside tool hooks and extension sidecars.
The generic check was measured from the latest surviving write, so a
README or CHANGELOG edit after the tests passed sent the turn back to
re-run them. The anchor now steps over a trailing run of prose whose
extent the host established, when no check is declared and the role is
not Delivery, and only while a check stands passing over both the latest
change beyond prose and the anchor the turn would have had without the
prose. The prose judgement is the one #12520 introduced, now read per
receipt by both the whole-ledger waiver and this one.
A named write follows links, so its receipt now also names the file a
link resolved to; otherwise writing code through a .md link and removing
the link before the end of the turn would read as prose.
A configured tool hook, or an extension sidecar on a point every tool
call passes (tool.before, permission.decision, tool.after), runs outside
the bash sandbox and names no paths, so the check contract carries that
fact and neither prose waiver applies while one is present.
Provider clients took netclient.NewHTTPClient, so a redirect from the
configured address was followed by Go's default policy and the key went with
it. Add redirectguard.StayOnOrigin, build the probe, provider, System One,
balance and doctor clients through endpointclient.New, and guard the default
clients of the model listing, balance and System One calls. A refused redirect
is a permanent error that names the target host and reaches the user as
redirect_refused instead of a network fault retried ten times.
The test relied on a freshly written file's modification time being inside the
20ms window of the process clock. A file system stamps from its own clock, which
on a loaded Windows runner can lag the process clock by more than the window, so
the object was served from memory and the leg failed intermittently. The store
clock and the file time are now both set, which is what the test is about.
Merge Studio 0ac9826a9e while retaining Pane-owned undo and upstream work folding, running-tail handling and repeated-speaker grouping. Adapt three imported transcript fixtures to the existing undo ownership contract and cover folding, attachments and restore/undo together through the real Pane.
Ordinary upstream integration: 238 files, 7968 insertions and 704 deletions. Manual resolution and regression delta: 41 insertions and 9 deletions. Both histories are preserved.
Windows refuses < and > in a file name, so TestTreeWriteKeepsJSONContentTypeAndEscaping
failed at mkdir on the Windows leg. & is the other character the JSON encoder
escapes, so the same assertion holds there.
A move copies the root while the app keeps running and writing to it. The
verification pass hashed every file with no progress report, failed the whole
move on the first file that had changed since it was copied, and the commit
then deleted the old folder even when the app had written to it since.
Verification now reports bytes and files as it goes, copies again what changed
(bounded rounds, then ErrSourceChanging), and the old folder is kept when it
was written to after the proof. A refused target names what it holds that the
data does not own.
Replaces the stack #12601 (hide by default), #12637 (hint line) and #12643
(kernel setting). [ui] show_receipt (default off) is read and written through the
controller (ReceiptSettings, GET/POST /receipt-settings) like show_changed_files;
Studio keeps a per-kernel answer and carries an old browser-local on across once;
the TUI draws a line, or the card when the key asks for it. The kernel grades the
receipt (Severity) and the hidden card leaves one line for a graded one.
#12655 added one Messages field and pushed the file to 801 lines, which fails
repolint on the baseline. Drop one separator line between two field groups.
The column opened with a browser tab already in it, whichever way it was
asked for. It now opens on a start page that offers the file explorer, the
changed files and the browser, and a browser exists only once it is chosen.
A page a followed link already has on its way is still shown as before.
The sidebar had three toggles in two screen positions (header button,
title-bar button that shifted with the sidebar, edge grip), a rail
conversation item that did nothing from the conversation view, quick entries
repeated in the sidebar while the icon rail held them, and an R mark plus a
name on every reply. The title-bar button is now fixed to the window and is
the only button, the header one is gone; the conversation item opens and
closes the workspace column and returns home from settings; the sidebar's
quick entries show only when the rail is not drawn and the rail gains the
files entry; replies from one model after the first carry no header.
[ui] fold_turn_work (default false) is a kernel setting read through the
controller like the changed-files pill. When it is on, a finished turn
shows its last sentence and gathers the tool calls, thinking and the
sentences in between into one closed line with the call count and any
failure; the line opens on click and keeps its state, and the turn that is
still running is never folded.
At a couple of hundred CSS pixels the composer's model group could neither
shrink nor wrap and the title bar's right-hand controls left its title no
room, so rows ran over each other. Below 300px of room (the width at which
the five controls leave the title under its 4em floor) the bar gathers phone,
find, theme, sandbox and browser under one button; each stays one press away.
The model group wraps, the tabs shrink, the meter row drops its cache figure
and the placeholder keeps to one line at the narrowest widths. The size
slider's fallback range parks the thumb mid-track while no range is announced.
The run analysis drew a rail labelled only 'Signal rail' and 'Shares the
timeline below'. A line now says it holds the run's records that have no
duration and are not tool calls, placed at the moment they happened, so the
position is the time and cannot be dragged.
Choosing a folder in the system dialog no longer grants it; Add does. The
panel states that the agent can change and delete anything in an added
folder and that panes already open narrow only on rebuild. A failed read
shows a retry, refusals use the error box, and the project row is laid
out like the other facts.
Settings > Tools and permissions > Sandbox lists the project folder and
the extra folders granted to it, and adds or removes them through the
existing /workspace/roots routes. The kernel's refusals are worded by
their codes. The panel says that 'allow everything' only decides whether
to ask and does not widen where writes can land. No folder is added by
default.
[notifications] sound = true plays a short system chime for the events already
selected, independent of system notifications and of their muting, so a
minimised or backgrounded window is still audible. Off unless asked for. The
Windows, macOS and Linux senders implement Chime; the Settings notifications
block gets its own switch.
Import Studio 71650fe7, including upstream desktop lint fix 85460ae and test-only sandbox portability fix 2b5f6448. Keep the running-jobs fixture deterministic by selecting its scripted provider explicitly and asserting the background launch.
Integrate Studio 71650fe7, including upstream desktop modernization 85460ae14 and the job-list test convention from 2b5f6448. Keep the latter test-only setting unchanged and use the supported scripted resolver with explicit selection and launch assertions. The only authored delta from the automatic merge is 17 additions and 10 deletions in that fixture; no coverage workflow or product sandbox changes.
In the activity group a running call that had printed nothing was a row that
did nothing when clicked, and a long command was clipped to one line, so a
call waiting out a timeout looked frozen. Such a row now opens to its input,
says it has no output yet and for how long it has run, names the command's
own timeout when it carries one, and its hover text holds the whole command.
Attachments, wallpapers and theme packs were each turned into base64 before the
kernel could refuse them. One guard in the port checks the kernel's cap first,
using the image cap for pictures, and a Go test pins every number to the kernel's.
The test starts a background bash job through the real Build assembly. On a
host without bubblewrap the sandbox refuses to run unconfined, so the job
never starts and the coverage leg fails there while passing on a developer
machine. Its sibling effect tests already declare [sandbox] bash = "off".
Windows 11 kept the kernel's 10.0 major, so a build like 10.0.26200 read
as Windows 10 (issue #12696). Builds from 22000 report as 11.0.build,
matching the product version the darwin path already reports.
/tree windows each workspace by the request's scope (active, archived) and word,
always builds pinned active conversations, and reports the total, archived and
remaining counts for the whole folder. The sidebar counts from the kernel, so the
chips do not move with the window, an archived conversation far outside it is
reachable, search is filtered on the kernel and paged, and a folder holds at most
300 rows. The remote machine list shows the far kernel's total instead of a
truncated count.
Windows accepted \??\C:\... because only a leading double backslash was
refused. A root's volume must now be a drive letter and a colon; UNC, \\?\, \\.\
and NT object paths are all refused. Adds a boot-level test where a delegated
child writes only a granted folder.
PowerShell candidates were launch-probed one after another with a 10 s
timeout each, and nothing bounded host construction or the lookups around
the probes. On a machine where Store aliases, a PATH entry or a registry
read stall, first start sat in shell discovery past the shell's 60 s
handshake wait.
PowerShell now shares bash's hedged search and budget, probe commands
cap their post-kill wait, and the whole resolution (host construction
included) gives up after both budgets plus a hedge: it uses a bash
already proven in this process, else the bare name with a warning that a
restart usually recovers.
The skill-shadowing warning after an install and the not-selected warning
after a root is registered now render their paths through hostLiteral
(60f37ee7c), checked by a unit test that feeds an unterminated OSC to the
two sentence helpers. #12547 also asked for the newline, bidi and
zero-width cases.
Two end-to-end tests add them: a skill is installed, or a root
registered, while another skill under a configured path whose name holds
the character keeps the selection, and the warning must show the
character escaped for every path rather than carry it raw. The newline
case is skipped on Windows, which cannot hold one in a file name, and
runs on the Linux and macOS jobs.
Fixes#12547
Each transcript row asked the browser to skip rendering it while off screen
with an estimated height, and swapped the estimate for the measured height
the first time the row scrolled into view. The page shrank under the wheel
(12975px to 11217px in the probe), so the content jumped, once by 1858px.
Rows that are far off screen are already unmounted by their block, which
holds the measured height, so the estimate had no job left.
unapplied_steer names the queue entry the kernel took out of the turn
without the model reading it and holds for a retry. The row that said it
was waiting stayed forever, while the queue panel, which the kernel
authors, showed the same line as blocked and retryable. The row now goes
when that notice names it; the notice card stays.
The 400 text told every reader it was probably a bug. The host cannot know that:
the reason the service gave is appended, so the preface now says whose refusal
it is, where the reason is, and what can be tried, without promising it works.
The poll re-ran setContextMenu every five seconds with an identical menu.
Where the menu is handed to the desktop's indicator over D-Bus, replacing
it closes one the person has open. It is now rebuilt only when the
sentence, the words or the close-to-tray setting differ.
The prose waiver judged paths by what the disk showed at the end of the turn,
so a documentation-named symlink to a code file that was written through and
then removed counted as a deleted prose file. The resolved target is now
recorded on the write receipt while the link still resolves.
Refs #12524
attach() turned the whole file into an ArrayBuffer, a binary string, base64 and a
JSON string before the kernel could refuse it at 25 MB, so a modpack-sized
archive could take the renderer down. The size is checked first and refused with
the kernel's own attachment.too_large identity.
The rail's hints were drawn the instant the pointer touched an icon, so a
pass across the column flashed every label. They now appear after a 0.3s
rest (--tip-delay; VS Code waits 250ms), and keyboard focus still shows its
hint at once.
The [desktop] renderer wrote conversation_width only for full, so a full-all
choice reverted to standard on the next start. It now writes every non-standard
width; a render-and-reparse test, a page-attribute test and narrow-viewport
checks cover the new choice.
The font fields took any family the machine has but suggested only a dozen,
and on Windows few of those are installed. The suggestions now cover the
common CJK, Latin and programming faces, filtered to the ones the machine
really has, as before. Nothing is chosen by default.
The full measure widens the transcript only, by design. A third choice,
full-all, moves the composer's column with it; the default and the existing
full measure are unchanged.
A host-authored inbox item (a background job's completion, for one) ran
as a synthetic turn that opens no boundary and so emitted answer text and
a turn end with no turn start. A client that never heard the start kept
the send arrow, showed no stop button and counted nothing running, and
its status poll stayed off, so it could not stop a turn that was
running. The queued host turn now announces its own start.
A refusal reached the model only in that call's result, which compaction can
fold away, and the posture refusal did not say not to retry. Calls refused by
the host (any permission refusal code) are now owed to the next real turn as a
refused-calls block, delivered once like the other turn projections, and the
posture refusal says the call and its rewordings will not be approved.
Refs #12253
On a 1M window the fixed 8K output margin was smaller than the prompt
estimate's own error, so a prompt a percent above its estimate plus a 64K
completion exceeded the window and the provider refused every turn. The margin
is now max(8K, window/32), and a codeless 400/413 is classified as an overflow
when the estimate plus the requested completion is past the window, so the
turn folds and replays instead of repeating the refusal.
Fixes#12694
Windows 11 reports NT 10.0, so the environment block read like Windows 10.
The form now shows 'Windows 11 (10.0.26200)' for builds from 22000; the
version string that is sent is unchanged.
The kernel sent toasts through the Windows Runtime but never installed an
activation callback, so a click did nothing. It now registers one; the
host forwards it as a show act over the handshake pipe and the shell's
act handler (now its own module) shows and focuses the window.
[agent] ask_timeout_seconds (user file only, default 0 = wait) bounds how long the
agent's own ask question waits. On expiry the host settles the card on every
frontend with a timed_out decision receipt and returns the model a result that
names the timeout, not an answer. External forms are never skipped.
Studio settings and the terminal's /ask-timeout drive the same controller
setting.
Extra roots are the project grant's folders in the user's home. They are a set
of their own: write roots and the OS jail read them, the guard on Reasonix's
own state does not treat them as exemptions, the turn tail names them, and the
scan, readiness and receipt of the executor and of every sub-agent count them
as workspace. One judgement (FolderGrantRefusal, shared with workspace trust
and POST /sandbox) refuses drive roots, home and its ancestors, storage roots,
key and credential directories and the forbid list, in either direction,
comparing file identity rather than spelling, plus folders the user does not
own, UNC and device-namespace paths, and overlaps with other write roots. Every
load vets each grant again. Withdrawing a root rebuilds every pane on the
workspace, after this pane's lock is released.
The change time cannot be restored the way a modification time can, so an
in-place edit that restores size and mtime misses the cache. Platforms
without that identity fall back to size and time. Adds limit and
replacement tests and states the in-process memory in the package doc.
A locked branch-meta read-modify-write passes what it read to the write
instead of reading again, and recording a revision no longer re-reads
what it just wrote. trustedstate.Object remembers a verified object with
its size and modification time and serves it while both are unchanged
and old enough to be meaningful; Verify always reads the bytes.
The docked pane's grid kept the navigation and find rows across both
columns, so the browser column began 44px below the pane top and its own
8px padding made the gap 52px. The column now spans every row beside them;
under the scene fold the old stacked layout is kept explicitly.
stageDispatcher built the successor with empty options, so after a subgraph
rebuild an optional extension without a sidecar was skipped silently, right at
the moment an install or enable makes that most likely. The successor now takes
the reporting of the dispatcher it replaces.
Refs #12258
Bumps the go group with 2 updates: [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) and [golang.org/x/sys](https://github.com/golang/sys).
Updates `github.com/mattn/go-runewidth` from 0.0.30 to 0.0.31
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.30...v0.0.31)
Updates `golang.org/x/sys` from 0.48.0 to 0.49.0
- [Commits](https://github.com/golang/sys/compare/v0.48.0...v0.49.0)
---
updated-dependencies:
- dependency-name: github.com/mattn/go-runewidth
dependency-version: 0.0.31
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: go
- dependency-name: golang.org/x/sys
dependency-version: 0.49.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: go
...
Signed-off-by: dependabot[bot] <[email protected]>
Keep the already-built quote and broadcaster projections when canonical configuration cannot be read. Warn without rewriting the recoverable file, while still enrolling valid local controllers. Cover real Adopt, Open, model, extension and workspace publication with malformed configuration, including built-in-default fallback. Refs #12674.
Assisted-by: OpenAI Codex
Bumps the go group in /desktop with 1 update: [golang.org/x/sys](https://github.com/golang/sys).
Updates `golang.org/x/sys` from 0.48.0 to 0.49.0
- [Commits](https://github.com/golang/sys/compare/v0.48.0...v0.49.0)
---
updated-dependencies:
- dependency-name: golang.org/x/sys
dependency-version: 0.49.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: go
...
Signed-off-by: dependabot[bot] <[email protected]>
Preserve the controller retirement identity at the HTTP boundary with a localized 409 refusal. Exercise selection racing a real model replacement, then delegate to the retired controller and assert no config or currency mutation. Refs #12674.
Assisted-by: OpenAI Codex
Exercise real booted local runtimes, a remote pane, live ledger and balance projections, future wire quotes, model replacement and sequential stale calls after close. The canonical-config fixtures do not claim a successful durable settings write. Refs #12674.
Assisted-by: OpenAI Codex
Verify canonical quiet enrollment, failed binding atomicity, replacement and close membership, and projection ordering against the next notice. Refs #12674.
Assisted-by: OpenAI Codex
Check full local sinks, future quotes, remote exclusion, retirement and failed-save atomicity. Retain the real HTTP currency-save assertion for CI; this sandbox cannot acquire the OS-user config lock. Refs #12674.
Assisted-by: OpenAI Codex
Enroll only local controllers and reconcile their quote and broadcaster projections before publication. Share the same ordered replacement path across model, extension and workspace rebuilds, and retire membership when a runtime closes. Refs #12674.
Assisted-by: OpenAI Codex
Serialize persisted display-currency changes with local-controller membership. Quiet enrollment refreshes the exact quote context from canonical configuration; retired generations cannot save, and successful saves notify each full local sink. Refs #12674.
Assisted-by: OpenAI Codex
Bind unread polling to availability and identity of the first local machine owner. Opening or replacing that owner starts a fresh read while ordinary active-pane switches keep the polling session. Cover remote-only startup, opening local sessions and retiring the first local owner through the real App. Refs #12674.
Assisted-by: OpenAI Codex
Use the same pinned local catalogue owner for the default-model write and read. Cover a model exposed only by the second local runtime through the actual Settings picker, SSE route and HTTP catalogue validation. Retain the durable-write assertion for CI; this sandbox cannot acquire the OS-user configuration lock. Refs #12674.
Assisted-by: OpenAI Codex
Pull requests whose diff is provably platform-neutral run only the Linux legs;
the macOS and Windows legs keep their check names but execute on an ubuntu
runner with every step skipped. Pushes to main-v2/studio are unchanged.
Give the benchmark window one stable BenchPort owner so its language, zoom, theme and save instrumentation still reach usePaint. Exercise the real harness and paint hook without changing browser guard assertions. Refs #12674.
Assisted-by: OpenAI Codex
Select the active local pane, else the first local pane, for Memory, model catalogue resolution and legacy-session recovery. Keep machinewide pages on their stable host owner. Retire cached workspace settings ports after successful close and restore the unrelated portFor comment. Refs #12674.
Assisted-by: OpenAI Codex
Allow host settings requests to pin an existing local runtime. Refuse stale or remote owners without falling back, while unpinned machine settings retain the first live local owner. Cover real workspace memory mutations, model routing and runtime replacement. Refs #12674.
Assisted-by: OpenAI Codex
The copy claimed alerts appear while the window is in the background; the sender
fires whether or not the window is in front. It now says so, says the switch
ships off, and that the sound is the system's with no separate setting.
The update path started the NSIS installer with no arguments, so it ran
as a fresh install: the previous uninstaller deleted the shortcuts and
removed the AppUserModelID entries (the taskbar pin), and the installer
recreated the desktop shortcut. With --updated the installer takes
electron-builder's in-place update path and keeps them.
Add a default-off Appearance preference for session tabs, persist the choice locally, and show the existing strip only when multiple session panes are open. Preserve single-session behavior and cover preference changes, persistence, and unread tab marks with interaction tests.
The orchestrating agent had no way to ask which background jobs are still
running and for how long, so it kept a count of its own and miscounted. The
listing comes from the job manager that owns the jobs (id, kind, label,
elapsed), through the existing read-only tool so no tool schema is added.
list_subagents rows now report when a child started and, while it runs, for
how long.
The queued line is drawn after the message the model is still writing, and
appending the next chunk only looked at the last item, so the chunk opened a
new message and one stretch of thinking showed as two blocks. Appending now
continues the open message past lines that are still waiting; once a line has
been delivered and the earlier message settled, the next chunk is a new one.
The branch chip's small "N changes" moves to a centred pill right above the
input box reading "N files changed +x -y", from the same tree.changes the
workbench lists. Clicking it opens the workbench's change section. A kernel
setting [ui] show_changed_files (default on) switches it off, which restores
the chip's small count.
TurnDone carries the run time up to the final answer; history carries it on
the answer that closed a turn. The terminal UI and Studio draw it under the
answer, and the live status timer uses the same compact form.
The Linux helper's accepted package name was a build-time variable that
linux-payload.js never set, so the helper in the .deb kept
reasonix-desktop and refused reasonix-studio as a different package.
It now takes the name from update.StudioLine, the one place the shell
asks dpkg by, so there is no build value to forget. A package command
prints it, studio.yml asserts the packaged helper accepts the .deb's own
package name, and a test ties electron-builder.yml's deb and rpm names
to it.
A read-only worker cut off by cancellation left a transcript the parent never
heard of: the aggregate said only context canceled. The reference now travels
with the cancellation (parallel_tasks, fleet, task) and read_subagent_result
renders what a cancelled child had said, called and read, labelled as not a
result and its tool output as untrusted. ReadFinalAnswer still refuses any
run that did not complete, so fleet adopt_ref cannot adopt one.
DELETE /themes/{id} removes a pack from the user's library only; the
listing marks those packs removable, and Appearance offers a two-step
delete on them. A bad id is a 400 theme.bad_id (ErrBadID), never a path
in the response. The config is settled before the files go and restored
if they fail, so it never names a pack that is gone.
The workbench handle lay full height over the transcript's scrollbar while
shut, and the open handles reached into the scrollbar on either side of a
seam, so a press on the thumb resized the column instead of scrolling. The
workspace handle now starts inside the seam, the docked transcript steps in
from the workbench seam, and the shut workbench handle is only as tall as its
grip, centred and set left of the scrollbar. The grip starts the drag itself
once the press has travelled.
/goal resume only flipped the state and said nothing, so the Goal sat idle until
another message arrived. It now says the Goal is resumed and runs the next turn
of it. A Goal the model or evaluator reported blocked also resumes on the user's
own next message, as one gated by await_user already did; the continuation epoch
is read after that lift so the lifted turn is advanced by the Goal instead of
being discarded as stale.
Refs #12554
The switch refused with busy.switch_model_jobs because closing the outgoing
controller cancelled its jobs. The replacement now adopts the job manager and
the workspace write lease, and the outgoing controller is retired without
closing them; its cleanup runs once the jobs it left have finished.
The admission notices for a session switch, a workspace switch and a draining
runtime told every frontend to resend, but Studio already queues the refused
line on the typed busy code, so following the advice sent it twice. The notice
now carries input_refused; Studio words it as queued, other frontends keep the
English text.
Refs #12554
The notes ceiling clipped the dormant-rules sentence and pushed the dismiss
button to a hidden third row. The button now shares the sentence's first row,
the ceiling has a floor outside short windows, and the queue yields the
difference. Also keeps the diagnostic for payload codes whose detail was not
consumed and pins malformed-payload cases.
Skill shadow and unselected warnings, the migrated and remediation
sentences (plugin name), the persist-status warning, manifest read
warnings and the strict-decode error now show package data through
ShownLocator or ShownIdentity.
Shadow warnings, the incompatible-plugin warning and the skipped
marketplace warning now write the whole sentence first and render the
package-supplied path, error text or reason with ShownLocator, so an
unterminated escape in that data cannot swallow the rest of the sentence.
conclude_blocked and await_user tell the model the turn ends, but a code write
with no check after it still produced a verification gap, so the host ran two
more rounds and reported the turn as failed. A successful hand-back after the
latest write now yields the continuation; the ledger still owes the change and
the completion receipt still reports it as unverified.
Refs #12554
Display, launch and stored policies share one scrub core, so the report
that a launch line lost characters is produced by the same pass that
deleted them (ZWJ, ZWNJ, bidi, escapes) and never by a second rule. The
marker leads the line so tail truncation in serve and the TUI cannot cut
it, and is worded through i18n. observe.Sanitize delegates to the stored
policy; the policy table is declared in doc.go.
TurnStarted and Steer events, and the rebuilt history, name the images under
.reasonix/attachments that a message references, resolved by the same
detectRefs pass that decides what the model sees and reported under their
normalized path. The transcript previews from that field instead of matching a
spelling in the text; the model still reads the @path text.
The runtime bar resolved variables for only two notice codes, so
permission_rules_dormant and job_* showed {n}/{list}/{rule}/{name} verbatim
plus the raw JSON payload. NoticeCard and RuntimeBar now share noticeWording();
a test fails when a code with placeholders has no registered reader.
The bash job function wraps the process's own ending in jobs.ExitError,
the failed-job notice payload carries exitCode and signal, and the
terminal UI and Studio word the sentence from them. The raw error text
stays on the payload and is what an untyped failure still shows.
Display (prose) now drops bidirectional and format characters exactly as
the prose policy does, where it used to keep bidi controls. The launch
line keeps deleting invisible characters, because redaction has to match
a key a zero-width character split, but it now says so with a marker
instead of reading as an unaltered line. One hostile fixture is asserted
across the locator, prose, display and launch policies.
The shell always opened at the default size, so a window the person had
sized, moved or maximised came back as if nothing had been chosen. The
normal bounds and the maximised flag are now recorded as the window settles
and as it closes, and the next launch reopens there. A rectangle no display
shows any more keeps its size and lets the platform pick the place.
The workbench handle lay full height over the transcript's scrollbar while
shut, and the open handles reached five to six pixels into the scrollbar on
either side of a seam, so a press on the thumb resized the column instead of
scrolling. Open handles now start one pixel inside the seam, a shut handle is
only as tall as its grip, and the grip starts the drag itself once the press
has travelled.
The message column shrinks to fit its content and a textarea at 100% of it
starts at its default width, so the rewrite box stayed about 270px wide in a
760px transcript. While a message is being rewritten its column now takes the
whole row.
The transport now returns ErrAuthenticationRequired (via AuthRequiredError with
server, resource and scope) for a 401 that carries a Bearer challenge on a
server without explicit credentials. InstallResultForError maps that identity
to action_required/authenticate and no longer matches words in the message or
promotes a bare 401/403 status. Adds a regression test that refresh stays at the
token endpoint stored at sign-in. The guide stops promising a Studio sign-in
control that does not exist yet.
The receipt took its success from the result after extensions rewrote it, so
a code write refused or errored by tool.after left no mutation in the ledger
and, as the only change of the turn, no verification was asked for. A named-path
write now keeps the success of the tool's own execution.
Refs #12528
The feedback timer spy counted testing-library's own waitFor deadline once
asyncUtilTimeout became 10s; it now ignores timers armed from inside the
library. The settings-layers case swallowed a lookup that never matched its
textbox name, which cost a full asyncUtilTimeout; it now asserts the real name.
vitest gets testTimeout/hookTimeout of 60s, and find/zoom-keys drop their
private copies of the same limits.
The shadow bundle inlined the accepted contract's criteria and the full
verdict obligation list on every turn. Both scale with the number of
captured tests, and the bundle bytes changed each turn, so the
content-addressed store kept a fresh multi-megabyte copy per turn.
The bundle now names the contract revision by record digest only, and
files the obligations as their own object, which repeats nothing when a
turn leaves them unchanged.
Messages is back under the line ceiling with room for the next notification kind.
A report whose last update is over thirty days old no longer keeps the unread
poll running.
Fedora, RHEL and openSUSE cannot install the .deb. electron-builder now
also builds an .rpm, signed and mirrored with the same minisign flow, and
studio-manifest lists it under native_packages[linux-<arch>-rpm] beside the
unchanged .deb key. fpm spells the architecture of an .rpm name x86_64; the
packaging hook renames it to the release architecture and fails on any
spelling it does not know. The package job reads the rpm back for its name,
the helper's owner and mode, and the rpm's post-uninstall leaves the command
link alone on upgrade.
The graphics panel no longer infers a driver fault from gpu_compositing: it
shows the raw status Electron reports, tells a saved choice from a launch-only
override, and says so when no status exists yet. The settings search moves into
its own hook so Settings.tsx has room again.
When the saved default_model is a decision source, a build that did not
ask to open on a fallback (reasonix run, the TUI) passes it over and
opens on the first conversation source, and said nothing about it:
ResolveNewSessionChatModel returned only (ref, fallback, ok), so the
boot had no skipped default to report, while the window path's
ResolveStartupChatModel carried one and emitted the notice.
Both resolvers now return one value, NewSessionModel{Ref, Fallback,
SkippedDefault}, and one helper decides that a saved default was passed
over: nothing configured serves it, or its source does not answer
conversation. newSessionModel takes SkippedDefault from whichever
resolver the build uses, so both paths emit the same
default_model_unavailable notice. What each path opens on is unchanged;
an unknown default is still kept on the no-fallback path for the CLI's
error, so it is not reported as skipped there.
Refs #12561
Record a digest of every tool definition the first time an authorized
server lists a non-empty catalog. A later listing that adds a tool or
changes any definition field is held out of the model's catalog and cache,
and a call to it is refused with the typed code mcp.tool_definition_held.
The user is told through a notice (mcp_tools_held), and accepts the held
definitions with 'reasonix mcp trust <name>' or POST /mcp/trust, which
advance the record and reconnect the server.
User-level servers keep one record for every workspace; project-level
servers keep one per workspace. Removing a server forgets its record.
Refs #12356
The announcement compared only the aggregate unread count, so another reply on a
report that was already unread was never announced. Each report's latest
maintainer reply id is now tracked as well.
When a .mcp.json is refused while planning (over the server limit, an
invalid entry), kind=auto hid the reason. A fetched .mcp.json fell
through to the skill attempt, and with strict:false the JSON was planned
as a skill named "mcp". A GitHub repository whose root .mcp.json was
refused ended as a remote HTTP MCP plan at github.com (a repo named like
"mcp") or as ErrUnsupportedKind. A local .mcp.json file ended in "no
installable ... found", and a folder carrying a refused .mcp.json
dropped it without a word.
A source that is itself a .mcp.json, fetched or local, now answers with
its refusal under kind=auto as it already did under kind=mcp. A folder
or GitHub repository whose .mcp.json is refused keeps planning its other
installables; a folder warns with the path and the cause, and when
nothing else plans, the refusal is the error. A .mcp.json that is absent
or declares no server says nothing, as before.
readMCPJSON no longer returns a bare filesystem error: an absent file is
ErrManifestMissing and one that cannot be read (a directory, a denied
read) is ErrSourceUnreadable, so the caller can tell the cause.
Refs #12514
An unread finish keeps a conversation out of the automatic sweep until it is
viewed. A hand-edited days value past the limit is held at the limit instead of
wrapping time.Duration. Pins are drawn from the tree's own pinned field;
localStorage only carries pins made before the kernel kept them and each is
sent once.
POST /submit answered 409 busy.session_running whenever the turn it had just
started was already over by the time the handler sampled Running(), and also
for a submission parked behind a finishing turn that would run next. The
submit entry points now return the controller's admission result and the
handler refuses only a submission that was dropped.
runTrace gave a fixture turn a fixed 15s to close. A turn is several rounds of
host work, which a loaded Windows runner stretches well past that while the
turn is still emitting events. A turn is now hung only after a silent window,
with a total ceiling for one that never stops.
Add a web app manifest, icons and the touch-icon and theme-color tags to the
page so a phone can install it and open it full screen. No service worker is
registered, so nothing cached can outlive a pairing or a credential. The serve
host now types .webmanifest as application/manifest+json.
The picker leads a row with an accent dot when the kernel reports it unread.
The TUI posts /sessions/viewed when it binds a conversation (launch, resume)
and when a turn ends in the one on screen while the terminal has focus; a turn
that ends behind another window is marked when focus returns.
First slice of treating a WSL distribution as a machine a kernel can run on.
Adds a read-only utility package that lists distributions from the structure of
wsl -l -v and checks one by its architecture and user, plus
reasonix remote wsl list/test. Nothing is installed or started.
An embedded struct's fields are its outer type's own on the wire, and an
interface's extended parents are part of its shape, but the checker read neither,
so the outer PermissionRules type could not be declared and a field added to it on
one side went unnoticed. Same-file embedded structs and extends parents are now
flattened; PermissionLists and PermissionRules are registered.
The first line of a snapshot, the app list and the screenshot caption printed
the application's name raw, and the app list printed window titles escaped
but unbounded. Route them through the literal the modal names already use.
A pin the kernel refused is rolled back and reported instead of staying
visible, and any remembered pin of a listed conversation the kernel does not
report as pinned is sent again on the next tree load, so a legacy pin in a
workspace added after the first sync is protected before its first sweep.
A re-delivered turn_started naming the turn already in front of us is the
same open twice. The transport folds the duplicate seq, but the reducer is
fed raw sequences, and sealing on order marked its live calls "no result"
and dropped an unanswered approval. The seal now keys the start's msgIndex
against the turn the live side holds; naming still runs on the duplicate,
because the row may have been sent between the two deliveries.
The record cannot end a turn. The kernel commits each text-only assistant
round before handleFinalResponse decides to continue it — baseline
criteria, executor-handoff nudge, empty-final retry, steer drain — and
/history carries no end marker, so rebuild stopped inferring "over" from a
record ending on a say. The typed fact rides every status read: Pane's
applyStatus dispatches __running into the reducer, equal answers fold to
the same state, and a payload that cannot say leaves the wire's running
standing. The identity merge stays; the record no longer decides the
turn's life. A lost turn_done self-corrects at the next status answer.
The rebase folds studio's extension retention into rebuild(): an
extension is not in the record either, so the tail keeps it the way the
old __restore filter did.
The status-inflight fixture now answers running over an emitted
turn_started: the pane reads that fact, and a port contradicting its own
wire is not a kernel these reads can be reasoned against.
A __restore re-read taken mid-turn — by hand, or by the stream-gap
rebuild — dropped the cards being written, letting the next delta mint
the answer anew: new identity, the entrance replayed over half a reply,
the text before the gap gone until the message frame. The tail now
merges with the record by identity instead of riding after it: the
kernel commits each assistant round at its boundary, before its calls
run, so the record can already hold a call the live side holds open —
the record wins where it is complete, the live card where it is not.
A record ending on the committed answer, with no call of ours still
out, is a turn_done the wire lost: the answer wins and the turn is
over, clearing a running flag nothing else would clear.
A turn whose end never arrived is sealed the way turn_done would have
sealed it when the next one starts, so its calls stop spinning through
a turn that has nothing to do with them.
session.ts stays under the 800-line ceiling: the merge lives in
state/rebuild.ts.
The read-only execution guard admitted only list and inspect, so
use_capability action=search fell through to the unknown-action block even
though the tool declares it and resolves it exactly like list. The tool's
own description tells every model to search before calling a tool
unavailable, so a read-only subagent was steered into the block.
usecap now exports the declared actions and the catalog-read subset; the
guard reads that set instead of keeping its own copy. The refusal for an
action outside the declared set names the action and the allowed ones.
Every boot.Rebuild re-derived each build-time notice (dormant permission
rules, held project MCP servers, default-model fallback, missing key) from
unchanged state and emitted it again. The runtime owner now carries a notice
ledger; each build runs through a window that drops notices the previous
successful build already showed, so only new or changed notices are emitted.
A Windows file name cannot hold a newline, so the quoting test could not create its directory. The bidi override still exercises the literal-quoting path there.
A removal refusal rendered in a banner at the top of the settings pane, out of
view once the pane scrolled, so Delete appeared to do nothing. The banner is
now an inline alert in the provider detail header. The list is re-read after a
failed removal too, because the kernel may have written the removal before the
failure. RemoveProvider now clears the guardian, recovery, vision, triage,
decision and advisor roles that named the removed service, walking the same
role table the other repairs use, and its doc comment states the behaviour the
code and tests already have.
A rule naming a tool that assembly registers only under some configuration or
in a sub-registry (system_one, advise, best_of_n, apply_isolated,
discard_isolated, submit_plan, conclude_no_changes, review_report,
complete_subtask) was refused on save and reported dormant at load, because the
tool is absent from the registry at that moment. Each owner now declares its
conditional tool with tool.RegisterConditional and the controller's vocabulary
reads the declarations; a boot test reconciles every tool the assembly can
register against the built-ins plus the declarations. The dormant-rule item
shape is also held by wire-parity.
The edit form showed 'read available models from provider' for a decision
source, whose protocol has no model listing. The provider entry view now
carries canListModels, derived from the protocol table's Discovery
declaration, and the editor hides both listing controls from it.
The authoring guards were three hand-written lists in corpus_test.go: which
corpora must carry a reference solution, which must have a grader that accepts
it, and which must let the agent decide when to stop. A seventh corpus could be
added and be checked by none of them, which is what happened to four of the
seven that exist.
Each corpus now declares, in one registry, how it stands against each rule:
enforced, or exempt with a reason. Discovery walks the corpus root, so an
unregistered corpus is a failure rather than an omission from a list. The three
guards read the registry instead of their own lists, and the stop-policy guard
covers every registered corpus rather than the three that happened to be listed.
An entry must name the corpus this root owns: its directory is compared with the
corpus's path under the root, not with its name, so a registry entry pointing at
some other directory that happens to share a name cannot stand in for the
committed one.
The pruning test the registry replaces was weakened rather than extended: a new
corpus used to be silently unchecked.
The model switcher listed every configured source, so a decision wire
(kind typesafe) appeared beside chat models and choosing one failed in the
provider registry as an unknown kind.
GET /models now answers one job: chat by default, answers=decision for the
decision sources, answers=all for the management views. The switch, the
default model and every role refuse a model whose wire does not answer that
job with a typed code (model.decision_only, model.not_decision_source), and
the provider builder refuses it with config.AnswersMismatchError. A saved
default that names a decision source is passed over like a stale one.
the contract translation (disabled -> none/low, enabled -> default) now
applies only when the menu is the contract, the same rule
InheritedEffortOfficial already used. Before, the Token Rhythm preset shape
sent thinking=enabled with reasoning_effort=max for a stored disabled.
Problem: PR #12348's browser menu dismissed agent-owned UI entries instead
of closing the underlying pages, overlapping the controller close work in
PR #12067.
Root cause: The tab strip owned a presentation-only close path separate
from the controller's browser lifecycle.
Fix: Integrate the latest studio base with #12067, retain its useTabClose
owner, and adapt #12348's WorkbenchTabs extraction, close-to-the-right menu
and keyboard access to that path. Keep close-all, preserve file editors,
and retain failed pages while disabling repeated closes in flight.
Verification: 99 focused frontend tests, TypeScript/production and perf
builds, census and its 10 fixture comparisons, make check, go vet, make
lint, builtin/boot and browser/serve/control tests, and 11 release-note
tests passed. Browser keyboard/menu acceptance and eight fixture screenshots
cover light/dark and wide/narrow layouts. Intrinsic, contrast and side
guards passed; wheel exits successfully with its two existing advisory
findings. Native Electron acceptance and hosted CI remain outstanding.
Co-authored-by: KHG420 <[email protected]>
Co-authored-by: par73e <[email protected]>
Both analyses edit a PR's source, run the tests, and put the source back. The
write-back discarded its error and then marked itself restored, so a failed
restore was indistinguishable from a successful one: the sweep went on to report
caught/survivor counts, and the differential went on to report pin results, for
a tree that was still mutated. A verdict from a tree that no longer holds the
PR's code is worse than no verdict, and the mutation would leak into the next
mutant and into the user's checkout.
restoreSource writes the bytes back and proves they came back: it re-reads the
file and compares it byte for byte. A git-based restore is checked with the tree
instead of the exit status of the command that was supposed to perform it, so
files that exist in neither revision now stop the analysis rather than being
dropped. Both failures are returned instead of noted, the differential's result
is recorded as an explicit analysis failure, and the report says the figures are
absent rather than zero.
The I/O entry points are variables so tests can inject a failure instead of
racing one: deleting either check makes TestRunMutationFailsWhenTheRestoreFails
and TestDifferentialPerTestFailsWhenRestoreFails reproduce the old behaviour
("a failed restore still produced a verdict: caught=1 total=1 survivors=[]").
key and hold_key need only the modal their keys go to, so they no longer
read the focused element's value. Typing is confirmed only when the
read-back differs from the value before by exactly the typed text put in at
one place, replacing what was selected there; the changed span is taken
from either end, since repeated characters make it ambiguous. A value that
merely changed and contains a short typed text, as autocomplete or another
writer can leave it, is unverifiable.
A modal's title and the name of the window it holds come from the
application. They were quoted but not bounded, so a 100000-character title
reached the model as about 100 KB, while every other label in the tree is
clipped to 160 by the helper. Both names now go through
textutil.BoundLiteral at that length: controls, bidi overrides and
invisible characters show as visible escapes, and so does a quote, so a
name cannot close its own quotation.
The Windows helper sent keys to whatever held the application's focus and
answered success. With Notepad's error dialog up, `type "hello"` returned
Done:1 while the text went to the dialog's OK button (#11366).
The helper now reads two structural facts, never a title or a message:
- a Win32 modal: an enabled window of the process whose owner chain holds a
window of the same process it has disabled (MessageBox, DialogBox, common
dialogs);
- a dialog drawn inside its window that declares WindowPattern.IsModal, which
is what Windows 11 Notepad shows: one top-level window, still enabled, with
a XAML popup holding its input.
With them:
- type and paste are refused as computer.blocked, before anything is sent,
when focus sits in a modal on an element that takes no text: a letter there
can be the dialog's mnemonic. Typing into a field of a modal (a file name
in a save dialog) still goes in;
- a ref or point step on an element in a window the application has disabled
is refused as computer.blocked, naming the modal that holds it;
- key and hold_key still go (Enter and Escape answer a dialog) and say which
modal they went to;
- type, paste and set_value read the focused element's value before and
after: confirmed when what was typed is in, suspected_noop when nothing
changed, unverifiable when it cannot be read (no value, a password, or a
value too long to compare). Values stay in the helper; set_value no longer
sends its read-back across the protocol;
- a snapshot lists the modals, and marks the modal's line.
The fixture gains a message box mode and an owned form with a text field;
the live tests run in the logged-in desktop session.
A computer_act step that the helper accepted read to the model as done,
whether or not anything reached the target. On Windows, typing into Notepad
while an error dialog held its input returned Done:1 and the text went
nowhere, and the dialog showed in the tree only as an unnamed window.
The helper protocol gains three optional fields, all read from structure:
- a step's result may carry an effect: a closed class (confirmed,
unverifiable, suspected_noop), the evidence kinds it rests on
(value_readback, value_unchanged), and the modal its input went to;
- a failure may carry blocked_by, with the new code computer.blocked for
input a modal would swallow;
- a snapshot may list modals: ref, title, and the window each one holds.
The kernel owns the grading contract: a class without the evidence that
proves it, an unknown class, or no effect at all (an older helper, or the
macOS helper until it reports one) settles as unverifiable. A step that
sends nothing, such as wait, carries no effect. computer_act says each
step's effect after its note and names the modal in a blocked failure; a
snapshot opens with the modals holding the application's input.
Tool schemas and descriptions are unchanged, so the cache-stable prefix is
byte-identical. Approval is untouched: grading only adds information, and the
new refusal only sends less.
The replacement now keeps the size and restores the mtime, so only the digest
comparison can tell it apart; it fails with the digest taken after the probe.
Refs #11434
The digest recorded for a persisted proof is taken before the launch, so an
executable swapped during the probe is not vouched for. The Probe field comment
now says the memo is private while proofs still persist.
Refs #11434
A failure with no sentinel on the read side (missing object, bad content
reference, size or digest mismatch) was reported as a failed copy. Reads now
default to corrupt, writes are classified separately, a folder without a
manifest is not listed as a session, and each copy button names its session.
Bash discovery launched each Git Bash candidate serially (10 s each), then a
PowerShell check, and remembered the proof only in process, so every cold start
repeated it in front of the Studio handshake. Candidates are now probed with a
hedge, the search shares one budget and falls back with probe_timeout, and a
successful launch is persisted keyed by path, size, mtime and content digest
(treated as a miss on any doubt). Boot gains shell, prompt and environment
phase marks so the start-up trace names every stretch.
Refs #11434
The free-text box of a question card stayed display:none until Other or a
single pick was chosen, so a card with only fixed options showed nothing
typeable while the run line read "Waiting on you" and the open plan kept
turning its in-progress arc.
The box is now always present on a live card and typing into it is an
Other answer, so there is one control for the free-text path. The run line
names the wait ("Waiting for your answer") and the plan stops turning its
arc while a decision is pending.
The legacy import dropped unreadable sessions behind event.Discard and
returned only a warning count. The migration layer now produces one skip
record per session (name, source path, reason code taken from the reader's
sentinel errors), the import response carries them, and the storage panel
lists them with the fact that the originals are untouched.
Problem: The browser contrast guard timed out opening Settings after the
duplicate sidebar footer was omitted beside visible icon navigation.
Root cause: The icon rail's Settings and Account controls did not carry the
chrome.settings and chrome.account identities declared for those actions.
Fix: Declare each footer action identity alongside its icon, label and
destination, and render it on the rail button. Extend the interaction test to
verify those identities and destinations through both footer locations.
Verification: The new identity assertion failed on the previous rail and
passed after the fix. All 36 related interaction tests, production and perf
builds, ui-census invariants and 10 fixture reports passed. The CI browser
guards (intrinsic, contrast and wheel) plus perf/side.mjs passed. Formatting,
go vet, make lint, make check, builtin/boot Go tests and diff checks passed.
A rule such as ask=["rm"] parses but its tool name matches no tool, so it never
fires and the user believes a boundary exists that does not. Saving a rule whose
tool no registered tool (or MCP/extension namespace) can match is now refused
with permissions.rule_unknown_tool; rules already in the file are kept, listed
as dormant in the permissions panel, and announced once at load with a typed
permission_rules_dormant notice.
Store.roots canonicalised every directory up to three times per call, and
BuiltinSubagentTools ran a full discovery pass for each of the five built-in
skills it checks. On Windows each canonicalisation is a chain of directory
lookups, so every boot.Build paid for around 120 of them in this one step.
Refs #12521
resolveTurnImages and withVisionRouting loaded the full project config on
every turn to decide how to route images, including turns with no image at
all, where neither answer is used.
Refs #12521
internal/assembly/boot took 478-717s on the Windows runner across 12 push runs, so the 12m per-package timeout panicked. The full step now tees go test -json and, on failure, prints the failed tests' output and the 15 slowest boot tests.
tool.after can report a finished write as failed, and a move can stop
half-done, so a failure is not proof that nothing was written. Non-prose
targets of a failed named-path call now keep the generic check owed. The
declared-checks cause also covers checks captured at task start.
Refs #12519
The crash files are the runtime's raw output and are not redacted, only
shell.log and host.log are. The notice now follows the same language the
page draws in instead of the machine locale.
A Studio kernel that died of a runtime fatal left no readable trace: its
stderr sat in host.log, which request logging rotated away every few
minutes, and the shell quit without recording how the kernel ended.
Routes that are polled declare themselves with routine(); a successful
request to one logs at Debug, a refused one stays at INFO. The kernel
mirrors runtime crash output into a bounded crash directory the shell
names, the shell quotes the tail into shell.log and the next launch tells
the person once.
Failed non-named-path mutations leave the extent of the change unestablished,
so the prose-only waiver no longer applies. The ask now names an unestablished
extent and declared checks as causes, spells paths from the workspace root, and
the trace fixtures are regenerated.
Refs #12519
The waiver read the whole workspace scan, so a README edit in a repository
with any code was still held to a verification command. It now reads the paths
the task changed: all prose (.md/.rst), scope established, no declared check.
The ask also names the changed paths that keep it owed.
Refs #12519
Git before 2.31 echoes the unknown option as an extra output line, which
Open reported as not-a-repository. Normalise the git dir, common dir and
work tree in Go instead, and do the same for the index path lookup.
The refusal echoed the command verbatim, so a long or multi-line command
could flood the message or forge a Cause line. The subject now goes through
textutil.BoundLiteral. A compound statement whose subcommand is dynamic
(git $X; ls) is attributed to shell expansion instead of leaking the
internal placeholder as an unknown program. The declared-writer cause no
longer claims a tool declared that it changes state.
Refs #12508
The install preview always shows every high-risk action, and an entry
with "tier": "eager" is high risk. A marketplace is capped at 64 plugins
and a skill repository at 200 skills, but a .mcp.json had no cap: a
package could ship tens of thousands of eager entries, bounded only by
the 2 MB remote read, and every one of them would be displayed.
parseMCPJSON, which planning runs for both a local and a fetched
.mcp.json, now refuses a file declaring more than maxMCPJSONServers (64,
the marketplace cap) with ErrInvalidManifest, before any entry is turned
into an action. The refusal is a hostFactError, so the caller reads the
declared count and the limit rather than an omitted detail.
With kind=mcp, a fetched URL whose path names a .mcp.json now returns
its parse error, as a local .mcp.json already did, instead of
ErrUnsupportedKind; the over-limit refusal otherwise never reached a
remote caller. Any other document fetched with kind=mcp, such as a
SKILL.md, still answers ErrUnsupportedKind.
Refs #12488
The planning gate recorded every call it could not prove read-only as a
side effect and told the model it "would change state", with no command,
no cause and no alternative. The effect is now Unclassified when the host
only failed to prove a read, SideEffect when a tool declares itself a
writer or an argument makes a reader write, and the refusal carries the
classifier's structural proof (unknown program, shell construct from the
parser, writing arguments, background, undeclared tool). The marker now
defines read-only shell in the same terms.
Refs #12508
A pack could declare recipes.density and recipes.corners, the schema
published both tiers and eight of the nine shipped packs carried them,
while nothing in the tree read either one. A pack asking for compact
density loaded clean, warned about nothing, and did nothing.
The reader keeps the two tiers now and reports a value outside them the
way it reports a token that fails its grammar, so an author sees the
problem in the picker rather than a pack that stays default in silence.
A recipe is a factor, not a size: the stylesheet owns what a radius or a
gutter measures, and a pack only says how far it steps. corners writes
--r-scale and density --d-scale, and each declaration that takes one keeps
its own value as the default, so a window with no pack on it is laid out
at exactly the numbers tokens.css writes.
Recipes is a hand-written wire mirror like the pack's other blocks, so it
joins the pairs declared in tools/repolint/wireparity.go: a field added on
one side alone then fails the gate at the line that cannot read it.
1.x suspended to the shell on Ctrl+Z, as terminal programs do; 2.x left
the key unbound, so it did nothing and there was no way to drop to the
shell without quitting.
Ctrl+Z now returns tea.Suspend from shortcutKey. Bubble Tea releases the
terminal (alternate screen, mouse tracking, bracketed paste) before
stopping the process and restores and repaints it on fg, so nothing of
1.x's manual mouse reset is needed. On Windows Bubble Tea has no job
control and ignores the request. GUIDE.md and its Chinese version list
the key.
Refs #12144
After scrolling back through the transcript, Enter on an empty composer
did nothing; getting back to the live output took Ctrl+End or the wheel.
1.x jumped to the bottom and followed the tail again on an empty Enter,
idle or while a turn ran.
An empty (or blank) Enter now does the same in full-screen mode, through
followTail, which Ctrl+End also uses. It still sends nothing. Inline mode
has no in-app view to move and is unchanged. GUIDE.md and its Chinese
version note the behaviour on the Enter row.
Refs #12144
Layout spacing carried 2364 distances across 48 distinct values, 1046 of them
on no scale at all, 10px 220 times and 6px 214 times. A pixel is not a level,
and those two are what a six-step scale could not hold: they are the values a
migration would have had to move 63% of the file's distances to reach. Eight
steps leave 44% off scale instead.
The tie rule is arithmetic rather than taste. 3, 5, 7, 9, 11, 14 and 20 each
sit exactly between two steps, and each sits above the step below it, so a tie
returns to that step and gives up only the air that was added by hand.
The scale is declared here; one surface draws from it. The activity group's
fold header takes --sp-6 for its inset and --sp-4 between the mark and its
label, and both were already those numbers: the two new steps renumbered the
ones below 12px, so the surface was renumbered with them and nothing moves.
TOKENS.md carries the scale, the step each role takes, and the tie table. Its
Space items were rewritten to fit the file's prose budget, which the first
draft of this change went one unit over.
A reply in Persian or Arabic was laid out left to right, and a sentence that
mixes in Latin words such as npm or a variable name came out jumbled, because
every block inherited the window's LTR base direction.
Markdown paragraphs, headings, lists, blockquotes and table cells now carry
dir="auto", so the browser takes each block's base direction from its own
first strong character: an RTL paragraph reads right to left and aligns to
the right beside English ones, and the bidi algorithm orders embedded Latin
runs correctly. Code blocks are left out and keep the document's direction.
The user's own message row and its edit box get the same treatment.
List indentation and the blockquote rule use logical properties
(padding-inline-start, border-inline-start), so they move to the right edge
for an RTL block and are unchanged for LTR.
Fixes#12114
Swapping in this build's system prompt after a model switch made the
transcript differ from the 1.x log, so the save that follows moved the
conversation to a new session. The system prompt is host contract, not
conversation; the unchanged check now leaves it out.
Refs #12474
The list scrolls inside a height the composer's aux area caps, so an editor
grown to its text could be taller than the list and get cut off at an edge.
The fit hook now takes the room left by the list, and the edited row is
scrolled into view. The browser check asserts the whole row sits inside the
list's visible area at desktop and phone width.
Refs #12477
The effort field is now resolved from the entry's reasoning protocol rather
than its kind, so an auto entry that resolves to a bespoke shape (Zhipu,
MiniMax, LongCat, DeepSeek) no longer claims reasoning_effort. The catalog
carries the protocols the field holds under and the panels read it instead of
keeping a copy. Adds the Anthropic none case and an end-to-end check that
existing effort-only configs keep their request bytes.
The harness measures context cost and could not report its own. It handed the
agent event.Discard, so the usage every billed completion already reports went
nowhere, and a batch's spend could only be read from a vendor console: this run's
index batch cost 604,544 prompt tokens (527,232 cached) plus 12,600 completion
tokens over 24 runs, and nothing in the tree would have said so.
The bench now installs a sink and totals what the agent reports — the same record
the product's own round path, compaction, command routing and failure paths bill
to. One path, not a second count of the same fact.
Two things stay honest about what the number means. A dry run prints an empty
suffix rather than "prompt-tok=0", because an unmeasured cost must not read as a
measured one. And the count is called usage_reported_requests: a request that
failed before reporting usage leaves no event, so it is not a count of attempts.
The package doc still said a cut of 50 actions sets previewTruncated, and the
field comment said escaped text sets it. The test-only PreparePlugin option
is marked as such.
The substrate the Status section asked for now has a result, and the result has a
direction, a size that is not yet quotable, and a reason.
The direction reproduces: six of six tasks, and the pooled paired batch searches
less and pages in less with the cue visible. A size is visible on the four tasks
whose absent-cue arm stayed out of a loop (44% less prompt cost), and it is
recorded as a post-hoc subset rather than an effect size: the two excluded tasks
are excluded because their arm looped here, and the qualification bar does not
screen for that. Saying 44% without that qualifier would mean picking the sample
after seeing the result, which is the mistake that invalidated the previous round.
The counts are stated separately because they differ: 36 runs executed, and the
token totals belong to the 24-run index batch that carried the usage line.
#12389 and #12035 each added the tsLookFile constant and an appearanceView mirror; the two merged cleanly as text and left tools/repolint unable to compile (tsLookFile redeclared). Keep one constant and one appearanceView entry, plus the zoomRangeView entry #12035 added.
PreviewTruncated also fired when only whole steps were left out, so the
confirmation said text had been shortened when none had. It now means that
text was cut or escaped, and HiddenActions carries the step count. The card
no longer repeats the plan-level sentence; it shows a short marker aligned
with the other rows.
The protocol table now declares where a chosen effort level lands in the
request body (reasoning_effort, reasoning.effort, output_config.effort). The
protocol catalog and the provider list carry it, and the add, detail and edit
panels word their help and example body from it instead of naming the Chat
field for every wire.
The Responses wire now takes the same thinking switch, reasoning_protocol and
declared levels as Chat in all three panels and in the edit endpoints.
reasoning_protocol=none is honoured when resolving the effective effort, and
a declared openai protocol lists its own ladder as the request vocabulary.
The shrunken-viewport case is renamed to say it checks the box stays within
60% of a shorter viewport with the caret at the end. The queue editor check
now compares against its computed cap and the room the list leaves. The
editor's own max-height was dead (the composer's textarea rule wins), so it
is removed again.
prefer=powershell tries Windows PowerShell 5.1 before pwsh, so advising it to a pwsh 7 user would trade away && chains. Advise prefer=pwsh when the interpreter in use chains.
Auto-detection never looked at where Git for Windows records its own install, so a Git outside the standard roots whose git is not on PATH was invisible and the session silently ran PowerShell. Add that registry location as the last bash candidate source, make a failed probe say whether it timed out, give the probe 10s instead of 3s for a cold start, and write the fallback warning that the startup notice already carries.
The theme category check had no test that reached ErrNotTheme. A marketplace
of fifty theme packages plus one with a hook must be refused and keep the
hook step visible in the envelope; sixty pure themes must pass. A table
sets each count field and the runtime alone, so dropping any term from the
sum is caught. install_source gains a test-only PreparePlugin option so the
market layer can plan a marketplace without a network.
The theme gate read the bounded preview, which lists at most 50 steps while
a marketplace may hold 64 plugins and a skill repository 200 skills, so a
hook or runtime past the cut skipped the check and the confirmation. The gate
now reads the unprojected actions; the preview keeps every high-risk step,
caps the rest, and reports how many it left out; the real skill count is
shown. Package inventory returns the package's own text again, and the
display projection is a separate call.
The FULL TRUST reason embedded the runtime command inside a sentence that was
then stripped as prose, so an unterminated escape sequence in an argument
swallowed the warning. The command is now rendered literally after the host
sentence. BoundProse reports removal as well as truncation, BoundLiteral
cannot be mimicked by typed escapes, and colliding display keys are flagged.
Stack on the refreshed read-only branch chip. Guard checkout atomically against all active panes and unfinished jobs in this host, preserve queued input cancellation, honor existing writer leases, and protect ignored local files including late-created collisions.
Problem: Usage, feedback, account/workspace and settings appeared twice when
the global icon navigation and workspace sidebar were both visible.
Root cause: Sidebar rendered its footer unconditionally, independent of the
navigation visibility already owned by App.
Fix: Pass the existing navigation visibility into Sidebar and render the
footer only when navigation is hidden. Preserve the fallback entries in off,
collapsed-only and narrow-window modes, plus existing feedback and settings
routing. Update layout/parity docs, pending release notes and fixture images.
Verification: 36 relevant frontend interaction tests, 11 release-note/link
tests and the frontend production build passed. Formatting, go vet, make lint,
make check, builtin/boot Go tests and diff whitespace checks passed. Browser
fixtures cover light/dark wide windows and narrow drawers.
The queued-line editor took its height from the number of line breaks, the
same defect as the rewrite box, so it now uses the shared fit-to-content hook.
Both height caps use dvh (with a vh fallback on the same line) so a soft
keyboard shrinking the viewport shrinks the cap. The browser check gains the
queue editor and a shrunken-viewport case; the harness can read a seeded
queue line back.
A failed job's notice now carries the same JobNotice payload as finished and
killed ones, plus the job function's own error text, so a frontend words
it from the label or id and shows the raw error beneath. A failure stored
without a payload still renders its text. Adds an emission test for the
killed notice.
Rebase the read-only branch chip onto the current Pane split. Keep the stylesheet within budget, register the complete workspace Git wire contract, and verify refresh after turn completion.
The rewrite box took its height from the number of line breaks, so a long
paragraph written as one line showed about 60px of it. The composer's
fit-to-content logic is now a shared hook used by both, with the box capped
at 60vh. A browser check covers a long single line at desktop and phone
width in both themes.
The completion notice carried only English text with a numeric id, so a
frontend could neither localize it nor name the job. Finished and killed
notices now carry a code and a JobNotice payload (kind, id, label); a
failure carries a code and keeps its diagnostic. Studio and the terminal
UI word them from the code; uncoded notices from older sessions still
render their stored text.
The folder mark beside the project name is decoration: it carries no click and
no meaning. A project can already be shown in the file manager, but showing it
reveals it in its parent folder; entering the folder itself needs a second
step.
This adds that step and wires it to the mark. The kernel answer and its refusal
are the ones reveal already asks for (/host/workspaces/locate), so the page
still names no location of its own and only the shell verb differs: openPath,
called on a folder the kernel itself named, instead of showItemInFolder. The
mark stays disabled when the shell cannot open a folder, or when no pane names
a workspace.
A text-less compaction_progress frame now tells a frontend the streamed
digest was discarded, so a retried summary does not append to the cut one.
The pressure effect test counts attempts as pairs with a larger retry cap
instead of capping raw request count. Adds a shared-window room test and
updates SPEC.md.
The summary request is capped at 16K output tokens and reasoning shares that
cap, so a model with no low reasoning tier can spend all of it and have the
digest discarded. Truncation is already a typed finish reason, so the host
retries once with the cap doubled (bounded by the model's configured output
ceiling) and fails with the cap and retry recorded when that is cut too.
The rail clipped itself to its animated width, which cut off the hints that
hang outside it, and shared a z-index with the workspace rail that paints
later. The rail now keeps its open width and slides off screen instead, and
sits one layer above the workspace rail.
Refs #12462
Problem: Default light Studio uses nearly identical frame and reading surfaces,
medium-weight prose, and a broad composer shadow, making long answers feel dense.
Root cause: Reading panes share the page token, replies inherit the small-UI
weight, and the composer uses the generic light surface and shadow recipe.
Fix: Separate the default light reading and composer surfaces, use regular
weight for default reply prose, and shorten the composer shadow. Preserve
explicit weight choices, installed theme packs, wallpaper surface overrides,
dark styling, and layout. Consolidate the duplicate light palette and document
the tokens. Include mock-fixture before/after screenshots at both viewport sizes.
Verification: 68 focused frontend tests; TypeScript/Vite build; browser theme,
weight, wallpaper, draft-input, and geometry checks; gofmt; go vet ./...;
make lint; make check; go test ./internal/tools/builtin/ ./internal/assembly/boot/.
Only the walk skips VCS store entries. An explicit write into a VCS store is a
change like any other. The snapshot-store exclusion is a path convention for
de-noising, so a symlink anywhere on the way disables it.
The unverified-change gap carried a sentence the kernel wrote in English, so
every language showed it; it now carries only the declined command. Changes the
host or a tool owns -- the task-snapshot store, VCS stores (including a .git
file), and directories tagged with CACHEDIR.TAG -- no longer count as work.
Refs #12457
The tests that fake Windows name probing on every OS spelled PATHEXT in
upper case while their fixture files are lower case. Windows file lookups
ignore case, so the real lookup finds mcp.cmd for .CMD; a Linux filesystem
does not, so under the fake the probed name missed the fixture and the
tests failed there. The seam fakes the probing, not the filesystem: spell
the extensions as the fixtures are.
Adds the off-Windows counterpart: where nothing probes extensions, a .cmd
beside the command does not affect the binding, and the command file does.
The shell exposed no way to leave the GPU out of it. Chromium fixes its GPU
use before the app is ready, so a saved choice (rx-hw-accel, in the existing
window-prefs.json) is read right after the profile directory is set and
before ready; the shell reports what this launch applied and what Chromium
ended with through a new shell:graphics handle. Appearance gets a Graphics
block, shown only in the desktop shell, saying which mode this launch uses
and that a change applies after a restart.
A fixed entry is upgraded from next to a concrete version only when a merged
closing pull request is contained in a studio tag whose release page is neither
a draft nor a prerelease. Prerelease status is read from the releases API, not
from a version cutoff.
A pack with a sky drew a blurred cloud bank under six backdrop-filter panels,
so every frame re-blurred 41% of the window and the idle window held a GPU
engine near full on integrated graphics. Frosted panels that are already
72-94% opaque blurred nothing visible, and the grain layer forced a
full-window overlay blend on every frame.
- Sky: CSS breathing animations removed; the canvas redraws on a 15 fps
timer and only while the window is shown and focused (presence.ts).
- backdrop-filter kept on the composer and the transcript backing only;
the other panels get a higher flat tint instead.
- Grain drops mix-blend-mode.
- New Appearance setting, Visual effects: Full / Reduced (rx-effects,
immediate, per machine). Reduced removes remaining blur, text glow, the
grain and the sky.
- perf/effects.mjs guards all of it in a real browser.
Clicking .html/.htm in the workbench explorer shows the file as a page in the
shared browser through the existing disk-path open, reloading when the file
changes on disk. Windows that draw no browser views and remote workspaces keep
the source editor.
The model menu now always opens with a search field, groups models under a
sticky provider heading with a letter chip, uses compact one-line rows and
grows with the window (up to 640px) instead of a short fixed box. Rows keep
the wire format and show a reads-images badge where the model declares it.
Enter no longer lands on the manage action, and an empty search says so.
An enable recorded for a server that .mcp.json or a project reasonix.toml
declares held for the server's name. The row now records a digest of the
declaration as written (type, command, args, env, url, headers), the project
.env values it expands, and the content of the workspace files it names:
its arguments, and every file the launcher could start for its command,
found by the launcher's own lookup (base/proc): the declaration's PATH for a
bare name (every Windows spelling of it, in key order), and PATHEXT
siblings on Windows, dotted names included. A different declaration reads
as ActivationChanged: off and awaiting the user. Rows written before this
carry no digest and ask once.
- Specs for a repository-declared server carry a LaunchCheck run before
every start and redial; env overrides merge in key order.
- Why a server waits is a typed code (awaiting_user_decision,
changed_since_enabled) on MCPHealth, the serve and ACP wires and the TUI;
Studio and the TUI word it in the reader's language; Studio shows the
launch line beside the switch.
- A connect or reconnect by name refuses a repository server in any pending
state with ErrMCPApprovalOwed (serve: mcp.approval_owed) and its launch
line; a server from the user's own files is still answered by a connect.
- Build reports each held project server once, so `reasonix run` prints
one warning line per server; serve prints the same at startup; /mcp and
`reasonix mcp list` show them. Repository-chosen names are sanitised
wherever they are printed, and run diagnostics sanitise every line.
- `reasonix mcp enable` prints the approved launch line; a project install
records the decision for the declaration as written; doctor --live
starts only what a session would.
Every test for the project MCP approval change, ahead of the change:
- boot effect tests through the real assembly and the settings switch: a
rewritten declaration, a launcher changed mid-session and an executable
swapped behind the declaration's own PATH must not start on an earlier
enable; held servers are reported once each at build; a repository's
server name reaches no notice as control sequences.
- config: the enable covers the declaration, the .env values it expands,
the files it names, the executable the launcher resolves (declared PATH,
every spelling of PATH, PATHEXT siblings including dotted names), legacy
rows ask again; base/proc holds the launcher's lookup.
- plugin: the launch check runs before start and redial; env merges in a
fixed order.
- control/cli/tui/frontend: a connect by name never approves a repository
server that waits; why it waits is a typed code on every surface; /mcp,
`reasonix mcp list`, serve's startup warning, run diagnostics and the TUI
panel print a hostile name as plain text; doctor --live starts only what
a session would.
The form moved focus from the tab into the body in a frame callback queued from the environment promise. The fieldset is disabled until that environment is committed, so a frame that fired before the commit called focus() on a disabled textarea and focus stayed on the tab. The move now runs in an effect on the environment, after the commit.
The unread interaction tests emitted wire events through a listener map that was only filled once the pane subscribed, and a missing listener was a silent no-op. The first test of the turn-ending group waited for the port to exist, which is earlier than the subscription, so an event could be dropped under load. emit now waits for the listener.
docs/GUIDE.md lists Ctrl+Enter (or /steer) as the key that adds guidance
to the active turn, and 1.x bound it so. 2.x bound only Ctrl+S, so on a
terminal that reports Ctrl+Enter the documented key did nothing.
While a turn runs, Ctrl+Enter now sends the composer as steer through the
same path as Ctrl+S. Idle it does nothing, as in 1.x, so a press meant as
a newline never submits the draft. Ctrl+S is unchanged.
Refs #12144
With in-app mouse capture on, a right-click copied an active transcript
selection and otherwise did nothing. 1.x pasted the clipboard's text on
a right-click with no selection, and docs/GUIDE.md and docs/CLI.md say
2.x does too, so the documented paste never happened.
A right-click with no selection now pastes through the same path as
Shift+Insert: text only, through the normal paste handling, with the
existing hint over SSH. It pastes only while the composer is drawn; the
condition that draws it moves into composerShown so the view and the
mouse read one answer. A right-click on a selection still copies it.
Refs #12144
Up and Down recalled history only while the composer held a single line.
Once a recalled entry had several lines, Up moved the caret to its first
line and then stopped: nothing older could be reached, and a multi-line
draft could not step into the history at all.
Up now recalls from the composer's first line and Down from its last, as
1.x did; between them the keys move the caret as before. A single line,
soft-wrapped or not, behaves exactly as it did.
Refs #12144
Cause: 30 days of Studio UI changes landed without screenshots or review, and a visible control was hidden without a linked issue or a release note.
The PR template had no UI requirement and no table tracked 1.x features.
Refs #12427. The rail moves left of the sidebar, gains the feedback entry, and the Appearance setting becomes always / only when the sidebar is collapsed / never. Stored on and off values keep their meaning.
The /mcp panel opened a server's tools with Enter and backed out with
Esc only. 1.x's MCP manager also went in with l / Right, backed out one
level with h / Left, and closed from anywhere with q, and docs/GUIDE.md
(both languages) still lists those keys, so pressing them did nothing.
The panel now takes l and Right as Enter, h and Left as Esc (back to the
list, or closed from the list), and q closes it from the list or a
server's tools. The repository-declared confirm line is unchanged: any
key but y still cancels it.
Refs #12144
The ask card took only the arrows: Up/Down (and Tab) to move between
rows, Left/Right to move between questions. 1.x's question card also
took j/k and h/l, and docs/GUIDE.md (both languages) lists them for it,
so pressing them on 2.x did nothing.
The card now takes k as Up, j as Down, h as Left and l as Right, with the
existing wrap, clamp and auto-submit stop. A typed answer or note is
unaffected: the composer gets those keys as text before the card does.
Refs #12144
The approval card moved its cursor with the arrows and Tab only. 1.x also
took j/k and Ctrl+N/Ctrl+P there, and docs/CLI.md says approval rows use
the same row-selection keys as the pickers, so pressing them did nothing.
The card now takes k and Ctrl+P as Up and j and Ctrl+N as Down. None of
them is a row shortcut (y/a/p/n/x), so no answer changes meaning.
Refs #12144
A call reached through use_capability only learned its delegation profile after the target finished, so the deck's sub-agent list and the running count missed every sub-agent until it ended. The profile is now announced when execution starts.
A notes response is accepted only when its declared type is markdown or plain
text, so an error page served with 200 is refused and never written. The cache
name is lower-cased with a digest for mixed-case prereleases and over-long
versions are refused. Expanding a row on a narrow window keeps its label and
date. Error wrapping and the wait group follow the linter.
The kernel serves GET /studio/versions/{version}/notes from the release mirror,
keeps each fetched document on disk for good, shares one request between
concurrent readers and refuses by typed code. A row whose catalog entry names
notes gets an inline view with images off.
The notes are optional, so a failed upload or head-object warns and writes the
catalog entry without a pointer instead of failing the Mirror to R2 step.
Studio release workflow uploads studio/notes/<version>.md ahead of the catalog
write and gives the new entry an optional notes field. A one-time script
backfills the entries already in the catalog.
pendingAsk returned an arbitrary entry of the ask map, including one still queued behind the prompt lock. When the second ask took the lock first, the test answered the queued one and then waited for it while the other held the lock for the user: a hang until the package timeout.
useWallet treated the empty account of a pane whose status had not arrived as a source, so the first status turned it into a source change: the wallet read that had already landed was emptied and read again.
Update capped the lock wait at 5s even for a caller that supplied its own deadline, so the number of writers a queue could hold was fixed by the package rather than by the caller. The serve tests now pass a budgeted context and fail on the returned error instead of reading a swallowed one as a wrong count.
The host appends a background-job-finished notice as a user-role message once the job ends. When it landed before the CHECK turn composed, the provider read the notice as the last user message, took its default branch and never issued wait/read, so the answers it recorded for w1 and r1 were empty.
Every config load re-parses each enabled plugin package, and a settings sheet
fires a dozen such loads at once, each queued behind the package store's lock.
With a 293-skill package a burst costs one full parse per request. Callers now
wait for a parse that starts after they arrived and share it, so a burst costs
at most two.
Every type the desktop re-declares by hand is held to the kernel's, and
src/port/look.ts was the one file under port/ that no pair named. The theme
a person is looking at is all of it: the pack listing, the pack's two shape
blocks, the import receipt, and the reader's size and wallpaper.
Declared and not inferred: no spelling tells a mirrored contract from a
struct that merely carries json tags.
The desktop-sessions-v5 by-id store carries no workspace, so every session
from it landed in the workspace picked in the dialog. The owner is recorded
in desktop/workspace-state-v1.json; read it from the picked folder and route
each session to its workspace while that folder still exists.
Pane.tsx sat at the 800-line ceiling with twelve open pull requests editing
it. The view (flow, analysis, browser) lived in component state, out of reach
of the tab strip and of any setting that will place tabs.
The view now lives in state/paneview.ts, keyed by pane id and owned by the
mounted instance, so a remount for another conversation still starts on the
conversation. Submit, surface slots, the meter row, the plan fold and the run
line move to files of their own; props and the report type move to
panetypes.ts. No visual or behavioural change.
Adds css-important, css-size and stray-file rules to repolint, deletes the
stray Settings.tsx-e, runs the intrinsic, contrast and concurrent browser
guards in the studio workflow, and adds a report-only wheel-scroll probe.
The rail was hidden by the studio layer's prototype alignment, which left Nav.tsx rendered but display:none. It is drawn again, beside the workspace column, when this machine has it on (default) and the window is wider than the scene fold.
The BrowserWindow carried no backgroundColor, so any pixel the page does not
cover (frameless window at a fractional display scale) showed the default
white. The ground now follows the stored rx-theme, or the system for auto, at
creation and when preferences are saved or the system theme changes.
The sidebar folds a conversation's recovery copies under one row, but
/tree/sessions/remove deleted only that row's file. The next copy then took
its place, so the delete looked like a no-op with the order shuffled.
Remove the lineage the way archive already addresses it, leaving siblings a
pane has open.
A 400/422 on a request whose history carried reasoning the connection
could not replay was labelled "the endpoint wants the thinking content
sent back", whatever the endpoint actually said. Dropped reasoning is a
fact about the request, not a cause the host observed, and the endpoint's
refusal carries no typed signal separating the two. The hint is removed;
the refusal now shows the status guidance and the endpoint's own words.
Search and list dropped results silently: hits below the relative score
floor (KeepTopRelativeScore compacts in place, taking the count with it)
and everything past limit. A reader could not tell a complete result from
a truncated one, while the empty-result message already warns that no
result is not proof of absence.
Both paths now carry the note automatic recall already uses for its own
omissions, counting both reasons and staying silent when nothing was
dropped. The spelling lives in one helper the recall block calls too, so
the two cannot drift apart; its output is unchanged. The count is one
function as well, so the case where both reasons fire is tested directly
instead of only through fixtures. Empty results keep their guidance: the
relative floor always keeps the top hit, so empty still means nothing
matched.
Refs #11853.
File tools resolved a path through the filesystem before comparing it with the
workspace, so a path spelled as a network share was looked up first. On Windows
that lookup is itself a connection to the machine the path names.
A network path is now answered by its spelling alone, with no filesystem call:
it passes only below a workspace root that is itself a network path, compared
lexically and case-insensitively, and is otherwise refused with
workspace.network_path_outside_scope. The check is one function in fileutil and
sits at the entry of the read tools, the writers, their previews and write-path
resolution, write claims and path observation. Local paths and network
workspaces behave as before.
A path spelled as a network or device path is now answered by its spelling
alone, before any filesystem call, and is refused with its own code
(browser.network_path) instead of the generic outside-workspace one. The
check lives in fileutil next to the containment helpers and covers page
loads and file uploads.
Both boxes submitted on Enter regardless of pointer type; they now ask
touchKeyboard() like the composer does. The queue editor also ignores Enter
while an IME composition is open, as the rewrite box already did.
The anonymous ping was a single attempt at launch with a 1 s total budget; a
failed attempt dropped the day's claim and nothing tried again until the next
launch, and a host left open past UTC midnight was never counted again. The
reporter now retries a failed ping with bounded backoff, waits for the next UTC
midnight (plus a splay) after a success, and stops with its context. Consent is
re-read before every attempt, so turning the setting off stops the next send
without a restart. A refusal the server marks permanent (400/401/403/404/410)
is not retried for the rest of the UTC day; 429, 5xx and network errors back
off. Request timeouts are relaxed since the work is off the launch path. The
payload is unchanged.
The interactive session that carried the first-run consent and the reporter was
removed, and the replacement terminal UI never started either, so CLI 2.x users
were neither asked nor counted. runTUI now runs the documented consent on the
plain terminal before the UI takes the screen, starts the reporter only after a
mode is stored, and wraps the controller sink so the existing bounded counters
flow.
An ask or tool-approval wait kept the session's claim for as long as the user
took to answer, so other sessions were blocked on paths nobody was writing.
The claim is now yielded for the wait and taken again, queueing behind any
session that claimed the extent meanwhile, when the user has answered.
Refs #11531
The address bar read "D:/DevCode/x.html" as host "d" over http. A drive
letter plus a separator, a leading backslash pair or a single leading slash
now names a file: URL, parsed once in Go (browser.LocalPathURL, used by the
agent's checkURL) and once in the window (localpath.js), both held to one
shared table. The window opens local files for the person; a network share
is refused with its own code instead of a DNS error, and a page's own links
still never reach a file.
The summary request is one user message that ends on the last tool output, so
the model sometimes carries on the agent's work (pseudo tool calls, "next I
will ...") and the host installed that reply as the digest: a read-only fold has
no changed file for the coverage guard to compare.
- The user turn now ends with an explicit ask for the briefing.
- A reply with no level-2 heading, the shape the summary prompt requires, fails
with the typed code summary_not_digest. It is billed to the compaction, is
retried like any transient summary failure once the input has grown, and
degrades to the mechanical digest when the fold must free space.
- The code has frontend text in the TUI catalogues and the desktop table.
- Test doubles that answer summary requests now speak the heading contract.
The worker now states a profile on GET /v1/feedback/mine and names the
window in every feedback refusal. The kernel client dropped both: the
profile never reached a frontend and a 429 collapsed to "wait N seconds".
- feedback.Mine carries a validated Profile (null when absent or
incoherent; the last confirmed one while offline, cleared with the
install identity).
- Refusals decode error.params into a typed LimitError (window, reset
time, wait) for rate_limited, reply_limit and busy; serve forwards the
same fields through refuse.
- Studio shows the level row, progress track, limits, trust note and the
badge artwork (single-colour SVG on text tokens) in My feedback, and
says which window a refusal ran out of and when it resets.
- The terminal's /feedback list prints the same standing and refusals,
worded through the i18n catalogue.
Refs #12025
keepToolCallGroup kept every result of a parallel tool-call group when any one
failed. Models batch 4-11 calls per turn, so one failing test pinned the whole
batch of reads, the candidate exceeded the 50% checkpoint ceiling, the billed
summary was discarded, and the non-transient block was never retried.
- The retained assistant turn now issues only the calls whose results stay; the
folded copy carries the rest, so the summarizer and the coverage guard still
see them and no request issues a call nothing answers. Turns whose call ids
cannot be told apart stay whole.
- When the retained failures alone would exceed the ceiling, the fold gives up
retaining them before the summary is requested; the coverage guard still makes
the digest name each failed command.
- A candidate_above_ceiling block is released by input growth below the physical
ceiling, and an overflow on a changed input may fold, instead of holding
forever.
The Settings subagent row writes subagent_model, while an entry the CLI
writes under subagent_models is read first for its profile, so the row
looked inert. The kernel now lists those entries per role with the file
that holds them, the row names each one, and a user-config entry can be
cleared from the row.
Refs #12151
A brokered pane takes its default from the home machine's catalogue, so a
default_model written next to the pane's own kernel was never read and the
row snapped back. The default is now its own request, POST /default-model,
sent to this machine's pane; the pane's switch stays a plain /model. A
brokered pane refuses the request by code instead of ignoring it.
Refs #12043
agent.subagent_effort was validated only against the parent model and then
applied to whichever model a subagent selected (subagent_models.task, a
skill's own model, a per-profile model, a per-call model) through
NormalizeEffort, which remaps across providers or lets an unsupported
level through. The default is now resolved per executing model by one
function; a level that model does not declare is dropped with a warning and
the model keeps its own default. Explicit efforts still go through
NormalizeEffort, whose refusal now satisfies errors.Is(err,
config.ErrEffortUnsupported) and names the model and level.
The ACP-specific subagent resolver and task defaults had no caller (ACP
sessions are built by boot.Build), so they are removed.
Refs #11871, Refs #11872
The notice appended to bash output when a command mentions the session store
read like a refusal: it said to stop retrying and report, and when the command
matched nothing it was the only text returned. A model that had just been told
to read a spilled output gave the output up. State that the command was not
blocked and name read_file/grep for spilled outputs.
Cancel had no bounded wait, so a stop the kernel never acknowledged left the
button on "stopping" forever, and stopping only cleared when the running flag
dropped. Cancel now shares the bounded acknowledged post with decisions, which
fails with a typed DeliveryError (kernel_busy, unreachable, ui_stalled). A
deadline that fires far past its time on the monotonic clock is reported as
this window being stalled instead of a busy kernel. A stop also ends when the
kernel's status reports no live turn.
Refs #12232
The shell held nothing against system suspension, so a laptop that went to
sleep overnight froze every running session. The kernel now answers how many
panes have a turn in flight (GET /tray/running, local and remote, read from each
controller), and the Electron shell holds a prevent-app-suspension block for as
long as that is non-zero, reconciling against the answer on every poll so a
closed pane or a dead kernel cannot leave it held. A machine-level switch in
Settings > Appearance > Window turns it off.
Build windows/arm64 on a native windows-11-arm runner and carry it through the
signing chain as a second leg of every Windows stage: one tree per job, legs
serial under the certum-signing group, a failed leg reruns alone. The package
check learns app-arm64.7z and refuses any PE image that is not the leg's
architecture, a new install smoke runs each installer on its own architecture
before publish, and publish counts, names and hash-checks both architectures.
The arm64 installer is packed with the 7-Zip BCJ filter: the default ARM64
filter yields an archive the NSIS extractor cannot decode, and the installer
then exits 0 with every executable missing. Signing contract tests pin the
matrix, the serial legs and the secret-free stages.
Add a windows-11-arm leg to the Studio package job: the kernel and the
computer-use helper build for the runner's architecture, every PE image in the
bundle must carry the package's machine type (the installer-written
elevate.exe and uninstaller are 32-bit by construction), and the package is
started, installed and uninstalled where it is meant to run. An arm64
installer now refuses a machine that is not ARM64 Windows instead of
installing nothing, and a second job checks that refusal on an x64 runner.
The arm64 installer is built with the 7-Zip BCJ filter: 7-Zip applies an
ARM64 filter to ARM64 executables by default, the NSIS extractor cannot decode
it, and the installer then exits 0 with every executable missing.
The ARM64 leg runs on pull requests only when files it verifies change.
Build and verification only: no secrets, nothing is uploaded to a release.
With no browser tab, following a link asked the kernel for the link's tab
and the column, mounting at the same moment, asked for an about:blank of its
own. Which of the two the selection rules landed on depended on reply and
poll order, so the blank page was sometimes the one shown.
openPage remembers what is being opened until the kernel answers, and the
empty column takes that page instead of requesting another.
Refs #12251
The sub-agent chip summed every delegate in the session, finished ones
included, while the background-jobs chip counted only running jobs (and
fell back to the total when none ran). Both chips now show what is
running; the sub-agent popover reports "N running / M total" and keeps
the finished ones listed.
An optional extension whose sidecar is not running was reported as an
English sentence with no code, which the composer's notice bar clipped
at its edge and which named no next step.
dispatch now exposes ErrNoLiveSidecar and a SidecarDown hook; boot turns
it into a notice coded extension_skipped whose Detail is a typed payload
(extension, point, reason). The desktop words it from the payload with a
button to Tools and integrations and lets the bar wrap, the TUI words it
from the i18n catalog, and ACP chunks carry the code and payload.
Refs #12258
The form card's submit awaits onAnswer and clears its in-flight flag in a
finally block. The tests resolved that answer on a 20ms timer they never
waited for, so the setState could run after the jsdom environment was torn
down and vitest exited non-zero on 'window is not defined'. The tests now
wait for the answer to settle, and an afterEach fails any test that leaves
one in flight.
An entry parked as uncertain with code steer_unapplied (guidance the turn
never read, e.g. after a stop) kept the queue paused, and the panel offered
Retry only for blocked entries, so Resume was a no-op and the only way out
was withdrawing and resubmitting. Other uncertain codes may already have
run and stay take-back-only.
Rename the model-section action to "Read available models from provider" and
offer the same action beside the add-model field, with the failure shown there.
Light --warn is 4.58:1 on white and 3.86:1 on the floating layer, under the
4.5 text line. Text rules that set color to a bare --warn, --ok or --err now
use the matching ink token; borders, washes and glyph marks keep the base.
A structural test fails a new bare hue used as text.
useWallet now takes the account (connection) the wallet belongs to: a change
of account empties the wallet at once and reads again, and only the latest
read may land, so a quick double switch cannot leave the first account's
number up. Switching models within one account keeps the wallet and does not
re-read. An account with no wallet (204) or an unreadable one shows nothing
instead of the previous figure. A reading the source reports as unavailable
is drawn in the warn-ink token and carries a text label; the figure itself is
untouched.
Refs #12266
The updater and studio-manifest already key packages by GOOS-GOARCH; nothing
pinned what happens around a second Windows architecture. Cover both
installers resolving to their own keys, a manifest without the client's key
resolving nothing, a delta for another architecture meaning no delta here,
and document the platform-key rules in the release runbook.
TestEffectVersionControlRidesTheTurnNotThePrefix builds two controllers and
requires byte-identical system messages, but the environment section in that
prefix comes from the process-wide probe cache, keyed on PATH and shared with
every earlier test. The boot package runs longer than the cache's five-minute
TTL on Windows CI, so an answer an earlier test left can expire between the
two arms: the first arm reads it, the second re-probes, and a tool that timed
out once (a cold docker --version) appears in only one prefix.
The test now appends an empty directory no other test uses to PATH, which
keys a fresh probe answer for the first arm that the second reuses seconds
later. PATH lookup order and what each probe resolves to are unchanged.
A second TUI attached to a session kept an ask or approval open after the
turn ended without a decision receipt (the other screen skipped the ask or
cancelled the turn), blocking its input on a prompt the kernel had already
dropped. The desktop already seals these in sealTurn; the TUI transcript now
does the same on turn_done.
Xiaomi lists mimo-v2.6-pro, mimo-v2.6-flash and mimo-v2.6-pro-ultraspeed, and takes mimo-v2.5-pro and mimo-v2.5 offline at 2026-10-21 10:00 Beijing time with no replacement model.
A PreToolUse/UserPromptSubmit hook that could not be evaluated used to be
treated as absent: a malformed matcher never fired, a spawn error was
DecisionError (warn-level), and a payload json.Marshal error was dropped so
the hook ran against empty stdin and passed.
On gating events (and Claude-imported PermissionRequest) each of the three
now blocks, with a typed cause (ErrInvalidMatcher, ErrSpawnFailed,
ErrPayloadUnserializable) carried on Outcome and Notice. The model gets only
structured facts (code, step, event, scope, source, label), never the
script's output or the underlying error prose. Observing events still do not
block; they report the outcome to the user. Save rejects an invalid matcher
on tool-matcher events.
The tests that pinned the old behaviour (malformed regex "should not fire",
spawn-error => DecisionError on PreToolUse) are rewritten to the new contract.
101f2ca12 stopped composing the fact index into the cached prefix, but
SESSION_MEMORY_RETRIEVAL.md and REASONIX.md still said it joins the prefix
at session start. Only pinned bodies and standing instructions do; the
index is reached through the memory tool.
A cache hit never wrote the snapshot of the directory it was served to, so
when the 5 minute in-memory entry expired the next build probed live and
could render a different section, moving the cached prompt prefix. Adopt the
served answer into that directory's snapshot.
Also print the first differing bytes when the scheduled-run prefix test
fails, so a future flake is diagnosable.
The host appended a <context-budget> block to the turn tail at 75% and 92% of
the compaction trigger and raised a matching warning card. The model can still
ask: the read-only context_budget tool and its figures are unchanged, and its
description now says when to check, since nothing reports it unprompted.
The window opened a link in a new tab and then switched the active tab back
to the agent's. The page list was re-read between the two, so the panel saw
the agent's active tab move to the new tab and back, and its follow-the-agent
rule selected the agent's tab again: the link's page appeared but was not the
one shown. Session.VisitBeside opens the tab without activating it unless the
session has no active tab, so there is no transient to misread.
A prompts/get failure surfaced as the bare transport error, and the model
never learned the user had invoked a prompt. MCPPrompt now returns a typed
MCPPromptError (prompt, server, stage; matches ErrMCPPromptFetch) and owes
the model a turn-tail block describing the failed invocation.
Draw the kernel's typed `unread` on session rows (dot, bold title, spoken
name), as a count pill on folded project rows and as a dot on pane tabs.
A session is marked viewed through the pane's own endpoint when it
becomes the pane in front, when an unread row or pane is clicked, and
when a turn ends in the pane in front while the window has focus. The
mark is cleared optimistically and restored if the call is refused.
Refs #12230
/attachments refused with plain text, so the composer could only show
"Could not add" and left the reason in a tooltip. Refusals now carry
attachment.unsupported_image, too_large, empty, unreadable and write_failed
with params, the port keeps the code, the catalogues say each in both
languages, and the failed chip shows the reason with Retry.
Panes that are not on screen kept re-rendering on every streamed delta, and
each of those renders also rebuilt the reply actions handed to every settled
row. With nine sessions streaming the renderer's main thread saturated and
queued work (status reads, stop, settings) starved.
A hidden pane now takes the first token of each stream at once and coalesces
the rest into one batch per second; any other event flushes first and bringing
the pane forward flushes immediately. Reveal pacing, the thinking clock and the
wait clock only run for a pane that is shown. The reply actions keep their
identity while a streamed answer grows, and the folded thought's length is no
longer recounted per delta.
Refs #12232
MarkSessionViewed pre-checked the session sidecar with a plain unlocked
read. Concurrent views replace the sidecar, so on Windows that read can
meet it mid-replace (access denied, or torn JSON after the replace
fallback) and the view failed with a 500 instead of waiting it out. The
pre-check now uses the tear-tolerant read the other sidecar readers use.
Refs #12230
The kernel now records two optional facts in the session sidecar,
finished_at (stamped when a turn ends) and viewed_at (stamped when the
person opens the session). A session is unread iff finished_at is after
viewed_at; the sidebar tree and GET /sessions carry it as a typed
`unread` boolean. A turn the person cancelled is stamped as viewed.
Controller.MarkSessionViewed (POST /sessions/viewed) writes only the
session sidecar and takes no workspace or transcript lease. Sidecars
without the fields read as seen.
Refs #12230
A fold leaves the model with a digest, and the host re-projected only the
step ids of plan-seeded unfinished lists. A task list the model wrote itself
(no ids), a list with every item complete, the files changed and the checks
that passed all survived only as digest prose, which a weaker model reads as
work not yet done and repeats.
While a fold is installed the request now ends with one derived "Host
progress record": the canonical task list with statuses when no todo_write is
left in view, the files changed in the folded prefix, and the verification
commands whose latest outcome was a pass (marked when a file changed after
them). It is derived from tool receipts, bounded (30 files, 15 checks, with
the omitted count), memoised on the covered-prefix fingerprint, and ends with
the fold. Sessions that never folded carry nothing extra.
A live serve with no record is attached and recorded, launch no longer
deletes the port/pid files another serve published, and a live serve
whose provider source differs fails the connect with a coded error
instead of being terminated. Document what desktop exit does to the
remote runtime.
The lock directory sat at a shared, predictable /tmp path, so another
identity could create it first and every later run by this user failed
on os.Chmod with a bare EPERM.
Put it under the OS user's home (from the account database, so HOME and
TMPDIR overrides do not move it), as Windows already did. Keep the owner
uid check before the chmod as a backstop: it returns ErrLockDirWrongOwner,
naming the path and uid, without touching the directory. The /tmp name
is only a fallback when no home is recorded. The check is a no-op on
Windows; lockdir_owner_other.go is renamed lockdir_owner_windows.go.
Fixes#11372
Package segments made only of dots, leading dots, a leading underscore on
unscoped names and .tgz/.tar/.tar.gz names are no longer accepted by the
installsource and registry checks. parseGitHubRepoSource refuses dot-leading
owners and dot-only repos such as the one TrimSuffix(".git") leaves from
"...git".
Refs #11353
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
perf/tooltip.mjs looked the attach button up by its aria-label in each
language, and the English label it searched for, "Add attachment", is not
the shipped one ("Add an attachment"). The lookup found nothing and the loop
skipped on, so the four English attach combinations were never measured and
the guard stayed green.
Controls are now found by structure, data-action for refine and the attach
class for attach, and a control that cannot be found fails the guard rather
than being skipped. All 24 combinations (light/dark x zh/en x two widths x
three cases) are measured: 96 checks, where 80 ran before.
Refs #12156
The veil blurred the whole window behind it, which the compositor redoes every frame while anything beneath animates. A flat, more opaque scrim dims the same way for free.
GUIDE.md lists "Ctrl+L or /cls" as clearing only the visible transcript, and
1.x bound the key, but the 2.x terminal UI left Ctrl+L unbound. It now runs
the same clearDisplay as /cls: the conversation, session and the kernel's
task list stay, and a running turn is refused the same way. Being a key
rather than a typed command, it leaves a half-typed draft in the composer.
Refs #12144
The fold trigger compared only a local estimate against the threshold. That
estimate is characters times one tokens-per-character ratio, and the ratio is
not learned from requests that carry images or provider-side tool results, so a
session with screenshots can sit well under the real size and defer the fold.
The trigger now takes the larger of the local estimate and the provider's
reported prompt_tokens (cached tokens included) for the last request plus the
local estimate of what the visible view gained since. The anchor is dropped when
a fold changes the projection, when the transcript is swapped, and when the
usage is estimated, aggregated over retries, or padded by provider-run tools.
The hard ceiling and the overflow paths still read the local estimate alone, so
a provider count that no fold can lower never ends a turn.
The slash and @ completion menu and the /resume picker moved only with the
arrow keys, while 1.x also took Ctrl+N / Ctrl+P there and GUIDE.md and
CLI.md list both for these lists. The provider and quick pickers already
bind them. Both lists now take Ctrl+N as Down and Ctrl+P as Up; neither is
text, so a picker's filter is untouched.
Refs #12144
bash_output and wait reported a background job that later failed or timed
out only in their text, with no tool error. The activity panel and the
failure count read the host-set error, so a job that failed after it started
never turned red and was not counted, while the model was told it failed.
Both tools now return errJobFailed, naming the failed jobs, whenever a job
they report ended as failed (a background command's non-zero exit and its
timeout both end that way). The output is returned alongside, so the model
still reads it under the error line. A job that finished, was killed, was
interrupted or is still running is not a failure and the call stays clean.
Fixes#12201
POST /providers/probe built its refusal message from probe.Error(), which
carries the model-list response body verbatim. That body can echo the
request, and this route receives the key typed into the add-provider draft,
so a 401 that quoted the key sent it back in the refusal. A failure that was
not a ProbeError came back as an untyped 502 with the error's text.
The message now carries the probe reason and only what endpointDetail lets
through (one bounded printable line, the draft key and credential-shaped
text redacted), the same treatment /providers/check gives its detail. A
failure without a probe identity is a kernel fault and refuses as 500
provider.probe.failed without its text.
Fixes#12188
A phone using the web client got an empty Workbench when the agent opened a
page: useBrowserTabs returned early because the web host draws no native
views, so the tab list was never read. Read it on every host and, where the
page cannot be drawn, show its title and address as plain text with no
controls.
Opening a session pane read GET /checkpoints three times against a real
kernel: the mount effect read it, the turn-boundary effect read it again on
its own first run, and a third time when the first status named the session
path.
useCheckpointRefresh now owns every read outside a session reload: the mount
read, a move from one known session to another, and both turn edges. The
kernel opens a turn's checkpoint when the turn starts and fills it while the
turn runs, so the read at turn start stays and the rewind entry still appears
on a running turn's message. The first status naming the session the mount
read covered no longer reads again, so opening a pane costs one request.
A failed read is tried once more after a second, since no later edge is owed
to it, and a read that a newer one superseded is dropped instead of
overwriting the newer list.
Refs #12080
The save area sat at the end of a long form, so its state was out of sight
exactly when it mattered. The bar is now sticky, shared by the edit and add
forms, and holds the failure or not-applied reason beside a single persistent
status line with a dot for each phase (clean, unsaved, saving, saved, saved but
not applied, save refused). Ctrl/Cmd+S saves a pending draft.
A skill that declares paths: is held out of the per-turn listing, the
capability catalog, the slash_command listing and the unknown-name hint until
the session has touched a file its globs match. The set of touched files is
owned by the skill store; eligibility is memoised against it and dropped when
it changes, and the next turn after a match owes the listing again. The user's
/<name> and run_skill by exact name are not gated.
The shell card and the activity group decided failure a second time from
execution.state and exitCode, so state background_started (no error, no exit
code) rendered a red BACKGROUND_STARTED badge and counted as one failure.
The host already classifies every call once: err is set for failed,
timed-out, cancelled and refused calls. Read only that.
Verifying a model was one click per row, and the form blocked while any one
ran. The model header gets a Test enabled models button that runs the existing
per-model check over the ticked rows, two at a time, and stops as soon as the
draft changes or the form is left. The summary line counts rows still being
verified, and the per-row live regions are dropped so a run is announced once
through the summary.
The agent tells a PathObserver which workspace files a completed call named,
through the ReadTargeter and WritePathResolver capabilities its tool already
declares; failed, refused and hook-blocked calls feed nothing and sub-agents
are not observed. Swapping the conversation rebuilds the set from the
transcript. skillSet wires its PathHits in; the catalog does not read it yet.
A failed catalogue refresh surfaced under the form's footer and a silent
refresh that found nothing looked like a click that missed. The add form's
connection failure also rendered below its save button.
Refresh failures and outcomes (including 'no new models') now render under the
model header, verified rows are summed up in one status line over the ticked
models, and the add form's connection failure sits under the button that ran
it. Results clear when the address or key they depend on changes.
Skills may declare file globs in a paths: key. The registry now parses them
(rejecting negation, parent traversal and malformed globs into InvalidPaths,
which doctor reports) and a PathHits set records workspace files the host saw
touched, resolving symlinks so nothing outside the workspace counts. The set
is owned by skillSet; the catalog does not read it yet.
Leaving the edit form for another service, the Add form, another service's
rename, or a protocol switch dropped pending edits without a word.
EditConn reports the pending state it already computes; Providers holds it
and routes every way out of the form through one guard that asks to keep
editing or discard. A save in flight is not interrupted and not asked about,
and the question closes by itself once nothing is pending.
The edit form had no way to tell pending edits from stored ones: Save was
always enabled, Cancel was a remount in disguise, and a successful save
silently turned into the same form with nothing said.
Pending state is the fingerprint of what a save would send, compared with the
one taken at mount, so typing a value back or toggling a row off and on is
clean again. Save is disabled when clean, Cancel becomes Revert, and the footer
says whether there are unsaved changes, no changes, or that the save landed
once the list has been re-read.
Cause: POST /providers/check returned a free-text `error` assembled from the
probe error's message. The probe already knows whether a key was refused, a
path is missing, nothing listed chat models or the deadline expired, but that
identity was flattened into prose on the way out, so the settings page could
only print the sentence (which also echoed the endpoint body unredacted), and
the refresh button invented its own literal error when a check came back empty.
Blast radius: /providers/check on failure now answers 200 with
`{ok:false, code, params?, httpStatus?, detail?}` and no `error`. `code` is the
dotted identity the add flow's refusals already use (provider.probe.*), plus a
new provider.probe.timeout split out of unreachable; `params` carries only the
numeric facts the sentence needs; `detail` is the endpoint's own text through
the same redaction the model check uses. Success responses are unchanged.
catalog.ProbeError gains Status and Body, read from the typed fetch error, and
the status/code table is shared by the refusal and the finding.
Neighbouring behaviours tested: refused, forbidden, payment, rate limit, missing
path, upstream error, no chat models, unreachable, timeout, success, key
redaction in the endpoint text, bounded one-line detail, missing grant,
unknown name, and the add flow's probe codes all translated.
Why this layer: the probe owns the class; serve only projects it; the page
maps the code to words.
The refine button's tooltip carried the Ctrl+Shift+E shortcut, but nothing
pointed the button at it: it had no aria-describedby and the tooltip had no
id, so a screen reader announced only "Optimize prompt" and the keyboard
route stayed undiscoverable. The attach button beside it already describes
itself this way.
The tooltip now takes an id from useId and the button names it in
aria-describedby, matching the attach button.
Refs #12156
A settled render_chart call (direct or through use_capability) is re-validated
on the page, lifted out of the folded execution group, and drawn as a bar,
line or pie chart from the validated spec by our own React SVG components in
a lazy chunk. A running, refused or invalid call keeps the ordinary tool card.
The same card is rebuilt on reload from the stored call. Each chart offers an
accessible data table, keyboard-reachable points and no entrance motion.
A settled chart call (direct or through use_capability) now draws its title,
marks and rows as a capped, width-fitted table in the terminal, with numbers
right-aligned and the fold key opening the rest, instead of a line count.
chartspec.FromCall and Spec.Table/Describe are the single projection; the TUI
history record and frame merge now keep the resolved tool name, without which
neither a reopened session nor a live call could be told from any other
use_capability call.
A successful save remounted the editor synchronously while the list reload
was still in flight, so the new editor was seeded from the stale entry and
showed the pre-save values. onEdited now returns the reload, the editor
waits for it before remounting, and save keeps the form busy until then.
A steer sent while a stop had paused the queue was refused with inbox.paused
but its queued copy stayed behind, so retries piled up and all ran on resume.
Hold the item as a follow-up under the admission lock and answer with the
queued receipt that already carries paused=true. The pause itself is kept,
and send-now from the queue panel still refuses. The composer reads the
receipt, from the steer path and from an idle skill send, and says the queue
is paused, once, in the notice strip.
Part of #12153
Stopping a turn while the plan approval card is open left the plan in
AwaitingApproval with no card to answer it. Later messages then ran under
the planning barrier and no new card could open. Treat a cancelled or timed
out approval as a revise so the workflow can propose again.
Part of #12153
The refine and attach tooltips are children of their buttons. They inherited
the button's white-space: nowrap, so the hint spilled past the 220px box in
both locales, and the disabled refine button dimmed itself to 35% opacity,
which multiplied down onto the tooltip that explains the control.
The shared .studio-control-tip now wraps inside its max-width, and a disabled
host dims its glyph instead of itself.
Part of #12148
Cause: the settings page awaited setRole and only then re-read /roles. While a
turn, a question or a background job is active the kernel has already written
the value and answers 409 runtime.saved_while_running; run() treated that as a
failure, the re-read never ran, and the controlled select snapped back to
"follow the main model" although config.toml held the saved model. The banner
also called it "操作未完成".
Fix: the role row always re-reads /roles (promise finally), and the page's one
failure banner now distinguishes saved-but-not-applied (SAVED_NOT_APPLIED, the
set the provider edit form already uses) from a real failure, with the same
title, warn colour and status role.
Blast radius: Settings.tsx only. Every action routed through run() now shows
"已保存,尚未生效" instead of "操作未完成" when the kernel answers one of the
SAVED_NOT_APPLIED codes; other failures render as before. No kernel change.
Neighbouring behaviours tested: role saved while idle, role saved while busy
(value and notice), real refusal (alert, no saved notice), other role rows and
a reopened page after a busy save, default-model row refused while busy (stays
on the kernel's default; the kernel refuses before writing, so it is not the
same defect). Go: POST /roles mid-turn returns 409 runtime.saved_while_running
and GET /roles returns the saved value.
Why this layer: the page owns the re-read and the banner; the code identity
already comes from the kernel and the shared SAVED_NOT_APPLIED list.
Part of #12144.
Cause
- `/quit` and `/exit` are in the slash catalogue but the TUI had no handler,
so since #11969 they were sent to the model as ordinary messages.
- Every Ctrl+C on a running turn re-sent cancel, so a turn that would not
stop could not be left, while the footer says "Ctrl+C exits".
- Tab in the completion menu moved the highlight down (and did nothing
visible with one item); the guides say Tab / Enter accept.
Blast radius
- internal/frontend/tui: send(), menuKey, the Ctrl+C branches of onKey and
viKey, one new Options field. internal/frontend/cli passes the quit names
from the slash registry. docs/GUIDE.md and GUIDE.zh-CN.md rows for Ctrl+C,
Ctrl+D and /quit. No controller, wire or config change. The bare words
exit, quit and :q stay ordinary prompts.
- Tab history: the cycling was part of the first completion commit
(5ff9d7a0f) with no stated reason and no test; nothing later touched it.
Neighbouring behaviours tested
- Idle double Ctrl+C, Ctrl+D (idle and while running), double Esc opens
rewind, a second Esc does not quit, Esc then Ctrl+C quits, one cancel call
per first Ctrl+C, vi mode Ctrl+C, a finished cancelled turn needs two
presses again, Up/Down still move the menu, Enter accepts, Tab accepts
the highlighted row, file-mention directory descends, Tab without a menu
still asks the kernel, /quit while a turn runs is not queued.
Why this layer
- Quitting is a frontend lifecycle action with no controller verb, so it
lives in the TUI local-command switch beside /mouse; the names come from
the registry through Options rather than a second alias list. Both
interrupt paths share one helper so Esc and Ctrl+C agree on what
"stopping" means. The unreachable shift+tab case in menuKey is removed.
When the kernel refuses browser.open, the workbench stored the reason in
`failed` but only drew it inside the file-tab branch. Opening the column
or pressing + with no page left the pane on its empty state, "choose a
file on the right, or open a browser", with no error.
Cause: `failed` was rendered only when the active surface is a file, while
the browser open paths (column opening with no agent tab, the + button)
also write it. The kernel also answered every failure with the generic
browser.open_failed, whose wording needs an {error} param it never sent,
so the typed engine_missing / engine_failed / profile_busy causes were
lost. Separately, `.workbench-error` used --danger, a token no stylesheet
defines, so such errors drew in the body colour.
Blast radius: WorkbenchPanel, one CSS declaration, serve's browser open
refusal and the kernel.ts / en_kernel.ts tables. The alert renders for any
non-file surface; file tabs keep their own copy and are unchanged.
`failed` is cleared when a tab is picked and when the active tab closes.
POST /browser/open now answers browser.engine_missing, browser.engine_failed
or browser.profile_busy for those causes (same 400, same params, plus the
detail in params.error) and browser.open_failed for the rest. The colour
fix also recolours the file-tab and explorer errors.
Neighbouring behaviours tested: the three codes and the generic one through
the real SsePort decoding path, on both open paths, asserting the exact
text and no leftover placeholder; the reason clearing on picking a file
and on a later successful open; a file read error staying single on its
tab and going away with it; serve end to end with real launch failures;
the refusal-code parity test; the whole WorkbenchPanel suite and tsc.
Why this layer: the pane owns the state and the draw; the kernel boundary
in serve is where the typed cause becomes a code, and internal/platform/
browser is not touched.
Fixes#12139
Add internal/base/scratch: a temp directory claimed by a long-lived
exclusive .owner.lock (internal/base/filelock). Remove releases the lock
first and retries sharing violations for up to 2 s. Sweep/SweepOnce remove
only non-symlink directories past a one-minute grace whose lock can be
taken, so a live owner is never touched. The jobs manager now creates its
reasonix-jobs-* fallback root through it, which also sweeps roots left by
earlier killed kernels.
Part of #12142
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
The feedback dialog is fixed to a 720px column and thread bubbles are capped
at 560px, so a long maintainer reply on a wide window reads as a narrow strip
that needs constant scrolling.
Add a "Fill the window" toggle next to Close. While pressed, the dialog takes
the whole veil and thread bubbles widen to 92% of the column; pressing it
again returns to the reading column. The toggle is a view-only action
(feedback.expand) and the state lives only for the open dialog.
Refs #11575
The reference-solution half covered only the e2e, train and verification-stress
corpora, so three committed corpora with the same tasks/ layout carried no
evidence that any attempt can score: a grader that can never pass is
indistinguishable from a task with no solution, and a corpus of those teaches
that nothing is ever accepted.
Their pristine seeds already fail, checked across all eleven tasks, so this adds
the reference solutions the guard needs: one ANSWER.md each for the three fleet
tasks, and the non-positive-skipping calc.py the eight project-check graders
assert. The e2e suite stays the single declared exception, named in the skip.
Refs #11852.
Prompt refine and commit-message drafting built their provider with effort forced to "disabled". Generic OpenAI-compatible endpoints refuse that at construction, so the side model was nil and the user saw "no usable model" while chat worked. sideProvider retries only on provider.ErrEffortRefused, with the lowest effort the entry's vocabulary lists (automatic depth when it lists none); other construction errors are not retried and are logged at warn.
When a summary fails under the pressure trigger the retry is held, and
while it is held and compaction is due nothing reached the user. The
agent now emits a coded notice, compact_held, once per turn and cause,
with the failure's code as detail. The desktop and the TUI word it from
the code in the UI language; a code a build cannot word keeps the
kernel's English.
A transient summary failure held the retry until the input grew by an
eighth of the trigger. Recorded near 105% of the trigger that release
point is the window itself, so the next request went out uncompacted.
The step is now capped at half of the room left below the hard ceiling,
so the hold always lifts before it.
A relay that refuses an oversized request with a 400 and no error code
was not recognised as an overflow, so recovery never ran. A 400 or 413
whose request our own estimate puts over the window now takes the same
fold-and-replay path, judged by status and size, never by wording.
A session row was selected only once POST /runtimes (or /resume), GET /runtimes
and GET /tree had all returned, so every switch to a session without a pane
waited out the kernel's pane assembly with nothing but a dimmed row to show for it.
The window now owns which session is being opened (useSessionSwitch). The row
reads selected and busy from that state as soon as it is clicked, the active
pane shows aria-busy, a ticket orders concurrent opens so the last click wins
and a refusal from a superseded open changes nothing, and a failed open drops
the selection back to the pane that is still in front while the error is shown.
Opening moves out of App into useOpenPane so every caller shares the one path.
The pane burst after an open now asks for /runtimes and /tree through one
single-flight read instead of one per caller.
/status called config.Load() on every poll, which re-parses every
installed plugin package under a process-wide lock. The controller now
carries the model entry boot resolved and /status reads effort, vision
and the model ref from it. The provider label still comes from the user
file because a rename rebuilds nothing.
Refs #12055
Discovery read every directory-layout skill's references/ and scripts/
to build a Body that a catalog never uses, so opening a pane cost
O(bytes in the skill library). Listing now keeps only SKILL.md; Read,
ReadSlash, Prepare and the capability digest complete the skill when it
is invoked or inspected.
Refs #12055
A summary request was cut off at a fixed 90 s of wall time, so a local model
that was still streaming steadily failed every /compress with summary_timeout.
Bound the silence between streamed chunks instead (6 minutes, one minute past
the providers' own 300 s stream idle watchdog), and add a 30 minute overall
ceiling with its own typed code summary_ceiling so a trickling model cannot
hold the turn. Both failures stay transient: a later attempt may retry once
the input has grown. The notices state which bound fired and its length.
GET /skills resolved each skill's activation through Controller.SkillEnabled,
and every call re-ran config.Load(), which walks all installed plugin
packages. Resolve activation once per request via Controller.SkillActivation.
Refs #12055, #12051
Go's default User-Agent is answered with 403 by edge bot protection on
datacenter networks. Catalog and manifest reads now use the Studio update
identity shared with artifact downloads, the relay websocket handshake uses
the account client's identity, and feedback, telemetry, crash and account
requests fall back to the shared client identity when none is set.
The pane polled /status every 250ms with no in-flight guard. When the kernel
answered in 13-16s the unanswered reads filled the browser's per-origin
connection pool and the approve, plan-decision and answer POSTs queued behind
them.
Idempotent reads now share one request per path with a single trailing
re-read, the pane schedules its next poll when the previous read settles, and
decision submits get a bounded wait that fails with a typed KernelBusyError.
The retry status line showed "connection broke before the response headers"
for every retried failure, including 502/429 statuses, did not say what the
attempt was waiting for or when it counts as unanswered, and its seconds
counter ran from the first notice of the whole stall instead of per attempt.
The retrying event now carries a typed failure class (connection_closed,
timeout, upstream_status with the HTTP status, stream_idle, upstream_error),
the backoff before the next attempt, and the header wait of the coming
attempt. The producers set them: SendWithRetry knows the class, the adapters
pass their idle_timeout_seconds window, and the stream replay path maps the
stream-interrupt reason. The window words each class and phase (backoff
countdown, then a wait that restarts per attempt) from those fields, never
from text. The CLI machine record carries the same fields.
Provider settings gain one advanced row for idle_timeout_seconds (1..32767,
empty is the default), validated in the form and by the kernel's provider
edit endpoint.
Fixes#12046
The host book has no per-machine state: every entry is dialed on demand and every entry is
listed in the rail. A machine that is temporarily off the network, or one kept for the
occasional job, cannot be marked as such - the only lever is removing it, which takes its
folders and its forward rules with it.
- RemoteHostEntry gains disabled, written as "disabled" rather than "enabled" so an entry
written before the field existed still means what it always did, plus IsEnabled(); the
renderer writes the key only when it is set.
- One switch per row decides whether the machine is dialed, and that is all it decides:
live panes keep running and the rail still lists the machine, because a machine being
off the network says nothing about what this window should show. Whether this window
lists it stays a separate preference, and it is an immediate row action next to the
row's other actions rather than a control inside the edit form, which saves on submit.
- remoteHostDialable is the single answer to whether a machine may be reached: POST
/remotes/open refuses a disabled one with remote.disabled before dialing, and remoteLink
(the tree and directory reads, and removing a far session) asks the same question, so a
disabled machine is not reached for at all. A book that cannot be read is a refusal
rather than a dial.
- GET /remotes reports disabled and the edit endpoint carries it.
Project every string and list of an install plan through one bounded
projection shared by all frontends: names, paths, commands and arguments
render hidden characters as visible escapes, prose has them removed, and
each field, list and the action count has a cap. A cut ends the field with
an ellipsis and sets previewTruncated on the action and the plan. The planId
still digests the complete unprojected plan, and the market ledger records
the unprojected paths. Installed-package detail views (inventory, record,
runtime, warnings) use the same limits from the shared text utility.
#11897 stopped a session's model switch from rewriting default_model, which left the
settings row reading status.modelRef - the model the session in front is running - so
switching models inside a conversation still moved that row while the file it claims to
describe did not change.
The row now shows the entry the model catalogue marks as default, falling back to the
session's model when the catalogue carries no default, and the column beside it follows
the same value. `main` stays the session's model for the vendor-protocol inference this
component does.
Tested: with a catalogue default the row shows it whatever the session runs; without one
it falls back to the session's model; the settings-catalogue, ModelUsage and i18n gates.
The interface scale was declared four ways: the config comment said
0.8..1.6, the save clamped to 0.7..1.8, the slider ran 0.8..2.5 and the
presets covered 0.9..1.3. Dragging the slider to 2.0 showed 2.0 while the
kernel stored 1.8.
config.ZoomMin/ZoomMax/ZoomStep are now the only declaration. The save
clamp, the rendered config comment and the range /appearance announces
(mirrored to the page by wire-parity) all read them, and the slider is
drawn from the announced range instead of carrying its own. A stored
value outside the range reads as the nearest bound but is not rewritten
unless the user changes the zoom.
Ctrl/Cmd + '=' / '+' / '-' / '0' (read by key or by key position, so
shifted and non-QWERTY layouts work) step the interface scale through the
named presets (range ends outside them) and reset to standard, saving
through the same appearance setting as the Appearance control. Chromium
page zoom is not used.
The range is 0.8..1.8: 1.8 was the largest value the old save could
store, so nobody loses headroom. The keys exist only inside the desktop
shell; a browser tab keeps its own page zoom.
Fixes#12032
Part of #11962
A stable studio-vX.Y.Z that is the highest stable studio tag is created with
--latest and without --prerelease. Candidates (-preview.N, -rc.N) keep
--prerelease --latest=false, and recovering an older stable tag does not take
latest back. The decision lives in scripts/studio-release-flags.sh and reads
the tag grammar, not wording.
The GitHub release no longer carries a root latest.json. The 1.x desktop
updaters fall back to releases/latest/download/latest.json, and builds up to
1.18 do not validate the manifest and share Studio's minisign key, so a
Studio manifest there would offer them a Studio package. The file stays in
the R2 mirror under <tag>/, which is the only place Studio reads it. The
release now has 21 assets.
Adds a flags-script test and a workflow contract test, and updates the
runbook, READMEs, MIGRATING and ROADMAP.
A deny or ask rule such as Bash(rm:*) compared only the words the model wrote,
so `env X=1 rm`, `sudo rm`, `command rm`, `nohup rm`, `nice rm`, `time rm` and
`/bin/rm` were judged by fallback mode instead of the rule.
shellparse.PeelWrappers reads one simple command from the shell AST and
follows a fixed set of wrappers (env, sudo, doas, command, builtin, exec,
nohup, time, nice) down to the program they run, naming a path-spelled program
by its base name. Deny and ask matching test every layer; allow and session
rules are unchanged. A wrapper whose target cannot be named statically needs a
human instead of falling through to the mode.
apply() wrote every pack token, and the ink variables even with no pack,
as inline custom properties on <html>, so any author stylesheet needed
!important to win. The values now live in one rule of a <style
id="pack-theme"> created after the built-in sheets, written through the
CSSOM so a rejected value is dropped. The contrast tiers carry the inks in
the stylesheet, with --ghost on --faint's value, and perf/tiers.mjs holds
each tier to the legibility users already had.
A Claude Code Mod declares "modules" in hooks/hooks.json instead of a
"hooks" map. The importer returned early on an empty hook map, so the
modules were dropped silently and a Mod with skills previewed as full.
The model list collapsed entries by endpoint + model, so two providers
sharing a base URL with different API keys listed each model once and
the second account's refs vanished from the picker.
TestInstancesDifferBetweenRuns instantiated every task twice and failed
whenever two independent draws collided. Three tasks answer with a bounded
number (i70 quota 1200..9800, i47 lease 15..300, i112 lease 20..420), so a
run fails with probability 1/8601 + 1/286 + 1/401, about 0.61%.
liveRand now delegates to liveRandFrom(io.Reader). The test asserts the
invariant at the seed: two live generators do not share a stream, the same
entropy yields identical instances and different entropy yields different
values.
The walk tolerated only not-exist, but a directory being removed on Windows
fails to open with a sharing violation or access denied. Those errors now
count as vanished only when a re-stat shows the path gone.
Cause: the per-model check classified any 4xx it could not name as
unknown/rejected and the row printed only "request refused, not confirmed".
The kernel already carried httpStatus; the settings UI dropped it, and the
endpoint's own error text was never sent, so a local Ollama 404 (base URL
without /v1, model id without its tag), a 400 for a model that cannot take
tools, and a missing key on a System One entry all read identically.
Blast radius: POST /providers/check/model gains an optional `detail` field:
the endpoint's error text as one printable line. It is built in this order:
control characters and ANSI sequences replaced, the probe key masked in raw,
URL-escaped and JSON-escaped form (skipped for keys under 8 characters),
secrets.RedactCredentials, then cut to 300 runes. Folding and the key
replacement see the whole text; only the pattern scrub is bounded (8 KiB cut
on a word boundary, after folding). The model row shows an HTTP badge and the text inline. Status and
reason are unchanged and never derived from the text.
Neighbouring behaviours tested: Ollama-shaped upstream (correct, base without
/v1, tagless id, System One kind); 400/404/405/422 classification; detail
equals the upstream text, masks raw/escaped/plain keys, Bearer, ?token=,
Cookie, neutralises newlines/ANSI/NUL, cuts after redaction, cuts at the scan cap with the key split at every offset, with and
without leading whitespace, bounds a 4 MB body, leaves short keys alone (each mutation-checked); add and edit flows
share one fact helper; full ui vitest suite and the serve package.
Why this layer: the serve handler owns the typed result and the row is the only
renderer of it; classification stays where the failure is first known.
POST /browser/open answers 400 when the navigation fails even though the
window's tab already exists, and the link handler read every refusal as
"nothing opened" and sent the same address to the machine's browser.
The refusal now names the tab it left behind and the handler only falls
back when there is none.
A pane adopted at launch is not a folder anyone picked, so nothing listed it
once a later launch opened another one. Its conversations stayed on disk with
no sidebar row to open them from. The folder is now listed behind the existing
ones when the pane's first turn mints its session path.
A queued line taken back could fail with a spurious error on a repeated click,
leave a pending transcript row when its receipt landed after the withdrawal,
and reappear in the panel when an older queue pull answered last.
A truncated, timed-out or failed summary request blocked automatic
compaction for the whole generation, so context kept growing past the
fold trigger with no further attempt. Block release now reads the typed
failure code: transient request failures are released after the input
grows by one retry step; every other class stays blocked.
The digest request inherited the session's reasoning effort, so reasoning
tokens spent the same 16384-token cap the digest had to fit in and a
finish_reason=length dropped the whole summary. It now carries
EffortOverride=low; adapters apply it only when the endpoint's own effort
vocabulary admits it, so endpoints without a low tier keep their configured
depth.
Refs #11970
/compact notices were raw English sentences built from err.Error(), and an
aborted card with no code read "compaction done / folded nothing". Add
compacted/compact_declined/compact_failed notice codes (detail = the
compaction code), give cancellation, unclassified failures and a busy
session their own codes, and word them in the desktop and TUI catalogues.
Refs #11970
The terminal UI asked the kernel to refuse any slash line the registry does
not answer, so a typed path or half-remembered command dead-ended with
"unknown command". 1.x sends it as an ordinary message and says so (#5756).
The TUI client no longer sets refuseUnknownSlash; the controller's existing
default path runs the line through the normal turn pipeline. That notice is
now localised (en/zh/zh-TW) and carries the unknown_command code.
A built-in named first is that built-in whatever follows it, as in 1.x: the
kernel matches /context, /new and /clear by first word, and the TUI does the
same for /help, /clear, /setup, /version, /mouse, /resume and its display
commands, so none of them is sent as prose when given arguments.
Refs #11493, #5756
With no API key the connection panel opened silently and a typed message went
to the provider, which answered 401 with a pointer to a shell command. The
panel now names the model that lacks a key, and /submit refuses a turn for it
with provider.key_missing before anything is sent. The TUI returns the line to
the composer and points at /setup.
The usage range test assigned the process-global time.Local while goroutines from earlier tests still read it, so the race detector failed the package. usageWindow now delegates to usageWindowIn, which takes the zone trailing windows count days in; the test passes a fixed zone and never touches the global.
A provider whose natural key slot is taken gets REASONIX_CONNECTION_<id>_KEY. Collect wrote that provider and its key, but restore validation denied every REASONIX_ name, so the backup sealed fine and then failed to open as malformed.
Settings and feedback are answered by a pane's kernel. With no pane open, feedback silently did nothing and the settings refusal never cleared. Opening either now starts a session in the current folder, a refusal is shown for both, and it is withdrawn once a folder or pane exists. The cloud-backup button now states what it is waiting for.
Preserve ACP frame-order assertions while skipping only MCP status updates,
and use t.Context in the serve regression test.
Report servers requiring launch approval as pending rather than failed in
controller, ACP and /mcp snapshots. Keep pending rows disabled without a retry
action while retaining their recorded transport and sanitized error.
Document connecting and awaiting-approval notifications, style the pending
indicator in both themes, and derive frontend state coverage from Go constants.
Add controller, endpoint, ACP and row-rendering regression coverage.
- Map the new `pending` state in the desktop panel with an i18n label. A
repository-declared server nobody has answered for is off, but not because
the user switched it off, and the row rendered the raw state string.
- Announce what actually happened. RecordFailure and
RecordLaunchApprovalRequired now share one store path and name their own
state, so a project MCP awaiting authorization stops reporting a failed
connection.
- Correct the MCPSurfaceReady contract: it fires on every change to what /mcp
answers, not once per server. Audited every consumer before changing it —
the ACP sink republishes a full snapshot, the serve broadcaster numbers the
frame and the SPA refetches /mcp, the headless stream passes it through — so
repeats are harmless and the documentation was the stale part.
- Keep the spawn claim beside the rest of the deferred-spawn bookkeeping
instead of a file of its own.
- Add the /mcp endpoint regression test the refactor was missing (live
transport and source, recorded status code, in-flight spawn, cached
description and tools), and a panel test tying every state label to the
English catalogue.
SetCredential and SetCredentialIfRevision built a KEY=value line and parsed it
with the dotenv reader, so ${...} was expanded and wrapping quotes stripped
before the key was ever written. The writer also left a value containing $
bare, which the reader then expanded. Store the assignment directly and pick
the first encoding the reader returns unchanged; a value no encoding can hold
(for example a trailing backslash next to a quote or $) is refused before
anything is written, so one entry can never make the whole file unreadable.
Adds GET/POST /display-currency over the controller's new
DisplayCurrencySettings/SaveDisplayCurrency (shared with /currency), a
Settings > Usage control, and makes the quote sink follow the
display_currency notice so rounds after a change are priced in it.
Picking a 1.x data folder only looked at <root>/sessions, so per-project
sessions, sessions-v4 and desktop-sessions-v5/by-id imported nothing and the
UI reported no old sessions.
render_chart validates a chartspec v1 spec with chartspec.Parse and answers
with a chart_id and the deterministic text summary, never the rows. It is a
registered built-in left out of the provider allow list, so it is found and
called through use_capability and the cache-stable prefix is unchanged. The
spec persists in the call arguments the session already stores (#11564).
cli-channels and cli-pointer both waited only for cli-tag, so the cask could be
pushed while the v<version> release holding its archives was still being
created, and stayed pointing at nothing if cli-pointer then failed. The cask
step also overwrote the tap whatever its version, so a workflow_dispatch of an
older tag moved Homebrew backwards; it now skips a tap that is ahead and fails
closed when the tap's version cannot be read.
Leaf package internal/contract/chartspec: spec types for bar/line/pie, a
strict Parse (size cap first, unknown fields refused, scalar cells only,
unsafe integers refused), typed refusals with dotted codes and caps, and a
deterministic Summary that quotes cell data. No consumer yet (#11564).
EditConn titled every outcome "not saved" at warn level, so a write that
landed but was not applied to the live session read as a failure, and a real
failure read as a soft notice. Split by outcome: error alert for a refused
save, status note for saved-not-applied, own title for a catalog refresh.
The archives that reasonix upgrade, the Homebrew cask and the GitHub release
install were linked with main.version only, so they reported no git_commit or
build_time_utc and the embedded product docs identified themselves as dev. The
npm packages of the same release were linked with all of them.
CostQuote carries a rate band for a rate confirmed as the vendor's own
schedule, aggregates fold it to peak, off-peak or mixed (none when any call
has no schedule), and the TUI prints it beside the cost as 1.x does.
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
The panel dropped a list as soon as every item was completed, so the final
N/N never showed. It now stays through the ask panel and the idle prompt and
goes when the next turn begins; a rebuilt screen (resume, rewind) still does
not draw a list that finished before it was opened.
Adds a typed Commits port on the controller: ProposeCommit reads the staged
index through gitcmd, frozen as a tree object, and asks the session's model
for a conventional-commit message in a request of its own (prefix untouched,
billed as commit-message). CommitStaged writes the reviewed tree with
commit-tree and moves HEAD only if it has not moved, so an index that changes
after confirmation cannot enter the commit. Files named as secret holders or
whose added lines carry a private key are withheld from the model and need an
acknowledgement. Hooks and signing never run. Nothing pushes.
Refs #11518
- Add the pre-5.3 GLM catalogs to shippedPresets so installs holding the
old list are moved forward, plus upgrade tests (old shape upgrades, a
curated catalog stays untouched).
- Declare the per-model ladder once in provider.ZhipuEffortContract and
read it from the /effort menu, the normalizer and the request builder,
instead of repeating the model lists across three switches.
- Cite Zhipu's Core Parameters reference for the documented levels
(5.2's none/minimal/low/medium/xhigh folds, 5.3's forced thinking).
- Add a boot.Build effect test at the provider-request boundary covering
the stored enabled/disabled -> EffortDisplay -> request path.
- Surface the disabled->low landing for GLM-5.3 in the /effort menu and
document the 1M context window the added models reuse.
CodeQL's go/command-injection models regexp matches as barriers only when the
match is the branch condition dominating each use. screen() folded the match
into an || condition and the -C directory skipped it, so alert 1014 kept its
flows through screen() and Args.
- keep typed field details for days and explicit ranges
- reject from/to combined with days
- treat explicit dates as zone-less calendar days
- relocate and extend usage range tests
rebuildFailed collapsed the busy refusal into runtime.rebuild_failed with
the English sentence as detail, so the compaction, shell, sandbox,
permission, browser and MCP-load saves told a zh reader the write had
failed. It now answers runtime.saved_while_running, which the UI renders.
A bare https://host passed the add-provider probe because {base}/v1/models
answered, while chat posts to the saved base + /chat/completions and failed.
The probe now reports the base that was proven (typed address completed with
/v1 only when the listing answered there alone, openai-style kinds only), and
add, onboarding and edit-refresh adopt it into the address field before save.
F5 and Ctrl/Cmd+R reloaded the whole Studio UI from any focus, so a page
or browser-pane user pressing them lost the window state. The app window
now reloads only on Ctrl/Cmd+Shift+R (white-screen recovery, crash reload
unchanged); the browser pane reloads its own page on F5 and Ctrl/Cmd+R and
ignores the cache on the shifted chord.
POST /model persisted every switch as the user's default model, so changing
the model inside one conversation silently changed what every new session and
the next launch started on. The typed /model command, the terminal UI and ACP
never did this; only the HTTP picker path did.
The request now carries an explicit `default` flag. The Settings default-model
row and the first-run model choice send it; the composer picker does not, so
it changes only that session. A pane resolving through the hub's resolver
still never writes this machine's config.
One structural rule (secrets.RedactEndpoint / CredentialKey / RedactArgs)
replaces mcpsetup.RedactURL's query-only masking. Userinfo and fragments are
dropped or masked, query and fragment keys are decoded before classification,
unparseable endpoints render as <redacted>, and transport diagnostics keep
error identity while omitting peer-supplied prose.
Operational values (install, approval fingerprints, persistence) are untouched;
preview and plan responses add separate display fields that the desktop mirror
and confirmation UI render.
Rebased onto the launcher-aware NameFromArgv: names derive from the original
argv but never from a value the display projection masked, so build-flag values
such as `go run -exec` still resolve. Install classification reads the typed
HTTP status instead of sniffing the server name out of the message.
Fixes#11785
The primary button is focused from a passive effect that runs after the
element is committed, so asserting document.activeElement right after a
findBy* resolves races that effect. Use waitFor, as settings-layers does.
testenv.TempDir retries RemoveAll for two seconds when a file stays held.
On Windows the usage and history SQLite catalogs, which the shared helpers
left open, held a file under nearly every test's directory, so each of those
tests paid the full retry budget at teardown. Closing the catalogs from the
helper's own cleanup, which runs before the removal, makes it converge on
the first attempt.
A successful Windows bash probe is remembered per executable identity (path, size, mtime) for the life of the process; failures are never remembered. Every boot.Build used to spawn Git bash for it.
agent.Options.WorkspaceScanLimit (zero keeps the 50,000 production bound) is threaded from boot.Options. The incomplete-prose-scan effect test now sets it to 64 and writes 65 files instead of 50,001.
The deadline starts at launch, so with a real interpreter the test measured startup time. Under load Git bash needed more than 500 ms and the pre-deadline write never landed.
TestRememberPermissionRuleSerializesConcurrentWriters queued 32 writers on a
lock that waits 5s, so a slow Windows filesystem ran the last waiters out of
time (lock timeout at 5.08s). The cross-process variant had the same shape:
each helper waited a fixed 5s for the others to spawn. Eight writers still
interleave, and the readiness waits now read the shared hang budget.
The deadline is armed at launch, so a 500ms budget races interpreter startup:
on a loaded Windows runner PowerShell had not printed anything when it
fired, and the test reported the pre-deadline write as lost. PowerShell now
gets a 10s deadline named for what it has to outlast; POSIX shells keep 500ms.
Hang-catching waits in the control tests were hard-coded to 2, 3, 5 or 10
seconds. A whole turn through the real agent loop can exceed that on a
loaded Windows runner, so TestTurnOutcomeClassifiesPerseverationPause failed
with 'timed out waiting for TurnDone' on a run that passed on rerun. They now
read testenv.Budget(t), which is the single named bound for a wait that only
exists to report a hang.
cloudPresence kept a monotonic counter, so every reconnect of the same phone
took the next number. Allocate the lowest ordinal not held by a live
connection instead; it stays fixed for the connection's lifetime.
The wide-workspace effect test compared total goroutine growth against a
magic 64, which host background work (sqlite watchers in the history
catalog) pushed past under load. Count only goroutines inside the walkers
and bound them by pool size times concurrent walk kinds; assert none
survive the turn.
A fold that was refused or failed ended with an aborted card reading
"folded nothing" and a blocked receipt whose code was empty; the cause lived
only in a sentence. Checkpoint rejections now carry a code per rule, summary
failures are classified by sentinel (truncated, timeout, input too large,
context changed, provider failure), and the code rides both the receipt and
the aborted compaction frame. The Studio card shows a localized cause, and
keeps its before/after token pair on one line when the row wraps.
The Windows runner's temp dir is an 8.3 short name. The grant target for an
ambiguous path is resolved to the long form, so the expectation built from the
unresolved root never matched.
The regression held row and button references across the refetch that
PackageList.submit now triggers after a successful review submission, and
its deferred submitMarket fixtures never updated the inventory myMarket
returns. Query live rows after each reload and delegate to the mock's real
submitMarket so the reloaded inventory reflects the new status.
A tilde inside a Windows 8.3 short-name segment (RUNNER~1) of a drive-letter
path is an ordinary character; any other tilde can expand to a home
directory and stays refused as non-literal. Unescaped braces are refused
too, since a brace word expands to several targets.
Also give the PowerShell delete-analysis subprocess test a hang guard
instead of a 15 s latency assertion that fails on loaded runners.
The reference-solution guard fired only for the train corpus. For every other corpus a missing solution/ was a silent skip, so a grader that can never pass stayed indistinguishable from a task with no solution - the case the rule above it exists to prevent. #11380 supplied the ten vs-pos solutions; this extends the guard to the whole verification-stress corpus and supplies the ten tasks it was still skipping.
The e2e suite keeps the skip, and the skip message now says why: its handwritten tasks do not commit a reference solution.
Verified: go test ./cmd/e2ebench -run TestCorpusGradersPassTheReferenceSolution is green across e2e, verification-stress and train. Removing any single solution/ fails the guard at corpus_test.go:254 (checked by hand for vs-neu-cfgkey), so the added solutions are what the guard grades.
A relay that rejected the continuation field with a message the client did not recognise surfaced the HTTP 400 to the user, and the stored response id was kept so the next turn sent the same rejected request. Any HTTP 400 on a request that carried previous_response_id now retries once with the full history; a completed recovery stops sending the field for the client's lifetime, a failed one surfaces its own error naming the attempt.
Revision after the security review: ordinary commands with dynamic executable names, non-recursive deletes and non-delete wrappers launch again; each refusal carries its own cause and remedy; bounded compound sequences with literal targets inside the granted roots are allowed; default-parameter, pipeline, alias, .NET, find, xargs, cmd and static interpreter payload deletes are judged; symlink components are resolved before dot-dot cleaning; the session temp root is a granted scope; the PowerShell parser is reused across calls; bilingual copy for the new codes.
Doctor now validates declared agent roots fatally, which the warning test added by the root-diagnostics change did not expect, so the two merged changes failed together. The warnings are printed first and the root rejection keeps its exit status.
Approval alone did not stop a recursive delete: in unattended Auto and YOLO a PowerShell sequence whose first assignment failed non-terminally went on to delete the real home directory. A pre-launch gate now parses the command (bash AST, PowerShell parser run as data) and refuses recursive deletes whose target is the home directory, a drive or filesystem root, a workspace ancestor, an expansion, variable or glob, or outside the workspace and granted roots, with a typed shell.destructive_target code; parser failure is shell.analysis_unknown. The gate runs after approval and before every launch route.
A write to a markdown or text file in a workspace with no declared or captured checks still added a stale_verification debt and, in Balanced, a readiness floor demanding a verification command. When every proven mutation is a supporting-role path and nothing declares or captured a check, neither applies. Mixed, opaque, unproven or partially observed effects and declared checks keep their behaviour.
The sidebar row and the account panel disagreed when a token was present but the identity lookup failed: the row showed a signed-in state with a question-mark avatar while the panel drew the sign-in form and hid the error. Both now render the signed-in state, the identity error, a retry and confirmed sign-out.
Adds a user-level [auto_archive] setting (enabled = false, days = 30 by
default; a project reasonix.toml cannot set it). While on, the controller's
ArchiveInactiveSessions archives conversations idle past the threshold from
the same background pass that sweeps recovery branches, so it stays off the
boot path. Archiving only flips catalog metadata and is reversible.
Never archived: pinned, leased by a live run (covers a run or a pending
answer/approval), carrying an unfinished turn, the controller's own
session, subagent traces and superseded versions. Restoring a conversation
counts as activity so it is not re-archived straight away.
The kernel is the authority for pins (BranchMeta.Pinned, tree rows carry
`pinned`). The window sends the union of its own pins and the kernel's
after the tree loads and retries on failure; the sweep, including the one
after enabling, refuses to run until a window has synced once.
Archived conversations stay findable: search returns them with an
"archived" marker, and rows archived by the sweep carry a mark so the
window says once how many were archived and that they can be restored.
Settings > Session gets GET/POST /auto-archive with persistence to the
user file and zh/en strings.
Refs #11516
An optional user-level [serve] share_port makes the direct link listen on a
chosen port instead of a random one. Unset keeps today's behaviour. The range is
1024-65535, a project reasonix.toml cannot set it, and the bind range is
unchanged. A taken port is a typed refusal (share.port_in_use). The settings
page saves it to the user config; changing it moves an open door and
phones re-pair.
Refs #11540
Headless `run` must leave stderr empty unless --debug is passed, so the
boot record of the resolved shell interpreter cannot be an Info log. It is a
diagnostic; the always-on counterpart is the Warn shellrun emits when a
launch actually fails.
Trim repolint comment blocks and move launch-failure logging out of RunForeground so the function stays within the 120-line limit. No behavior change.
Signed-off-by: Harbor404 <[email protected]>
A pinned [tools.shell] path was honoured on existence alone, so a pwsh.exe
that stats fine but will not start was held for the whole session while every
command failed at launch — the failure #10633 reported, still reachable
through the pinned-path branch after #10617 reworked auto-detection.
- Prove a pinned PowerShell the way the bash arm already proves its own, and
fall back to detection with a warning when it will not start.
- VerifyShell proves a pinned PowerShell too, so the settings screen refuses
one where it was typed rather than on every later command.
- powerShellLaunches answers false for a path that is not there: a missing
executable is not launchable, so callers that gate on it alone now agree.
- resolveShell takes the launch probe as an argument, which puts the decision
table ("pwsh will not start, 5.1 will") under test on any host.
- Detection that finds nothing says so instead of silently returning bare bash.
- Boot records the interpreter the session resolved to, and a launch-phase
spawn failure records the shell, its path, the caller and the error. The
desktop relays the sidecar's stderr into desktop-shell/logs/, which the
report found empty.
Windows end-to-end is not verified locally; the fallback is covered by the
injected probe on Linux and macOS.
Refresh the feedback unread count on window focus (at most every 30 s) and
every 10 minutes while a report is unsettled, with back-off to 1 h and no
error UI. A person with no open report is never polled. A hidden window keeps
polling only for someone who enabled the new feedback-reply notification.
Add a fourth notification kind, feedback_reply (default on under the master
switch), raised through POST /notifications/feedback-reply when the unread
count rises while the window is unfocused. The text never carries the reply.
Headless `run` must leave stderr empty unless --debug is passed, so the
boot record of the resolved shell interpreter cannot be an Info log. It is a
diagnostic; the always-on counterpart is the Warn shellrun emits when a
launch actually fails.
Trim repolint comment blocks and move launch-failure logging out of RunForeground so the function stays within the 120-line limit. No behavior change.
Signed-off-by: Harbor404 <[email protected]>
Project configuration could replace a user server entry and drop disabled_tools, while a stale call to a filtered tool was reported as unknown. Merge user and project disables, reject empty names, and carry a typed refusal through boot and hot-added MCP registration.
Move an existing contextual-tool helper unchanged so execute_one.go does not grow past the file-size ratchet.
A pinned [tools.shell] path was honoured on existence alone, so a pwsh.exe
that stats fine but will not start was held for the whole session while every
command failed at launch — the failure #10633 reported, still reachable
through the pinned-path branch after #10617 reworked auto-detection.
- Prove a pinned PowerShell the way the bash arm already proves its own, and
fall back to detection with a warning when it will not start.
- VerifyShell proves a pinned PowerShell too, so the settings screen refuses
one where it was typed rather than on every later command.
- powerShellLaunches answers false for a path that is not there: a missing
executable is not launchable, so callers that gate on it alone now agree.
- resolveShell takes the launch probe as an argument, which puts the decision
table ("pwsh will not start, 5.1 will") under test on any host.
- Detection that finds nothing says so instead of silently returning bare bash.
- Boot records the interpreter the session resolved to, and a launch-phase
spawn failure records the shell, its path, the caller and the error. The
desktop relays the sidecar's stderr into desktop-shell/logs/, which the
report found empty.
Windows end-to-end is not verified locally; the fallback is covered by the
injected probe on Linux and macOS.
2026-10-01 11:07:32 +08:00
6537 changed files with 502594 additions and 34218 deletions
Delete this section if the PR touches nothing under `desktop/frontend-next/src`
or the website pages.
-->
- Screenshots, before and after: light and dark, a wide window and a phone-width one (about 390 px). Attach them to this description.
- Entries moved, hidden or removed: none / list each one.
- Hiding or removing a visible control needs a linked issue and a release-notes line naming it. Do not retire UI with `display: none !important` or a feature flag.
- If a row of `docs/STUDIO_PARITY.md` changes, update it here.
## Issues
<!--
@@ -26,3 +54,7 @@ the source-to-target contract here: easing syntax, time units, callbacks,
cancellation, reduced-motion behavior, and failure fallback. Verification must
assert that the target API was actually called; a mock that silently skips it
does not count.
## Disclosure
- [ ] AI assistance was used for this change (optional; for transparency only, it does not change how the change is reviewed)
go test -count=1 -timeout=3m -json -run '^TestSubagent(PersistedStatusAdmission|UnknownStatusStillReadableForDiagnosis)$' ./internal/runtime/delegation | tee "$RUNNER_TEMP/subagent-status-admission.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
go test -count=1 -timeout=3m -json -run '^Test(CaptureDirectoryMaterializesContainedLinks|CaptureDirectoryRefusesUnsafeLinks|CaptureHookFileFlattensContainedLink|MaterializeStillRejectsLinkedRetainedFilesAndRoot|WindowsCompositionJunctions|WindowsLiveSkillJunctions|CompositionRetainsLinkedSkillAcrossRestart|CompositionLinkedSkillRejectsLinkedResources|CompositionLinkedExtensionResources|CompositionLinkedHookResources)$' ./internal/state/agentpreset ./internal/ext/skill ./internal/assembly/boot | tee "$RUNNER_TEMP/composition-links.jsonl"
go test -count=1 -p 4 -timeout=18m -json ./internal/runtime/isolation/... ./internal/platform/worktree/... ./internal/state/mutation/... | tee "$RUNNER_TEMP/isolation-owners.jsonl" |
go test -count=1 -timeout=3m -json -run '^Test(UsageScope.*|DelegatedUsageKeepsGoalOwner|EffectDelegatedUsageKeepsGoalOwner)$' ./internal/contract/event ./internal/session/control ./internal/assembly/boot | tee "$RUNNER_TEMP/goal-usage-scope.jsonl"
go test -count=1 -timeout=3m -json -run '^TestEffectPlannerConfiguredTaskBudget$' ./internal/assembly/boot | tee "$RUNNER_TEMP/planner-local-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(SemanticRootUsage|EffectRoutingRootBudget)$' ./internal/runtime/capability ./internal/assembly/boot | tee "$RUNNER_TEMP/controller-routing-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/capability", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(PreparedRootBudget|CoordinatorRootBudget|EffectCoordinatorRoot)' ./internal/runtime/agent ./internal/runtime/coordinator ./internal/assembly/boot | tee "$RUNNER_TEMP/coordinator-root-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/runtime/coordinator", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(GoalResumeRenewsPlannerBudget|GoalNonBudgetResumeKeepsPlannerBudget|GoalTokenBudgetPausesAndResumes|EffectGoalResumeRenewsPlannerBudget)$' ./internal/session/control ./internal/assembly/boot | tee "$RUNNER_TEMP/goal-planner-resume.jsonl"
go test -count=1 -timeout=3m -json -run '^Test(RootWall|EffectRootWall)' ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/root-wall-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RootToken|EffectRootToken|BudgetCountsKnownReportedTokens|ReportedToken|EffectReportedTotalTokenBudget)' ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/root-token-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RequestOnlyUsage|RunBudgetMissingUsage|TaskBudgetKnownCost|RunBudgetExplicitZero|TaskBudgetUsageCompleteness|TaskBudgetCostUses|EffectBudgetCompleteness|TaskBudgetGateIgnoresCostWhenUnpriced)' ./internal/contract/provider ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/budget-usage.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/contract/provider", "reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(HostStorageEnvironment|HostObjectStorage|HostExplicitStorageControls|DetachedStorageEnvironment|WorktreePreparationOwnsItsIndex|EffectIsolatedTaskOwnsGitStorage)$' ./internal/platform/gitcmd ./internal/platform/worktree ./internal/assembly/boot | tee "$RUNNER_TEMP/git-storage-environment.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/platform/gitcmd", "reasonix/internal/platform/worktree", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(InterruptedUsage|EffectAnthropicPartialUsageRecovery)' ./internal/model/anthropic ./internal/assembly/boot | tee "$RUNNER_TEMP/anthropic-interrupted-usage.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/model/anthropic", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RootWebSearch|EffectRootWebSearch)' ./internal/tools/websearch ./internal/assembly/boot | tee "$RUNNER_TEMP/root-websearch-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/tools/websearch", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^(TestProfileRootBudgetLifetime|TestProfileBudgetPreparationUsesTurnAdmission|TestEffectExplicitSkillRootBudget)$' ./internal/session/control ./internal/assembly/boot | tee "$RUNNER_TEMP/explicit-skill-root-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/session/control", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RootTriage|EffectRootTriage)' ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/root-triage-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RootAuxiliary|EffectRootAuxiliary)' ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/root-auxiliary-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(LocalBudgetUsesOccurrenceQuote|LocalCost.*|UsageQuote.*|EffectLocalCostUsesOccurrenceQuote)$' ./internal/contract/event ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/local-cost-quote.jsonl"
go test -count=1 -timeout=3m -json -run '^Test(AuxiliaryUsageObserver|BoundedUsageObserver|RootBounded|EffectRootBounded)' ./internal/contract/provider ./internal/model/boundedllm ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/bounded-root-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
platform=$(git diff -z --name-only --no-renames "$BASE_SHA" HEAD | bash scripts/ci-platform-scope.sh)
if git diff --name-only --no-renames "$BASE_SHA" HEAD |
grep -qE '^(desktop/electron/|desktop/packaging/|desktop/cmd/update-helper/|desktop/cmd/studio-manifest/|desktop/go\.(mod|sum)$|cmd/reasonix-studio-host/|internal/platform/(update|appupdate)/|go\.(mod|sum)$|scripts/(linux-install-smoke|verify-linux-package|verify-linux-appimage|verify-studio-artifacts)\.sh$|scripts/(windows-signing-lib|verify-windows-package|verify-windows-authenticode|install-smoke)\.ps1$|\.github/workflows/(studio|release-studio)\.yml$)'; then
# The update channel is the signed manifest. A second answer to which
# version is current is not a convenience. The .blockmap is the same
# channel's differential index and carries the release prefix, so it also
@@ -233,47 +623,17 @@ jobs:
echo "page: ${page:-<missing>}"
test -n "$host" -a -n "$page"
# Read off the package rather than off the config that describes it. Both
# of these went wrong the same way once: electron-builder names a package
# after package.json unless told, and fpm records the owner it finds on
# disk. Either one leaves a Studio that cannot update itself on Linux and
# says nothing about it until a user asks.
- name:The .deb is Studio's own package and carries what updates it
- name:Linux packages carry their declared architecture and update payload
if:runner.os == 'Linux'
run:|
deb=$(ls dist/ReasonixStudio-linux-*.deb)
name=$(dpkg-deb -f "$deb" Package)
if [ "$name" != "reasonix-studio" ]; then
echo "::error::the package is $name; dpkg holds that beside the installed reasonix-studio instead of upgrading it, and update.StudioLine asks for it by name"
exit 1
fi
dpkg-deb -c "$deb" > contents.txt
# The path is the polkit action's exec.path annotation, so it is the
# only one authorization is granted for; root because polkit will not
# run a helper the invoking user could have written. Either spelling
# of root: dpkg-deb prints the numeric ids it cannot map, and which
# one it prints is a property of the machine reading the package
# rather than of the package.
if ! grep -qE '^-rwxr-xr-x (root/root|0/0) +[0-9]+ .* \./usr/lib/reasonix-studio/reasonix-studio-update-helper$' contents.txt; then
echo "::error::no root-owned executable update helper at the path the polkit action names"
grep reasonix-studio-update-helper contents.txt || echo "(not in the package at all)"
exit 1
fi
if ! grep -q './usr/share/polkit-1/actions/io.reasonix.studio.update.policy$' contents.txt; then
echo "::error::the polkit action is not in the package, so pkexec has nothing to authorize against"
if ($run.ExitCode -eq 0) { throw 'the arm64 installer exited 0 on an x64 machine' }
if (@(Get-ChildItem -Recurse -Force $target -ErrorAction SilentlyContinue).Count -ne 0) { throw 'the arm64 installer wrote into the target on an x64 machine' }
<p align="center"><strong>Open source · MIT · a single Go binary</strong></p>
<h3 align="center">A reliable coding agent for complex software engineering tasks.</h3>
<p align="center">One local engine, four ways in — terminal, desktop app, browser, or your editor over ACP. Plan mode, permissions, a workspace sandbox and per-turn checkpoints keep a long autonomous run something you can still read and undo.</p>
<p align="center"><strong>Open source · MIT · runs on your computer, model of your choice</strong></p>
<h3 align="center">An open-source coding agent for your terminal, desktop, browser and editor.</h3>
<p align="center">It reads your project, edits files and runs commands and tests. Have it ask before each step, or work on its own within limits you set. Files its edit tools changed can be rewound. New here? Start with the Studio desktop app.</p>
<p align="center">Maintained by <strong>Huahui Yu</strong>.</p>
> [!IMPORTANT]
@@ -57,13 +57,59 @@
<br/>
## ❤️Sponsor
<table><tr><td width="180" align="center" valign="middle"><a href="https://www.rightapi.ai/register?aff=e083bf73" target="_blank" rel="sponsored noopener noreferrer"><img src="./.github/sponsor/rightcode.png" alt="Right Code" width="64"></a></td><td valign="middle">Thanks to Right Code for sponsoring this project! Right Code reliably provides routing services for models such as Claude Code, Codex, and Gemini, with both pay-as-you-go and monthly subscription billing options available. Invoices are available upon top-up, and enterprise and team users can receive dedicated one-on-one support. Register via <a href="https://www.rightapi.ai/register?aff=e083bf73" target="_blank" rel="sponsored noopener noreferrer">this link</a>.</td></tr><tr><td width="180" align="center" valign="middle"><a href="https://www.packyapi.ai/register?aff=rIbQ" target="_blank" rel="sponsored noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="./.github/sponsor/packycode-dark.svg"><img src="./.github/sponsor/packycode-light.svg" alt="PackyCode" width="80"></picture></a></td><td valign="middle">Thanks to PackyCode for sponsoring this project! Access leading AI models through PackyCode with one API endpoint and one API key, with automatic failover and dedicated high-speed routes for Codex and Claude Code. Register via <a href="https://www.packyapi.ai/register?aff=rIbQ" target="_blank" rel="sponsored noopener noreferrer">this link</a>.</td></tr></table>
## What it is
Reasonix is an open-source AI coding assistant. The program runs on your computer; the AI model is a service you configure, which receives what it reads. You describe a task in plain language; it reads code, edits files and runs commands and tests inside the project folder you choose, and shows you each step.
You pick the model: a DeepSeek preset is built in, any OpenAI-compatible service is one config entry, and you pay the model provider directly.
## What you get
- **Read, edit, run.** It reads code, edits files and runs tests and commands, all inside your project folder.
- **Permission levels.** Have it ask every time, or let everyday actions run on their own; your forbid rules always hold. To see the approach first, use plan mode: it starts work only once you approve.
- **Rewind.** A file is saved before it changes, so you can rewind by conversation turn without touching your git history. Only files its edit tools changed are covered, not what a shell command did.
## 30-second start
Download, add a model key, open a project. New here? Start with the Studio desktop app.
1. Download and install the desktop app, Reasonix Studio, from the [releases page](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true).
2. Open it and enter your model provider and key when asked.
3. Open your project folder and write what you want, for example "add retry on failure to the http client".
4. At the level you chose, it stops and asks before anything that needs your say-so. Allow it to continue, or refuse.
Prefer a terminal? Download the `reasonix` command-line tool, run `reasonix setup` to pick a model, then run `reasonix`. See [Install](#install) and [Quick start](#quick-start).
## Four ways to use it
- **Desktop app (Studio).** A graphical app; the best place to start.
- **Terminal.** A full-screen text interface; `reasonix run "task"` also works in scripts.
- **Browser.** `reasonix web` opens a web interface on your own machine.
- **Editor.** Install the extension in VS Code; other editors that support ACP (Agent Client Protocol, a common way for an editor to connect to an AI assistant) can connect to `reasonix acp`.
All four are the same program underneath. What follows is for advanced users and contributors.
## Use it from DeepSeek Harness
DeepSeek Harness (`dsh`) can hand a task to Reasonix as a subagent over ACP. This repository publishes a plugin index for it:
1. Run `reasonix setup` on the machine that runs `dsh`. In the DSH Web UI turn on Custom plugin markets (Plugins, More) and add this index in the Discover tab: `https://raw.githubusercontent.com/esengine/DeepSeek-Reasonix/studio/integrations/dsh-plugin/market-index.json`
2. Install "Reasonix subagent" from it.
3. Open the plugin's page and turn its switch on. While it is off the model has no `reasonix` tool and may run `reasonix -p` in its own shell, which looks like a delegation and is not one.
Setup details, the permission policy and limits are in [Using Reasonix from DeepSeek Harness](./docs/DSH.md).
## Versions
Reasonix ships on two lines. See the [version roadmap announcement](https://github.com/esengine/DeepSeek-Reasonix/discussions/10748) for the reasoning behind the split.
`verify-page.sh` is the one to run after a change: it starts the kernel with the
same arguments `Kernel.kt` uses (`--auth token`, `--token-file`, `--port-file`,
`-page`) and checks that it binds a port and answers 200. Without a device, that
is as deep as the chain can be verified.
## Where the code is
| File | Owns |
| --- | --- |
| `MainActivity.kt` | The WebView, the back gesture, downloads, external links, renderer restart |
| `Kernel.kt` | Unpacking the page, starting and stopping the kernel, waiting for the port, health probes, logs, the workspace directory |
| `KernelService.kt` | The foreground service that keeps a long turn alive |
| `NativeBridge.kt` | `@JavascriptInterface`: what the page may ask the phone for |
| `BrowserShellServer.kt` | Loopback HTTP server the kernel's browser tools drive the overlay WebView through |
| `Downloader.kt` | Writing a kernel-produced file through the system document picker |
The page reaches the shell through `window.reasonixHost` and `ReasonixNative`,
defined in `desktop/frontend-next/src/port/host.ts` (`AndroidHost`).
**Phone and desktop share one page build.** What differs only on a phone is
decided at runtime, not by a second set of components. `phoneShell()` in
`src/port/host.ts` and the `[data-shell="android"]` selector in `mobile.css` both
read `<html data-shell="android">`, which `index.html` sets before the bundle
loads.
One attribute feeding both is what keeps style and components from disagreeing
about what a phone is.
## What the page may ask the phone for
`NativeBridge` is reachable from whatever document the WebView holds, so every
method that touches the device asks `trusted()` first.
The activity answers by comparing the **main frame's** origin against the
kernel's and against the one paired computer the person scanned a code for. A
page that navigated elsewhere is not trusted, even though it may still hold the
object.
Five methods answer without the check — `version`, `systemTheme`, `safeArea`,
`setLayerOpen`, `setRunning` — because they report back what the page already
knows about its own window and grant nothing.
`openInApp(url)` is the pairing decision, not a navigation one: it loads a URL in
this window only for the origin the scanner just returned, and only once.
Without that, any page holding the kernel origin could hand itself the bridge.
External links leave through the browser. A non-web scheme is routed to the
system only for `mailto:` and `tel:`; everything else is dropped, because an
`intent://` URL can start an arbitrary exported activity with caller-supplied
extras.
## Permissions, and what they cost
`MANAGE_EXTERNAL_STORAGE` — opening a folder on the phone as a workspace. The
kernel is a Go binary: it opens real paths with `open(2)`, and SAF hands back a
`content://` URI it cannot open.
Requested from the folder-picker action, never at launch. Android shows no dialog
for it and opens system settings instead.
**There is no OS-level sandbox around this kernel.** The PR sets the sandbox to
off on Android, so confining the agent to the chosen folder is Reasonix's own
write-root check and nothing else. A shell tool with all-files access can reach
all shared storage.
`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` — Doze freezes the app group, which stops
a turn that is still running. Asked once when a turn actually runs, never at
launch, and the person can refuse. Refusing costs battery life during long turns
and nothing else.
**Google Play restricts this permission**: fine for a sideloaded build, a
store-policy risk if this is ever listed.
`FOREGROUND_SERVICE` / `FOREGROUND_SERVICE_DATA_SYNC` — keeping the kernel alive
while the screen is off. `dataSync` is capped per 24 hours from Android 15. It is
the closest honest description of "an agent is working".
`CAMERA` — reading the pairing code a computer shows. Requested when the person
asks to scan, never at launch. `ScanActivity` is not exported.
`READ_EXTERNAL_STORAGE` / `WRITE_EXTERNAL_STORAGE` — the same folder walk below
API 33. `maxSdkVersion`-gated; above 33 they are superseded by the permission
above.
`allowBackup=false` with explicit extraction rules, so a `keystore` or a token in
app storage never leaves the device in a cloud backup.
## Cleartext HTTP
The kernel serves its page on `127.0.0.1:<port>`, and a paired computer serves
the same page over its LAN address. Both are plain HTTP.
Neither address can be written into a `network-security-config`: the port is
chosen at start, the LAN address at scan time, and the format matches literals
and domains, not subnets.
So the config permits cleartext and the boundary is in the code instead.
`shouldOverrideUrlLoading` sends every other http(s) origin to the browser, and
the bridge answers only the kernel and the just-paired computer. A plaintext page
on the LAN cannot become the document holding the bridge.
## What goes into the APK
| Content | Size | Why it is shaped this way |
| --- | --- | --- |
| `lib/arm64-v8a/libreasonix.so` | 45 MB | Go static binary, stripped. It is a `.so` rather than a file in `assets/` because only `nativeLibraryDir` is guaranteed executable — a binary copied to app storage fails on a device whose data partition is mounted `noexec`. |
| `assets/page/` | 14 MB | The frontend build, including the Inter and Noto Sans SC fonts, `noCompress` |
| `assets/LICENSE` | 1 KB | The repository's MIT licence, verbatim. MIT requires the notice and the licence text to accompany a binary distribution. |
`assets/LICENSE` is generated by `sync-page.sh` from the repository's root
`LICENSE`, not committed a second time: a checked-in copy drifts when the root
file is updated.
It sits in `assets/` rather than `assets/page/` because that directory is removed
and rebuilt on every build.
A release APK is about 31 MB.
## Building
**The Gradle wrapper is committed**, jar and all. `build.sh` refuses to run
without `gradlew` rather than generating one from whatever `gradle` is on `PATH`:
the dependency versions are part of the build.
`gradle-wrapper.properties` pins `distributionSha256Sum`, so a mirror that serves
a different distribution zip fails verification instead of being unpacked and
run. Dependency repositories are the official ones only — a mirror belongs in a
user-level init script.
**Signing.** A release with no key configured comes out **unsigned**
(`app-release-unsigned.apk`) with a warning. It used to fall back to the debug
key, which produced an artifact that looked shippable and was signed by a
per-machine key nobody chose: it could not install over the previous build.
Point the build at a real key by writing `keystore/keystore.properties`
(`storeFile`, `storePassword`, `keyAlias`, and optionally `keyPassword`) or by
setting the matching `REASONIX_KEYSTORE*` environment variables. Key material
stays out of the repository and out of build logs.
Debug builds always use the debug key. Signing a debug build with the release key
would let anyone who can read the keystore produce a build that installs over the
shipped one under the same signature.
**Non-ASCII checkout paths.** AGP refuses to build under a path with non-ASCII
characters on Windows unless `android.overridePathCheck=true` is set. That is a
property of one machine's disk layout, not of this project, so it is not in the
repository's `gradle.properties`.
Set it in a user-level `~/.gradle/gradle.properties` or pass
// The extraction script: prefer article/main for content pages,
// fall back to body. innerText preserves visual line breaks. It
// also stamps a ref on every element a person could act on, so the
// kernel can name one in an act step the way the desktop backend
// names an accessibility node. A ref already on an element is kept,
// so it stays the same across snapshots of one document; a fresh
// document starts the numbering over, and the kernel only accepts a
// ref from the snapshot it last read.
valscript="""
(function(){
var seq = window.__rxRefSeq || 0;
var used = {};
var marked = document.querySelectorAll('[data-rx-ref]');
for (var i = 0; i < marked.length; i++) { used[marked[i].getAttribute('data-rx-ref')] = true; }
function roleOf(el) {
var tag = el.tagName.toLowerCase();
var role = (el.getAttribute('role') || '').toLowerCase();
if (role) return role;
if (tag === 'a') return 'link';
if (tag === 'button') return 'button';
if (tag === 'select') return 'combobox';
if (tag === 'textarea') return 'textbox';
if (tag === 'input') {
var t = (el.getAttribute('type') || 'text').toLowerCase();
if (t === 'checkbox' || t === 'radio' || t === 'submit' || t === 'button' || t === 'reset') return t;
return 'textbox';
}
return 'generic';
}
function nameOf(el) {
var n = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('name') || '';
if (!n) { n = (el.innerText || el.textContent || '').trim(); }
return n.replace(/\s+/g, ' ').slice(0, 160);
}
var sel = 'a[href],button,input,select,textarea,[role=button],[role=link],[role=checkbox],[role=radio],[role=tab],[role=menuitem],[contenteditable=true]';
var els = document.querySelectorAll(sel);
var elements = [];
for (var j = 0; j < els.length; j++) {
var el = els[j];
var ref = el.getAttribute('data-rx-ref');
if (!ref) {
do { seq++; ref = 'e' + seq; } while (used[ref]);
el.setAttribute('data-rx-ref', ref);
used[ref] = true;
}
var raw = (el.value != null && String(el.value) !== '') ? String(el.value).slice(0, 160) : '';
Five harnesses live under `benchmarks/`; `cmd/e2ebench` also exposes a
Six harnesses live under `benchmarks/`; `cmd/e2ebench` also exposes a
SWE-bench Verified mode:
-`e2e/` — the committed end-to-end task suite, driven by
@@ -19,6 +19,10 @@ SWE-bench Verified mode:
-`fanout-width/` — a shape-instructed fleet whose members share nothing, for
pricing what running them at the same time is worth. See
[Pricing the run graph](#pricing-the-run-graph) below.
-`realrepo/` — long tasks over a pinned snapshot of this repository, for
measuring changes that cut model round trips (13–31 requests per task on
`deepseek-flash`). See
[its README](realrepo/README.md).
## Directory layout
@@ -416,11 +420,11 @@ own outcome).
| Flag | Default | Purpose |
| --- | --- | --- |
| `-mode` | `suite` | `suite` \| `diff` \| `swebench` \| `compare` \| `traj` (`diff` generates tests for the PR diff; `swebench` runs the official per-instance evaluation; `compare` renders KPI/Pareto readouts from 2+ `-json` reports; `traj` re-digests recorded trajectory files without spending tokens). |
| `-mode` | `suite` | `suite` \| `diff` \| `swebench` \| `compare` \| `traj` (`diff` generates tests for the PR diff; `swebench` runs the official per-instance evaluation; `compare` renders KPI/Pareto readouts from 2+ `-json` reports; `traj` re-digests recorded trajectory files without spending tokens; `audit` checks a batch's published figures against its retained rows). |
| `-task` | *(all)* | Suite mode: run only these comma-separated task IDs (e.g. `-task fix-add-bug`); unknown IDs fail with the available list. |
| `-attempts` | `1` | Suite and diff modes: retry a task until an attempt passes, up to N; enables the `Pass@≤N` KPI, and TTCS charges a retried solve with its failed attempts' wall. |
| `-trials` | `1` | Suite mode: run every task N times and keep every outcome. Adds a **Reliability** line: `pass^N` (tasks that passed every trial),`pass@N` (any trial), mean `pass@1` over all trials, and the flaky tasks by name. `Pass@≤N` asks whether the agent can solve a task; `pass^N` asks whether it does so every time. Cannot be combined with `-attempts`. |
| `-trials` | `1` | Suite mode: run every task N times and keep every outcome. Adds a **Reliability** line: `pass^N` (complete tasks that passed every trial) and`pass@N` (complete tasks with any pass), both over the tasks that ran all N trials only (`n/a` when none did), mean `pass@1` over every graded trial, and the flaky tasks by name; a task the token budget cut short is listed on an **Incomplete** line and is in neither. `Pass@≤N` asks whether the agent can solve a task; `pass^N` asks whether it does so every time. Cannot be combined with `-attempts`. |
| `-tape-record DIR` | — | Suite mode, with `-meter`: record every provider exchange at the meter, one tape per task and trial (`DIR/<task>/trial-N/0001.request.json`, `.response.json`, `.response.body`). Taped runs use a fixed workdir path and pin the seeded files' times, because both reach the model through the workspace block and file listings. |
| `-tape-replay DIR` | — | Suite mode, with `-meter`: answer each run from its tape instead of the provider, and add a **Replay** line naming the first request whose body differs from the recorded one, down to the message (`request 2, message 4 (tool)`), the tool list, or a field. The diverging request is kept as `NNNN.replayed.json` beside the recorded one. A kernel change that should not alter what the model is asked replays with every request matched. |
| `-bin` | `reasonix` | Path to the reasonix binary. |
@@ -429,6 +433,7 @@ own outcome).
| `-ablate` | *(none)* | Ablation arm: comma-separated subsystems to switch off — `evidence`, `planner`, `subagent`, `retrieval`, `compaction`, `upstream` and `recall-search`; `none` \| `all`. |
| `-json` | *(none)* | Write the JSON report here (optional). |
| `-batch` | *(none)* | Suite mode: write `results.jsonl` (one row per attempt) and `batch.json` (manifest v1) here, so the report's figures can be audited later. Audit mode: read the batch from here. See [Auditing a batch](#auditing-a-batch). |
| `-trajectories` | *(none)* | Suite mode: write one `<task-id>.trajectory.jsonl` per task into this directory (the agent's full event stream with timestamps — see `reasonix run --trajectory`). The report gains a time-attribution line (tools vs. model) and each JSON result a `trajectory` digest. |
| `-force-planner` | `false` | Suite mode: prefix each prompt with a plan-first directive so the two-model turn engages regardless of the planner gate. Use for the "with planner" arm of an A/B; results carry `plan_forced` so arms are only comparable with equal forcing. |
| `-anchor` | `blind` | Suite mode: which hypothesis the agent holds before it looks at anything — `blind` (none, the control) \| `correct` \| `wrong`. The seeded arms prefix each prompt with the task's authored seed and **skip** tasks that have none, so an unseeded control run never lands in a seeded denominator. Results carry `anchor`. See [Anchor resistance](#anchor-resistance). |
from `results.jsonl`, prints the manifest's claim beside the recomputed value,
and exits non-zero naming any figure that moved. It also refuses a batch whose
manifest is missing, records no commit, or whose rows are truncated.
This is an **aggregation audit, not a replay**: it proves the published figures
follow from the retained rows. It does not re-execute the model, tools or
environment, and cannot show the experiment would reproduce. A real replay would
need the commit, a corpus fingerprint, the model and provider configuration, the
seed, the flags, the raw results and the runtime version; it is not implemented.
`schema_version` is top level. The rest is three groups:
| Group | Fields | Answers |
| --- | --- | --- |
| `over` | `command.mode` and `command.axes` (every flag that changes what a figure means; paths omitted); `tasks.count` and `tasks.digest` (sha256 over sorted, NUL-separated task ids — the corpus identity, never the task text); `filters`; `denominator` (`unit`, `definition`, `value`) | what the figures are over |
| `producer` | `commit` (or an explicit `unknown`, never a guess), `commit_source`, `dirty`, `model`, `provider`, `seed`, `started_at`, `finished_at`, `harness_version`, `go_version` | which run produced them |
| `trust` | `results_sha256`, `claimed` (the headline figures), `ledger`, `retention` | why they can still be checked |
The ledger accounts for every planned run, and the audit checks that the counts
form a funnel (`scored ≤ completed ≤ attempted`, `failed ≤ scored`,
`scored = pass1 + failed`, `rows = attempted + skipped`). A row count is never a
denominator: `denominator.value` is a count of scored tasks, and `rows` is
published only to show what was retained.
| Count | Meaning |
| --- | --- |
| `planned` | graded runs the batch set out to attempt: filtered tasks × trials |
| `attempted` | retained rows for runs that started (skipped rows are not attempts) |
| `completed` | attempts that ended and left a row; this harness writes a row only after an attempt ends, so it equals `attempted` and the equality is checked, not assumed |
| `scored` | completed first attempts of solvable, non-skipped tasks — the pass@1 denominator |
| `failed` | scored attempts the grader did not pass |
| `unmeasured` | attempts whose cost and tokens are unknown because no metrics landed; kept out of spend totals, never counted as zero |
| `excluded` | rows kept out of the accuracy denominator: skipped, `no_solution`, and retry entries |
Spend and cache fields are omitted from `claimed` when nothing was measured, so
an absent figure reads as unknown rather than as a measured zero. `seed` is
omitted for the same reason: the harness does not set a sampling seed.
`results_sha256` covers the values and the presence of every row; line endings,
indentation and key order are normalised away because none of them is evidence,
while number spelling is kept (a rewritten `1` is not `1.0`).
A successful audit records the verifier version (`audit.verifier_version`) and
the digest it checked in `batch.json`, so a re-verification can tell which code
checked the batch.
`results.jsonl` is retained unredacted beside the manifest — task prompts and
agent notes included — and is not public. The manifest itself carries a task-id
digest, no prompt text, no absolute paths, no endpoints or keys and no model
output. A claim whose rows are gone cannot be audited: there is nothing to
recompute from, and this tool will not certify it.
## Dataset retention
Keep every `-json` report and`-trajectories` directory from real runs: they
are the accumulating corpus — per-task contracts-to-be, full event
Keep every `-json` report,`-trajectories` directory and `-batch` directory from
real runs: they are the accumulating corpus — per-task contracts-to-be, full event
trajectories, checkpoint oracle verdicts, stop curves and phase traces — that
any future offline learning (routing, stop policies, budgets) would train
and evaluate on. The control plane stays deterministic and interpretable
1. Measured on 2026-10-17 against the official `https://api.deepseek.com` OpenAI adapter, using the model returned by `/models`: `deepseek-flash`. Thinking was disabled. Inputs were synthetic; no workspace history or credentials are recorded here.
2. Each of three text and three rich-history trials sent a main request to warm the prefix, then called the actual agent summary path, then an independent summary control. Rich history included a synthetic closed `read_file` call, result and reasoning. Warmup outputs were discarded, never executed.
3. A summary had a 1,024-token output cap; warmups had 64. Success means a heading, preservation of the fixture's `ParseDuration` and zero-value constraint, no emitted tool calls, and `finish_reason=stop`. This checks the fixture's contract, not arbitrary-summary factual completeness.
4. First-attempt reuse success counts successful reused summaries / six reused summaries. Fallback rate counts independent requests initiated by the reuse coordinator / six reuse trials; the six explicit independent controls are excluded. Both were 6/6 success and 0/6 fallbacks. Independent controls also succeeded 6/6.
5. The raw result file records provider-reported usage and full stream latency, measured from adapter `Stream` until channel close. Token rows below sum only the three indicated summary requests; they exclude warmup, model listing and any earlier exploratory requests. Cache residency was uncontrolled; prior probes had warmed these synthetic prefixes.
## Results
| History | Shape | N | Prompt | Hit | Miss | Output | Median ms | Estimated USD |
1. Estimates use the [official pricing](https://api-docs.deepseek.com/quick_start/pricing/) checked on the measurement date: off-peak Flash USD per million tokens, hit 0.003, miss 0.15, output 0.6. October 17 was a Saturday. Formula: `(hit*0.003 + miss*0.15 + output*0.6)/1_000_000`. These are token estimates, not account deductions.
2. Reuse hit fractions were 82.7% (text) and 81.3% (rich), versus 63.1% and 57.3% for controls. This small fixture does **not** establish cost savings: retaining the system/catalog increased both total input and input cost. Text total cost increased 6.8%; rich total decreased 12.5% because output was shorter, not because input was cheaper.
3. The six warmups consumed 7,032 prompt and 231 output tokens, estimated USD 0.000346680. All 18 recorded generation calls together cost an estimated USD 0.002491470. No failed-digest retry occurred in these live trials; the deterministic fallback tests establish its behavior and accounting.
4. A rejected nonempty reused answer earns one independent-shape fallback. A separate truncation retry may enlarge the output cap, for at most three logical model calls total. Every actual attempt emits and records its own spend; returned usage is their sum. Empty output, stream errors and cancellation do not earn shape retries.
5. These sequential, six-pair trials establish interoperability for one model, mode and short fixture. They do not estimate production failure probability, thinking-mode behavior, other models, cold-cache cost, or large-history savings.
## Reproduction
1. Provide a test key through an existing secret environment configuration. Do not put credentials in source, command arguments or captured logs. Select a model currently returned by `/models`.
2. Run the opt-in probe; it skips without `DEEPSEEK_API_KEY`. The dedicated build tag avoids enabling unrelated live tests.
```sh
REASONIX_LIVE_MODEL=deepseek-flash go test -tags live_compaction ./internal/runtime/agent -run '^TestLiveCompactionPrefix$' -v -count=1
```
3. This probe exercises actual summary construction and the HTTP adapter. The hermetic `TestEffectCompactionSharesThePreviouslySentPrefix` separately exercises manual, automatic pressure and rich history through `boot.Build`, comparing actual JSON request-prefix and schema bytes. Its sparse-history case also proves an interleaved retained user turn survives the fold.
There are no measured results yet. Component tests and scripted providers prove
delivery, validation and replay; they do not measure a model's choice of interface
or a person's effort.
## Paired runs
Run each task twice in fresh desktop sessions: ordinary text/ask interaction and
native surfaces. Use the same model, sampling settings, initial workspace,
permission mode and task prompt. Record commit, model, configuration and the
native-surface capability setting. Alternate condition order across repetitions.
Keep the hidden tool out of the provider prefix in both conditions; only its
availability through capability discovery differs.
The baseline may batch questions or accept a single text reply. Never force it to
ask one question per field. Do not tell either model how many turns to use or
which component to render. If the surface-enabled model chooses text, retain
that run in the surface condition. Interface choice is part of the experiment.
The operator knows the private values below but does not volunteer them before
the model requests the corresponding information. Supply all requested values
together, regardless of presentation. Allow inspection and local editing before
submission. Save the trajectory and an observer ledger locally; raw transcripts
and draft content must not enter the aggregate report.
## Fixed tasks
| Task | Initial prompt, identical in both conditions | Private operator input | Completion criterion |
| --- | --- | --- | --- |
| `release-brief` | Prepare a release brief. Ask me for the audience, release date and whether this is a staged rollout before writing it. | audience: existing desktop users; date: 2026-11-03; staged rollout: yes | Final brief contains all three values and adds no release commitment. |
| `storage-choice` | Help me choose storage for a small desktop app. Present reasonable choices, ask me to choose and ask whether offline operation is required. Then summarize the decision. | storage: SQLite; offline: yes | Final decision states SQLite and offline operation, with no installation or file changes. |
| `draft-revision` | Draft a two-line release announcement and let me revise it before returning the final version verbatim. | Replace with the exact string ` Desktop release ready.\nKeep existing settings.\n` (decode the escapes). | Final returned string has exactly the specified leading spaces and newlines. Formatting wrappers are excluded from the comparison. |
| `preview-decision` | Show a plain-text before/after preview changing retry count from 2 to 3. Ask whether to keep that change and whether to include a release note. Return a decision summary; do not change files. | keep change: yes; release note: no | Preview shows 2 to 3; final summary preserves both decisions; workspace is unchanged. |
Freeze this task set before collecting results. Record a new protocol version if
prompts, private inputs, completion criteria or metric definitions change.
Neither the model's final claim nor a successful input acknowledgment establishes
completion. The observer checks the resulting output against the criterion.
## Metric definitions (version 1)
The anchor is acknowledgment of the initial task prompt. The observation ends
when the criterion is verified, the operator abandons the task, or the run fails.
The initial prompt is excluded from subsequent-input counts because both
conditions start from the same request.
| Metric | Count and population | Exclusions |
| --- | --- | --- |
| `acknowledged_inputs` | Distinct operator submissions acknowledged after the anchor: chat replies, ask answers, surface answers and surface actions. Includes acknowledged input later withdrawn or superseded. | Initial task prompt, local draft edits, retries with the same acknowledged request identity, automatic dispatch, replay and observer grading. This counts input exchanges, not provider rounds. |
| `submission_attempts` | Explicit operator send attempts, including rejected submissions and manual retries. | Automatic transport retries, replay, local edits and model tool calls. |
| `withdrawals` | Successful operator withdrawals of admitted input. | Failed withdrawals, automatic cancellation and replay. |
| `completed` | Runs whose output meets the fixed criterion. Denominator: every started run in each condition. | None; failures and abandoned runs remain in the denominator. |
Record these in a content-free ledger: task id, repetition, condition, observer
event ordinal, event type, opaque request identity, acknowledgment status and
completion verdict. Record the source trajectory reference and observed start
and end anchors alongside it. Distinguish manual observation from host-recorded
events. Do not copy labels, prompts, private values, drafts or answer strings into
the ledger. Identities are local correlation handles, not account identifiers.
The current pane counters and rendered trajectory export cannot reconstruct all
acknowledgments, refusals and withdrawals. Do not infer them from `ask` calls,
tool-result counts, final prose, message wording or card mounts. Until structural
acknowledgment records cover every channel, an observer ledger is required.
## Report and decision
Report the number started, completed, failed and abandoned for each condition,
then paired input-count differences for repetitions where **both** conditions
meet the same criterion. List excluded pairs and their reason beside that
comparison. Also report submission attempts and withdrawals; fewer admitted
inputs with more failures is not automatically less effort.
Keep per-task results visible. Do not combine scripted fixtures with observed
model/operator runs or describe this small task set as an account-wide result.
Report model/configuration differences and missing evidence as limitations.
The RFC's benefit remains unproven until observed trajectories show fewer input
exchanges to the same verified outcome. These results alone do not justify P3;
adaptive layout or MCP Apps needs a measured problem in addition to this result.
The owner-requested map, comparison and dashboard workspaces extend the delivery
scope independently of this study. Their scripted acceptance fixtures verify
rendering and interaction only; they do not count as observed benefit evidence.
prompt="Add a function add(a, b) returning a+b to util.py."
class="generic"
max_steps=8
timeout_sec=180
Loaded 100 of 6537 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.