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).
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.
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.
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.
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: 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
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.
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.
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.
2026-10-15 14:51:37 +08:00
2403 changed files with 140471 additions and 8289 deletions
# A pull request with no platform-relevant change (scripts/ci-platform-scope.sh)
# moves the Windows and macOS legs onto an ubuntu runner with every step
# skipped, so the check names still report without a scarce runner. A push to
@@ -272,7 +371,7 @@ jobs:
arm64=false
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-studio-artifacts)\.sh$|scripts/(windows-signing-lib|verify-windows-package|verify-windows-authenticode|install-smoke)\.ps1$|\.github/workflows/(studio|release-studio)\.yml$)'; then
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
@@ -93,6 +93,16 @@ Prefer a terminal? Download the `reasonix` command-line tool, run `reasonix setu
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.
@@ -151,7 +161,7 @@ pre-releases):
| --- | --- | --- |
| macOS | `.dmg` or `.zip` | Apple Silicon (`arm64`) / Intel (`amd64`) |
| Windows | Installer `-installer.exe` or portable `.zip` | x64 |
| Linux | `.deb` or `.rpm` | x64 (`amd64`) / ARM64 (`arm64`, aarch64) |
| Linux | `.deb`, `.rpm`, or `.AppImage` (download only) | x64 (`amd64`) / ARM64 (`arm64`, aarch64) |
Every package ships with a `.minisig` signature, and the release carries
`SHA256SUMS`. Once installed, Studio updates itself in place.
@@ -240,12 +250,14 @@ For advanced CLI usage and configuration, see the **[CLI reference](./docs/CLI.m
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.
// The split counts are absent; the checkpoint-derived ones are not.
line+=fmt.Sprintf("\n\n**Correct boundary**: **mutations before** %d · **regression-after-correct** %s — nothing to average: the runs reached a correct state, but none recorded a trajectory, so the boundary was never split",
ablateFlag:=flag.String("ablate","","ablation arm: subsystems to switch off ("+ablation.ModuleList()+"; none|all)")
foldIndexFlag:=flag.String("fold-index","","fold-index arm: how much of the model-visible address list to keep (default|half|quarter|off)")
outMD:=flag.String("out","","write the markdown report here (default: stdout)")
trajDir:=flag.String("trajectories","","suite mode: write one <task-id>.trajectory.jsonl per task into this directory")
trajDir:=flag.String("trajectories","","suite mode: write one <task-id>.trajectory.jsonl per task into this directory (<task-id>.trial-N.trajectory.jsonl per trial under -trials)")
forcePlanner:=flag.Bool("force-planner",false,"suite mode: prefix each prompt with a plan-first directive so the two-model turn engages regardless of the planner gate")
anchorFlag:=flag.String("anchor",anchorBlind,"suite mode: hypothesis arm — blind (no hypothesis) | correct | wrong; correct/wrong prefix each prompt with the task's authored seed and skip tasks that have none")
outJSON:=flag.String("json","","write the JSON report here (optional)")
@@ -585,10 +585,11 @@ func runTask(cfg suiteConfig, t task) result {
- ⚠️ `a` is **dominated** by `b`: at least as accurate and no slower — the unambiguous alarm
- ✅ `b` is on the Pareto frontier (50% solved, TTCS median 45.0s)
### Per-class winners
| Class | `a` | `b` | Winner |
|---|---:|---:|---|
| bugfix | 100% · 1m00s | 100% · 45.0s | b |
| perf | 0% · — | 0% · — | a |
<sub>Per-solved figures divide each arm's accounted totals (failures included) by its accounted solves; TTCS charges a retried solve with its failed attempts' wall.</sub>
It is a section crop; viewport, CSS size and density are unspecified.
Implementation capture and full-view/focused comparison remain unavailable
because browser observation was rejected by the URL security policy. No
alternative browser surface was used to bypass that refusal.
- Typography: inherited Studio font, scale and weight tokens. Rendering remains
unverified; source-specific font substitution was not introduced.
- Layout: responsive two-column cards, large rounded borders, equal-height bodies,
separate footer actions and single-column narrow layouts. Rendered rhythm and
scrolling remain unverified.
- Color: existing surface, border, text and selected tokens. The selected badge
uses the text/page pair. Theme contrast still needs browser inspection.
- Assets: supplied product icon library reused for copy; no new raster assets
or handcrafted icon geometry.
- Content: Standard/Minimal remain the shipped modes; IDs remain canonical.
Details, usage and copy remain separate from the card's default selection.
- Behavior: card clicks persist a machine-local built-in choice. New local and
remote requests carry it explicitly; history and explicit per-task choices
retain priority. Old server preferences alone remain inert.
待浏览器恢复后,对照所选卡片截图检查字体、留白、颜色、图标和文案,并覆盖
深浅主题、窄屏、默认状态及展开的使用说明。当前功能测试不能替代视觉验收。
## Historical palette verification / 历史配色验收
final result: passed
Clear Blue and Tea Green implement the two approved colour directions for
@@ -84,3 +175,190 @@ did not specify a dark layout. Phone captures confirm no horizontal overflow.
Windows native installer acceptance and hosted CI are separate from these
local results. The production build retains its existing large-chunk warning.
# Composer composition placement / 输入框组合布局
final result: passed
The annotated request moves the existing composition selector and management
entry beside the run status. It preserves the existing screen, visual tokens
and session behavior. / 按标注将组合选择与管理入口移到运行状态旁,沿用现有界面。
## Evidence / 证据
- Source visual truth: author-approved composer annotation (1310 × 1826 pixels; browser chrome included, density unspecified). The user attachment remains private; the synthetic baseline below records the same controls.
- Full-view comparison: the matched before/after captures above were opened together, each 1440 × 1000 pixels, 1440 × 1000 CSS px, device scale factor 1. No density resampling.
- Focused comparison: composer controls from those captures were compared together at original pixel density; `composer-context-light.png` records the final control arrangement.
- Additional captures: `composer-light-390.png`, `composer-dark-390.png` (390 × 844 CSS/pixel dimensions), and `composer-dark-wide.png` (1440 × 1000), all in the same image directory.
- State: local Mock preview, Chinese, idle, General selected, existing input draft and missing-key notice. No provider request submitted.
## Findings / 结论
No actionable P0/P1/P2 differences remain. / 无需修复的 P0/P1/P2 差异。
- Typography: existing UI family, 11 px medium-weight controls and status typography retained; long names remain bounded by the selector.
- Layout: selector begins 16 px after run status; controls occupy the previously unused top row. The bottom toolbar fits one row at 1440 px. At 390 px the selector, status and management entry remain visible, with document width exactly 390 px.
- Colors: existing semantic theme tokens and hover/focus styles reused in both light and dark mode; no new palette.
- Assets: logo and icons preserved; no generated or replacement imagery.
- Content: composition names, source labels, error notice and management action retained. The placement is the only intentional visual change.
- Accessibility: composition controls sit outside `role="status"` and its live announcements. Accessible names and existing lock behavior are retained.
## Checks / 验证
- Switched General → Code review → General through the relocated selector.
- Opened Agent presets through the relocated management entry, then closed it.
- Confirmed the selector is absent from the bottom toolbar in the real DOM.
- Browser warning/error log after interaction: empty.
light/dark, wide/narrow, typecheck, affected and full frontend tests.
# Unified composition menu / 统一组合菜单
final result: passed
This section supersedes the separate selector/management controls above.
The annotation asks to combine those entries without changing the surrounding
composer. / 本节替代上述独立选择器与管理按钮,按标注合并为一个入口。
## Source and capture / 来源与截图
- Source visual truth: author-approved annotation showing the composition selector and adjacent management button, 452 × 312 pixels; density and full viewport unspecified. The user attachment remains private.
- Implementation: `composition-menu-light-wide.png`, `composition-menu-dark-wide.png` (1440 × 1000) and `composition-menu-light-390.png`, `composition-menu-dark-390.png` (390 × 844), all in the same directory, scale factor 1.
- Focused implementation: `composition-menu-detail.png`, 344 × 364 pixels at scale factor 1, including menu and trigger.
- Full-view comparison: `composition-menu-before.png` and `composition-menu-light-wide.png` were opened together. Baseline is closed; implementation is open to assess the added menu and unchanged surrounding composer.
- Focused comparison: the annotation and `composition-menu-detail.png` were opened together without density resampling. Source typography cannot support a 1:1 pixel comparison; the matched viewport supplies the product typography/layout reference.
- State: local Mock preview, Chinese, idle, Code review selected. No provider execution.
## Flow audit / 流程检查
1. Read the active composition: healthy after combining the entries. A single trigger shows name and localized source. The separate ellipsis had no visible explanation; its action is now explicit.
2. Choose a composition: healthy. Menu rows show capability notes and source, mark the current choice, and close after selection. Evidence: `composition-menu-light-wide.png` and `composition-menu-selected.png`.
3. Manage presets: healthy. A separated footer opens the existing management page. Evidence: `composition-menu-manage.png`. Management costs one extra click; frequent switching retains the same click count.
## Fidelity and interaction / 视觉与交互
- Typography: existing UI family and weights; 13 px choice titles and 11 px notes. Source labels sit beside names rather than consuming another row.
- Spacing: existing composer-menu recipe, 320 px menu width, 12 px radius and 8 px inner padding. At 390 px, the menu spans x=64…384 and the document remains exactly 390 px wide.
- Colors: existing surface, overlay, text, muted and focus tokens work in both themes; no new palette.
- Assets: existing chevron and brand assets retained; no new image assets.
- Copy: localized Built in/Personal/Project labels replace raw source identifiers. Management is a named footer action.
- Accessibility: trigger name includes the active composition and lock state; selected rows expose current state. Keyboard arrows skip disabled rows; Enter selects, Escape closes and restores trigger focus. This is scoped interaction evidence, not a full accessibility compliance claim.
- Safety of interaction: started/busy conversations never rebind through this menu. The same menu offers explicitly labeled new-conversation actions and management; hosts without creation support still expose management.
## Iteration and validation / 迭代与验证
- First inspection found source metadata on an extra line, making each choice unnecessarily tall. The item title now uses a flex row; revised captures show compact name/source pairs. No actionable P0/P1/P2 visual issues remain.
- A later resize check found a P2 shared positioning defect: after 390 → 1440 px, menu left remained 88 px while its trigger settled at 570 px. Window resize preceded fold publication and sidebar transition completion.
- Shared floating placement now responds to fold publication and transition completion. The revised dark-wide capture and browser rectangles show both left edges at 570 px.
- A deterministic real-Picker regression fails without the positioning fix and also checks cleanup after closing.
- Verified selection, management, Escape and responsive placement in the browser; warning/error log was empty.
- Regression tests cover keyboard focus, distinct action identities, failed/pending selections, locked sessions, management without creation support, equal names from different sources, and deleted bound presets.
- Final validation after the resize repair: typecheck passed; frontend full suite passed, 633 files and 5,225 tests. Repository checks passed without increasing a budget.
- Native shell behavior and provider execution are outside this layout change; hosted CI and publication status are reported separately in the pull request.
Checklist complete: one entry, grouped choices/actions, clear current state,
retained locks, keyboard access, themes, narrow viewport and regression checks.
## Studio integration verification / Studio 合并验证
The upstream integration preserves the top-row Agent composition trigger and
the unified selection/management menu. The upstream screen-reader-only run
label remains inside the live status region; interactive controls remain
outside it. Composition menu text follows the shared text-size setting.
check("a 1280x900 viewport in a 560x620 panel: the view is scaled to 560x394 and centred",fit.width===560&&fit.height===394&&fit.x===380&&fit.y===173,JSON.stringify(fit));
check("the page still lays out at the viewport it was given",(awaitevalIn("innerWidth"))===1280);
check("an agent click aimed at the right column lands on it",(awaitclick())==="hit");
check("a panel larger than the viewport shows it at full size, centred, never enlarged",roomy.width===1280&&roomy.height===900&&roomy.x===80&&roomy.y===70,JSON.stringify(roomy));
check("and the click lands at full size",(awaitclick())==="hit");
views.hide();
awaitwait(600);
check("parked, the page is not scaled and the click lands",(awaitclick())==="hit");
views.show(page.targetId,panel);
awaitwait(600);
check("shown again it is fitted again",view.getBounds().width===560&&(awaitclick())==="hit");
check("a viewport smaller than the panel is shown at its own size, centred, not in the panel's corner",phone.width===390&&phone.height===600&&phone.x===20+(900-390)/2&&phone.y===20+(700-600)/2,JSON.stringify(phone));
check("and a click on it lands",(awaitclick("lc"))==="hit");
check("the agent's screenshot is the size of the viewport it asked for",png.readUInt32BE(16)===1280&&png.readUInt32BE(20)===900,`${png.readUInt32BE(16)}x${png.readUInt32BE(20)}`);
awaitwait(600);
check("after the agent takes a screenshot the page is still fitted and a click still lands",(awaitclick())==="hit");
check("a zero-sized override is no override: the page fills the panel and a click lands",JSON.stringify(view.getBounds())===JSON.stringify(panel)&&(awaitevalIn("innerWidth"))===560&&(awaitclick("lc"))==="hit",JSON.stringify(view.getBounds()));
check("clearing the viewport gives the page its panel back",JSON.stringify(view.getBounds())===JSON.stringify(panel)&&(awaitevalIn("innerWidth"))===560);
constback=awaituntil("the page beside the conversation again",async()=>(!beneath()&&width()<placed.width?guest.getBounds():null));
// The column settles a pixel or two after the first measurement, so beside means
// the same place and size to within a few pixels.
check("leaving the full-width view puts the page back beside the conversation",Math.abs(back.width-aside.width)<=3&&Math.abs(back.x-aside.x)<=3&&!beneath(),{back,aside});
check("the right edge of the page is what the view draws at its right edge",edge.b>edge.r,edge);
check("the agent's click on a control at the right of the page landed",(awaitguest.webContents.getTitle())==="clicked",awaitguest.webContents.getTitle());
check("a screenshot taken the way the agent takes one shows the page at the size it asked for",dims.width===SIZE.width&&dims.height===SIZE.height,dims);
check(`${name}: the view is on screen, beside the conversation`,b.x>=0&&b.width>100&&b.height>100,b);
check(`${name}: the title is the image's own`,guest.webContents.getTitle().includes(path.basename(file)),guest.webContents.getTitle());
check(`${name}: what the view drew is the picture, not a blank`,share>0.2,{share,size,bounds:b,scale:require("electron").screen.getPrimaryDisplay().scaleFactor});
check("the model was told the typed cause, not a bare system error",result.startsWith("root missing: the project folder ")&&result.includes("not a permission or argument problem"),result.slice(0,200));
check(`${scheme}: icon ${i+1} of the message's own row matches the reply's`,awaitsize(mine.nth(i))===replyIcon,`${awaitsize(mine.nth(i))} vs ${replyIcon}`);
}
check(`${scheme}: the buttons are the same size`,awaitsize(page.locator(".user-acts > button").first())===replyButton);
check(`${turns} turns: the current branch is selected`,awaitpalette.getByRole("option",{name:"main",exact:true}).getAttribute("aria-selected")==="true");
check(`${tag}: the badge is on the wordmark's row, vertically centred on it`,m.shown&&m.dy<=1.5&&m.after,JSON.stringify(m));
check(`${tag}: it is an outlined uppercase badge in both themes and the text is untouched`,m.fill==="rgba(0, 0, 0, 0)"&&m.border!=="0px"&&m.radius!=="0px"&&m.upper==="uppercase"&&m.text==="studio",JSON.stringify(m));
if(!seen)seen={left:m.left,top:m.top};
check(`${tag}: it sits in the same place whichever theme is on`,Math.abs(m.left-seen.left)<=0.5&&Math.abs(m.top-seen.top)<=0.5,`${m.left},${m.top} vs ${seen.left},${seen.top}`);
}else{
check(`${tag}: the badge stays hidden on a narrow window`,!m.shown);
check(`${scheme}: no icon column or vertical track`,m.iconColumn==="none"||m.iconColumn==="absent",m.iconColumn);
check(`${scheme}: no box around the row`,m.sides==="0px,0px,0px"&&m.radius==="0px"&&(m.bg==="rgba(0, 0, 0, 0)"||m.bg==="transparent"),JSON.stringify(m));
check(`${scheme}: opened, with its caption above`,m.open&&m.caption);
f({kind:"turn_done",receipt:{verdict:"partial",changes:[{path:"a.go",reviewed:true}],verifications:[{command:"go test ./...",passed:false,stale:true}],gaps:[{kind:"unverified_mutation",detail:"改了没跑"}],risks:[],unverified:[],saysSomething:true}});
check(`${scheme}: an opened step has a detail panel`,m.length>0,`${m.length} found`);
check(`${scheme}: it is bordered on all four sides`,m.length>0&&m.every((x)=>x.sides==="1px,1px,1px,1px"&&newSet(x.color).size===1),JSON.stringify(m[0]??{}));
@@ -40,26 +40,24 @@ for (const scheme of ["light", "dark"]) {
check(`${tag}${round}: the browser column is drawn`,!!open.side);
if(open.side){
if(label==="wide"){
check(`${tag}${round}: it starts at the top of the pane, beside the navigation row`,Math.abs(open.side.top-open.pane.top)<=1&&Math.abs(open.tabs.top-open.pane.top)<=1,`pane ${open.pane.top} tabs ${open.tabs.top} column ${open.side.top}`);
check(`${tag}${round}: it starts at the top of the pane`,Math.abs(open.side.top-open.pane.top)<=1,`pane ${open.pane.top} column ${open.side.top}`);
check(`${tag}${round}: it runs to the bottom of the pane`,Math.abs(open.side.bottom-open.pane.bottom)<=1,`${open.side.bottom} against ${open.pane.bottom}`);
check(`${tag}${round}: the navigation row stays left of it`,open.tabs.right<=open.side.left+1,`${open.tabs.right} / ${open.side.left}`);
check(`${tag}${round}: its strip ends on the navigation row's line, so the two rows read as one`,!!open.strip&&Math.abs(open.strip.bottom-open.tabs.bottom)<=1,JSON.stringify([open.tabs,open.strip]));
check(`${tag}${round}: the workbench card's own edge separates it from the conversation`,open.divider>=1,String(open.divider));
}else{
check(`${tag}${round}: the navigation row stays above the page`,!!open.tabs&&open.side.top>=open.tabs.bottom-1,JSON.stringify([open.tabs,open.side]));
check(`${tag}${round}: the page takes the pane's full width when it stacks`,open.side.left<=open.pane.left+1&&open.side.right>=open.pane.right-1,JSON.stringify([open.pane,open.side]));
check(`${tag} undocked: the column is gone and the navigation row is as wide as the pane's body again`,(!shut.side||shut.side.left>=shut.pane.right-1)&&!!shut.tabs&&shut.tabs.right-shut.tabs.left>=shut.pane.right-shut.pane.left-24,JSON.stringify([shut.side,shut.tabs,shut.pane]));
check(`${tag} undocked: the column is gone`,!shut.side||shut.side.left>=shut.pane.right-1,JSON.stringify([shut.side,shut.pane]));
awaitpage.close();
}
}
// Every room between phone and wide, not two samples: the left column shrinks with the window, and the tabs it holds
// must still fit and stay clear of the browser column.
// Every room between phone and wide, not two samples: the conversation shrinks with the window, and it must
// stay clear of the browser column, which stacks over it once the window is narrow.
check("from 560 to 1440 the tabs fit their row, stay clear of the browser column, and the column stacks exactly when tabs plus its 320px floor no longer fit",bad.length===0,bad.join(" "));
check("from 560 to 1440 the conversation stays clear of the browser column, which stacks only in a narrow window",bad.length===0,bad.join(" "));
check(`${sc.name}: every piece of text answers to the text size or the reading size`,holes.size===0,`${total} text nodes${holes.size?"; stuck: "+[...holes.keys()].slice(0,6).join(", "):""}`);
check(`${tag}: the push says where it goes before it goes`,push.includes("github.com")&&push.includes("main → origin/main")&&push.includes("从不强制推送"),push.slice(0,120));
check(`${tag}: a folder of projects shows a section per repository`,heads.length===3&&heads[0].includes("api")&&heads[1].includes("feature/login"),heads.join(" | "));
check(`${tag}: staging in one repository leaves the others as they were`,(awaitmany.locator('.workbench-repo[data-repo="api"] [data-action="workbench.stage"][aria-checked="true"]').count())===1);
{name:"wiki",state:"ready",enabled: true,transport:"http",tools: 4,followsChanges: true,description:"The team wiki."},
);
}
asyncworkspaceFile(path: string){
// ?netshell=1 is a connection over HTTP: a `!` line is refused as the kernel refuses it.
asyncsubmit(text: string,chips?: ChipCall){
(window.__sent??=[]).push(text);
if(query.has("netshell")&&text.trimStart().startsWith("!"))thrownewHttpError(403,"shell commands are unavailable over HTTP",{code:"shell.unavailable_over_http"});
returnsuper.submit(text,chips);
}
// ?checkfail=<name> makes a plain test of that source answer 401.
constcontent="# Release plan\n\nThe plan lives in the library folder you granted this project.\n\n- freeze on Friday\n- tag after the checks are green\n- announce the next morning\n";
constcontent="# Install guide\n\nDownload the package for your system, then run the installer. It places the program in your user folder and does not need administrator rights.\n\nAfter the first start, open Settings and add a model service before you begin a conversation.";
check(`${tag}: the tip has the name and the full command, inside the window`,tip.inside&&tip.lines===2&&/npm run tauri:test/.test(tip.cmd),JSON.stringify(tip));
check(`${tag}: its text can be selected`,tip.select==="text",tip.select);
awaitpage.mouse.move(width-5,5);
awaitpage.waitForTimeout(200);
check(`${tag}: it closes when the pointer leaves`,(awaitpage.locator(".job-tip").count())===0);
check(`${tag}: a 401 row offers sign-in beside the retry`,r.figma?.includes("登录")&&r.figma.includes("重连"),r.figma?.join("|"));
check(`${tag}: a row with withheld tools offers both approvals`,r.notes?.includes("批准并重新连接")&&r.notes.includes("接受并以后都自动接受这个服务器的改动"),r.notes?.join("|"));
check(`${tag}: a followed row says so and offers to stop`,r.followNote&&r.wiki?.includes("取消跟随"),r.wiki?.join("|"));
check(`${tag}: no button spills out of its row`,r.spill.every((n)=>n===0),r.spill.join(","));
check(`${tag}: no two buttons overlap`,r.overlap.every((o)=>!o));
Loaded 100 of 2403 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.