The cap keyed on data-live alone, so Open and Open-while-running looked the same while the model thought. SayCard now marks data-full when the thinking mode is open and the cap skips it.
.ds-row .sesspin-action set display: grid in a later rule of equal specificity than the one hiding it, so every row showed the button at rest. A pinned row shows .ds-pin at rest when quick actions are on, as it already did when they are off. The badge in the model menu could shrink to one character per line beside a long name; it is now flex: none. Both are held by perf scripts and a rows.css rule test.
On single-column pages the title and cards are centred in an 880px column while the page index was not, so it sat 95px to the left of them. A sub-page's hide rule lost to the later display rule of inline rows (equal specificity), leaving three empty rows above it. Both are held by perf/settings-page-column.mjs.
The session's first executor request pays for the prefix every later one
reuses, so the session-wide hit rate mixes "did the cache work once warm"
with "how much a cold start could reuse". usagereport already records the
cold start, but nothing a live session reads projected it.
The live figures come from the serve ledger, which restarts on /new,
/resume and /fork; usageTally does not, so its ColdStart can belong to an
earlier conversation in the same process. The broadcaster therefore takes
the cold start from the ledger's own population: the first request the
ledger files under the executor (pricing.LedgerSource, the rule Add
already used), split by the same CacheSplit the ledger applies, and reset
with it. SessionUsage carries it as coldStart.
- /status (TUI): "warm cache" row, ledger hit rate with the cold start
taken out of hit, miss and unknown; "out/in" row, output ÷ input.
- Desktop session overview behind the cache meter: the same two figures,
computed in state/overview beside cacheRate.
- wire-parity: ColdStartUsage mirrored in port/session.ts.
Every keystroke sent a plan request that walks the whole root. The panel now waits 400 ms after the last change and drops an answer whose path is no longer the one in the box.
Planning ran on every keystroke and its write probe called MkdirAll on the target, leaving a folder per typed prefix. The probe now runs in the nearest existing ancestor, free space is read from it, and Move creates the target once the user starts the move.
The grip on each divider (a dotted handle, an arrow tab when shut) is gone; the
divider still drags, resets on double-click and sizes from the keyboard, and a
shut column is a thin edge that can be dragged back out. The sidebar toggle is
the panel icon at the top left whenever the sidebar is collapsed, and its
shortcut is Ctrl+B as the board says, replacing Ctrl+backslash.
The composer showed the connect strip, the trust question and the sandbox note
together, each with a paragraph, and a short stack clipped them over the input.
Now one strip is drawn, by priority (cannot send, trust, no sandbox): a single
line, the explanation in the hover text, calm grey for reminders and warm only
for "cannot send".
A terminal opened from a remote pane runs on that machine through the existing
forwarding, and its header carries the host. A test holds that the host's
requests are forwarded and a paired device's never reach the far kernel.
A fourth kind of tab opens a real shell (ConPTY on Windows, a pty elsewhere) in
the project folder, outside the model's permissions and sandbox and with the
host's provider keys left out of its environment. The kernel keeps each shell's
output, so a tab left or a window reloaded comes back as it was; a tab is named
for the command submitted while the shell is busy and carries a blue dot, and
closing a busy one asks first.
Also: the composer's note stack no longer clips to a few pixels when it holds
two notes, which left the second one overlapping the input.
The unused linter reports eight symbols in prune.go that the archive-to-file change left without a caller; the strategy type and its hint mapping, which are still used, stay.
The setting promised it above the input box while the chip draws inside the composer's top edge, as the design board shows. Both lines of the setting now say top.
With two currencies the third chart took a row of its own and left the right half of the page empty; the spend charts now stack in one column beside the usage chart. The daily table's header is 11px medium in the interface face, not 13px bold mono, and its rows take 9px padding.
Overview: four figures (requests with the main/sub split, cost, tokens, cache hit), each source as a bar with its cost, and a footer with the work time and a link to the context details; the context window is no longer drawn twice. Context details: the compact action, the MCP count and the manage link share one footer row instead of a server list, and a row with a breakdown shows its figure and a chevron, not a bordered pill.
The veil starts below the title bar, so with the enlarge mode gone its text showed sharp above the blurred app. A strip beside the veil, not inside it because a blur only reaches what is within its own element, now blurs that band and lets clicks through to the title bar's buttons.
The sidebar's settings button and the common settings page wore a circle with eight rays, which is the sun the appearance page and the light-or-dark control already are, and at 16px the two could not be told apart. Both now use one toothed gear; the appearance page keeps its sun.
1360 by 900 at most, over the dimmed app; a smaller window still gets it filled. On a 2000px window the content was either 700px columns or a block with 180px of empty margin each side. Follows the revised board Set-Wide.
The sheet already fills the window, so enlarging only took it over the title bar and added three window buttons beside its own two. The control, the stored preference, the window buttons it summoned and the drag-region rules that went with it are removed; the board draws no buttons in that corner besides closing.
A block that applies on this machine at once says nothing; one that needs a restart, belongs to the workspace and so on gets a tag beside its name, and the page title carries one line saying what the usual is. Descriptions stay on one line with the whole text on hover, rows take 6px padding, and a row with no description is a single line. Follows the board Set-Full.
The right column of a settings row was sized to the widest line of the note under its control, so at 572px a column took 501 and the label shrank to one character. It is now capped at 320px and the note wraps. The page title takes the block's width and centering.
Settings: the card column is centered, 760px for single-column pages and 1160px for two-column ones, with 常用 added to the two-column pages; below 860px it stays one column. Changes: past eight files a new or untracked file shows a tag in its own header instead of a folded row under it, the top line splits modified from untracked, and the header weights, the 14px checkbox and the note's weight follow the board. Follows the new boards Set-Wide and WB-Changes-Many.
The model panel pins its title, filter and footer and scrolls just the list inside its own column, with the effort column fixed beside it and both capped at 440px. The agent preset menu is 340px with 12px descriptions and the workspace dialog 340px. TOKENS.md now states the picker panel rules the board Spec-Pickers draws instead of the older 12px-corner, 11px-description line. Menu items can be marked foot to stay under the scrolling rows.
600px instead of 700, capped at 440px tall; model rows are one 34px line with the provider note and badges beside the name; effort rows lose the strength bars and the third line; the column fits its content and stays in view, and its footer is the single line the board draws.
300px panel, a bold title with the scope on the right, two-line rows with a 12px description, the current row in a grey wash with a blue tick. The unimplemented Ask row and its two strings are gone; it comes back when the mode exists.
No border or box: a 26px line in the transcript's own type, the running label shimmers as text (the same 1.6s light as thinking) and nothing else moves while it is folded, open it is an indented list on a hair rule, a failure is a small pill, and a finished group folds to one faint line of count and time. Follows the Think-Exec light variant.
360px panel, a small two-segment switch with the creation note under it, a source-branch label, 32px search and branch rows in the mono face with a pill for the current or occupied branch, and the one-line note at the foot. The per-row chevrons, dividers and the switch's icons and ticks are gone; the folder name and path stay as quiet lines because the board does not draw the current-folder page.
The note is a borderless warn wash inside the input box with a text link, not a bordered strip with a filled button. The pill's pull-up margin assumed nothing above it in the box; with a note present it now takes its own line.
The board sets body text at 500; the default weight ladder is 500 / 600 / 700, so 500 is --w-reg. Rules written as --w-med drew every line one step heavier than the board, and on Windows 600 falls on the bold face. Side panel answers also take the board's 1.7 line height.
The mark for a model came only from the preset a provider was installed from, so an entry pointing at api.deepseek.com by hand drew a letter tile. The address is now read through the curated preset serving it, as vision models already are. Setup service cards draw the mark their vendor declares.
Compaction moves the folded span into <session>.outputs/fold-<hash>.md and leaves a pointer the model reads back with read_file. The summarizer path, its retries, bounds and tests are removed.
Answering an ask with no selection ends the turn normally instead of reporting a failure.
Find bar, overview and context cards, compact dialog, image preview, root-missing banner, toasts, side panel and prompt refine are aligned to the Chat-Tools board; the agent preset picker follows the Agent-Picker board.
A fixed ten seconds reported a hang on a loaded Windows runner where the first-run test had already spent forty seconds. testenv.Budget exists for waits that only catch a hang.
A removal that lost a race with the walker was dropped, so the directory stayed behind and, because names are never reused, a later walk could not tell it from an unreadable one and failed after the five-second re-stat window. The churn now retries the removal until it succeeds, which is what the never-reused-name invariant assumes.
The running-copy check matched processes whose path it could not read by name, and the name list included cua-driver.exe, which other computer-use apps ship too. An elevated or orphaned driver from another app therefore read as Studio still running, and the retry loop could never succeed. Only the app's own executable and its host are matched by name now.
The compaction card is titled by what it did (earlier content folded into a
digest, auto or manual) with its reason and sizes in one line and no bar or
frame of its own; the execution group has no outer frame, its failure count is
red text, and a failed step's reason sits behind a 2px red rail instead of in a
tinted box; delivered files are one list with hairlines, their open action a
text link. Frames stay on the cards that ask for something.
After the second compaction the content before the second-latest compaction
folds behind one divider line at the top of the conversation ("N earlier
messages folded into a digest · show the folded content"); the stretch the
latest compaction closed stays on screen, so a fresh compaction never leaves the
page empty, and each further compaction folds one more stretch into the same
line. The fold is display only, always starts folded, and opens in place.
The paid figure is the first of four tiles instead of a hero above them, with
its estimate and coverage caveats under it; daily tokens and daily spend sit
side by side, spend drawn as bars rather than a line; the duplicate block
heading above the page goes. The kernel still reports only a total for output,
so there is no output tile.
The palette gets the board's scope chips under the input (actions, sessions,
settings; each toggles its scope), a count on each group once something is
typed, and a coloured dot with the word on a session that is running or waits
on the person, the rest carrying their age. Notes that say something about a
row sit beside its name; a shortcut keeps the right edge. The foot puts the keys
on the left and the scope symbols on the right. Files are not a scope: nothing
in the port lists a workspace's files.
Onb-Flow's welcome centres a large wordmark, the headline, one line of body,
the primary button and the note; it was a left-aligned card with the skip
beside the button. The skip stays, now under the button. The board's
import-from-backup link is not drawn: onboarding has no import flow to call.
They were one component hiding the other two's blocks with a stylesheet. Each
page is now composed as its board draws it: Appearance as theme, text and
window cards (wallpaper, typeface, size and window behind pills; light/dark,
effects, weight and contrast on the row), Conversation display as process,
files and previews, and notices, and Operation & performance as shortcuts, tabs
and navigation, while running, and performance. A single-control block draws as
one row through Group's inline form. The advanced-appearance disclosure goes:
its weight, contrast and effects are rows now. Rows carry the id landing and
search look for.
Set-connect draws network, and other machines (remote, phone access) as one
row each behind a pill; Set-about draws account, version and diagnostics
(version, logs, trace export) and a short About note, with community and
sponsor behind pills. Landing on a setting that is its own page now targets the
page itself, and the section counts leave out blocks drawn inside another page.
Set-data draws memory and usage, session storage and backup with one short
row per setting: memory (with its count), usage, upload volume, storage and the
two backups sit behind a pill, while currency, auto-archive, session location
and 1.x sessions act on the row. The board's upload switch is not drawn: the
kernel has no upload setting, only a report of request sizes, so that row opens
the report. Opening settings at 'memory' still lands on the memory page.
Set-agent draws persona and presets, plans and questions, and collaboration as
one short row per setting. Plan approval, narration and sub-agent write scope
become segmented rows, the question wait and the reference size keep their
value beside the row, and sessions-between-sessions sits behind a pill. What
the old long descriptions said about what a switch releases or costs stays as
the row's hover text. The plan-mode switch goes (the start mode owns it), and
the file-changes, fold-work, receipt and level-notice switches move to the
conversation-display page where the board puts them.
Set-safety draws approval (tool approval, rules, outside text, tools), sandbox
and execution, and web as one row each: the controls that are a value stay on
the row, the ones that are an editor sit behind a pill with their count. The
outside-text switch keeps what it covers and what it does not as the row's hover
text, so the board's one line does not drop it. Message keys the old blocks
alone used are removed.
Set-models draws services (model services, health check) and uses (by-use
models, context maintenance) as short rows, each with its scope and when it
takes effect. A block that names an action now draws as a setting row with a
pill that opens the same page; the long content moves behind the pill, so
providers, by-use models and the compaction gauge are pages of their own.
Also: Ctrl+Enter reads as written in the board, and the selected settings
nav item keeps the regular weight the board draws.
Appearance borrowed feedback.full_width for its full-width option, so removing
that key with the old feedback form left the option reading as a raw key. The
option gets look.full_width in its own namespace.
In the activity group a failed call's line preferred the last line of its
output over the host's error. For a command that tail is its stderr and says
more than "exit status n"; for any other tool the output is not about the
failure, so the host's error wins there. Tests that still expected the drawings
replaced by the boards (exit labels beside the exit summary, the old missing-file
sentence, the ext settings target) now hold what the boards draw.
Sess-Rows draws project rows without a session count, folded or open, and a
remote project with its chevron and its state word (connecting, offline) after
the host. The count, its component and its two messages go; what is left to
load is already said by the show-more row beneath the sessions. The connection
line now carries only the detail the word does not (the step, the error).
An unread row shows its time beside the left dot, as the board draws it, so the
dot is the only unread mark and now carries the accessible name. The effort
reading in the composer gets the board's icon. The perf mock gives its sessions
times so the harness shows what the board shows.
Settings opens as a sheet filling the window inside a 24px margin, the
board's frame, rather than a smaller box centred in it; the content
column keeps its 760px measure. The Common page's header adds the board's
line that it is a shortcut and each setting also lives in its category.
The shared switch is the spec's: a 34 by 20 outlined track with a 12px
knob, grey when off, and when on an accent outline over the accent wash
with the knob in the accent. Buttons from the shared kit set their label
in the medium weight the spec gives every button size.
The cache hit and miss hues moved from the legacy stylesheet, which they
had pushed past its size budget, into the design sheet beside the chart
rules that use them.
The rail, the palette and the meter open Usage as its own page, titled
Usage, holding only the usage blocks: the display currency, usage and
cost, and upload volume. A section drawn as a page of its own now keeps
only the blocks the catalogue files under it, so Extensions likewise
shows no blocks from neighbouring sections.
The daily chart draws the input side as stacked bars, cache hits and
misses in a validated pair of hues with a legend, and its tooltip adds
the total the provider reported, which shares no axis with the split.
The tiles read input with its cache rate, the provider-reported total,
requests with turns, and active days with requests a day; output is not
drawn, since the kernel does not report it apart from the total. Export
CSV writes the range's days, one money column per currency.
The vote row in a market package's detail draws the approval rate as a
bar, green for the share of positive votes, before the words that state
it. A package with no votes draws no bar.
Discover lays packages out as cards: a kind icon, the name with a check
when an admin marked it trusted, the publisher and version, two lines of
summary, then the kind, whether it is pinned, installs and approval along
the foot. An installed package says so in the corner; a pinned one not
yet installed offers Install there, which opens the package straight onto
what it would install. A package nobody pinned has no shortcut: trusting
its publisher stays a choice made on its page.
The rail, the command palette, the meter and the runtime notice now open
Extensions as its own page rather than as a section of Settings: titled
Extensions, without the settings navigation or search, and with the
Installed tab laid out as the list beside a detail pane. Choosing a
package, service or skill in the list draws it whole in the pane, with
its state, description, facts, what it provides, and every action it
offers; the list rows keep only their name line and switch. The first
item is chosen when the page opens. The pane ends with the note on when
a change takes effect.
The page is the Settings surface opened on one destination, so every
extension read, refresh and model update stays one path. Settings opened
on its own keeps Extensions as a section that unfolds rows in place.
Usage and cost becomes a page of its own under Data, and the rail's usage
button and the meter's wallet open straight onto it instead of onto the
Data section where it sat under memory. Landing on a block that is its
own page scrolls to the page's top, so the breadcrumb is in view.
Interface theme becomes a page of its own under Appearance: on the
section it is one row naming the theme in effect, and opening it shows
the palettes, the installed themes and theme import under a breadcrumb.
Agent presets, with the preset editor, the system persona and the mode
picker switch that live on it, move onto a page of their own under Agent
behaviour. The catalogue says which settings are drawn on another
block's page, so a search hit or link on the persona or the picker switch
opens the presets page rather than a section that no longer shows them.
The second batch of settings blocks that are lists or editors moves onto
pages of their own: automation rules and scheduled runs under Extensions,
the network under Connections, storage and exporting or importing the
configuration under Data, and versions and updates under Account and
About. Each shows on its section as one row and opens under a breadcrumb.
The settings catalogue can now say a block is a list or an editor rather
than a value. Such a block shows on its section as one row (its title,
its hint, what it holds now, and a chevron), and opening it gives the
column to that block alone under a breadcrumb back to the section. Its
contents mount only once it is opened. Search and links that land on
such a block open its page; changing section closes it; outside the
settings screen the block draws in place as before.
The first blocks given pages: models by use, the rules you spell out,
the tools the model can use, and messages between sessions.
First launch opens on a welcome that says in one line what Reasonix does
and that setup takes about a minute. Setting up goes through steps shown
at the top, with Not now on each: connect a model service, chosen from
cards that name what each is (a relay or self-hosted entry is the only
one that asks for an address; a listed service says the address it
filled), then pick a default model from the models the key can reach.
Only a typed address that answers both ways asks how it is spoken to;
listed services never do. Steps slide in the direction they go, in the
panel duration, and still under reduced motion. Opening a project stays
the app's own page after setup finishes.
The device-access card is titled Use Studio on your phone and says what a
paired phone can do. Phones already connected come first, under one
count, each saying which way it came in. Then How the phone gets here
offers the two routes as their own blocks, same Wi-Fi first with its
address and its own switch, QR code and network picker, then From
anywhere with the account it needs. The routes stay separate: switching
one on never opens the other.
Each machine is a card: its dot, name, address and state as a tag on one
line, its folder and counts under it. A machine that is not connected
offers Test it right on the card; edit, test, hide from the rail and
remove move into the card's More menu, and the switch stays in view.
Machines found in ~/.ssh/config can be added together with Add them all.
The market install card's close button now names what it dismisses, so
it is not mistaken for the Settings sheet's own Close.
Updating a skill from its source folder and uninstalling it move off the
row into a More menu, so a skill row is its name and marks, description,
where it lives, and its switch. Uninstall is the danger-coloured item.
The update and uninstall confirmations become cards: the question as the
title, warnings as tinted boxes rather than edge bars, and the buttons at
the foot.
Publishing is a titled card. The type comes first and the source address
straight after it, since what the package is decides what the source may
be; name and version follow side by side, then the rest. Visible only to
me is drawn as a switch in a tinted row, and the footer holds the note
beside the submit button. In My publications each version says where it
stands as a coloured dot and a word: public, in review, not approved,
private.
A market package opens as a card: its name, kind and trust tags on one
line, the summary, the facts in a tinted box, the vote row, and a footer
that holds the note beside Back and the next step. Kind and trust read as
tags in the list too. The unpinned warning and the notice that the list
is from the cache are tinted boxes rather than edge bars, the cached one
with Retry on its own line.
The confirmation every market install passes is a card titled with what
will be installed and a close button. A pinned version opens with the
digest check in the success colour and the digest itself beneath; an
unpinned or own version keeps its warning first. Warnings and the
read-each-of-these group read as tinted boxes instead of coloured bars
down the edge. The kernel's risk grouping is unchanged.
A skill row is its name (or command) with what it is — drift from its
source, subagent, read-only — on the first line and its description on
the second; where it lives is a tag, then the switch. Drift reads in the
warning colour, and a source folder that is gone in the error colour, so
it stands out of the list rather than waiting in a menu. A failed
service is marked by its red dot alone: the red bar down its left edge
is gone.
A package or service row is its status dot, its name in full (never cut),
one line under it saying what it brings and where it came from, and its
switch. Update, export, remove, reconnect and sign-in move into the row's
body, and a row with something wrong opens by itself so the reason and
the way to answer it are in view without a click.
Everything that puts something into the extensions list sits behind one
Add… button beside the Installed / Discover tabs: install from the market,
from a repository address, import a local folder (which opens the folder
picker straight away), or connect an external service. A way this window
cannot take right now is left out of the menu: no new install while a
package is mid-update, no service while the kernel is not live. The
per-group Add and Connect buttons are gone.
The runtime card no longer leads the page. It sits at the foot of the
installed list as one line saying that enabling, disabling, installing and
updating take effect from the next turn, with Reload runtime beside it for
the one case that still needs it: an extension's own code edited on disk.
Categories are cards that say what each is for; the body keeps its
preview toggle and byte count, with the public-if-filed line beneath it;
screenshots sit in one row with Capture this window and Add images, and
the warning about what a capture carries sits under them. Nickname and
contact keep their row. What is sent along reads as one line that opens
to the full environment and the logs folder; the security policy link
moves to the footer beside Send. The discussions pointer goes, since the
form itself now takes ideas and questions.
The receipt is a check, a short thanks, the receipt number in a box with
an icon copy button, what happens next, and View my feedback or Write
another. Legacy rules for the replaced classes are removed.
The read-only note stays at the top beside a read-only tag, with zoom out
and in on both kinds and fit width for Word; a workbook switches sheets
from a tab strip at the bottom that also says when rows or columns were
cut. With Office preview off the preview names the file and offers both
ways out: open it in the system application, or turn the preview on in
Settings, which the workbench now reaches through the pane.
The PDF preview's toolbar is the board's: icon buttons for the previous
and next page around a page box that takes a typed number, held to the
document's page count; zoom out and in step through fixed scales from
whatever fit-width last drew, with the drawn scale between them; fit
width is a toggle. A PDF that cannot be read (damaged, or asking for a
password) gets one strip saying so with Open in system application.
The sign-in a page or proxy asks for in the built-in browser gets a titled
dialog with a close button, the site's own prompt as it sent it, labelled
fields, the note on where the password goes under a lock, and a footer
holding Cancel and Sign in. A proxy keeps the same dialog under its own
title.
The open-with control remembers the last program chosen and opens the
project in it from its left half; the arrow lists every program. The menu
is titled, draws each program as its initial on a tile coloured by the
kernel's id (the kernel ships no icons), sets the file manager apart under
a divider with the platform's own wording, says when nothing was found,
and ends with the project's path.
Selecting text in a finished reply raises the same small bar the editor
has: quote selected text, or copy it. It appears 120ms after the pointer
lets go, never takes focus, and gives way to the context menu, a
collapsed selection or a scroll.
Both bars now share one component and sit above the selection as the
board draws them, dropping below only where the scrolling area leaves no
room above; the editor's bar carries its line range as a trailing note.
Selecting code in the workbench editor raises a small bar under the
selection 120ms after it settles: add to conversation with the line range
it covers, or copy. It closes on collapse, scroll or the context menu, and
a selection ending at a line's first column does not count that line.
The in-page text menu now leads with add to conversation, then cut, copy,
paste and select all with their platform shortcuts, then undo and redo,
each group separated.
File menu: a header names the file and its folder ("根目录" at the top);
加入对话 comes first, then 复制引用 with the @reference it copies shown on the
right, then 复制路径; a rule, then reveal and open in the system app. Each
item carries its icon. An unusable reference keeps its item, greyed, with the
reason beneath it. A copy that lands says so in the bottom note capsule
("已复制 @README.md"); one that fails stays in the menu until the next try.
Browser tab menu: a header names the tab ("浏览器标签 · localhost:5173"), and
each close choice shows how many tabs it would close. Accessible names stay
the items' own words, without the counts or references drawn beside them.
The keyboard test for the file menu walks to 复制引用 rather than counting a
fixed number of steps, since the board moves it.
One WorkbenchState card (icon, what it is, why, what to do) now carries:
- a file that is no longer there: "文件已不在原处 · 可能被删除或移动了", with
重新读取 (reads it again) and 关闭页签;
- a document over the preview limit: "文件太大,无法预览 · 48 MB,预览上限 32 MB",
with 用系统应用打开 in the card (the toolbar's copy steps aside there);
- a folder without git: "这个文件夹不是 git 仓库 · 改动和提交历史需要 git".
A page that does not load keeps its own BrowserFailure, drawn as the same
card with a globe. The board's 在此初始化… is not offered: no kernel route
initialises a repository.
Closing a file tab with unsaved text opens a small prompt under it, as the
WB-Frame board asks: 保存 saves that file (any open one, not only the file on
screen: the files hook gains saveAt and dropAt) and then closes it, 不保存
drops the draft and closes, 取消 keeps the tab and its text.
The workbench slides in 24px from the right over 200ms when it is brought
out, never when it is drawn already out; its own close button slides it back
out over 160ms before putting it away. Reduced motion skips both.
States: a file with unsaved text shows a dot where its close button is, and
the close button again under the pointer (the files hook now reports which
open files are unsaved); the page the model is working on keeps its blue dot,
now after the name and pulsing every 1.6s; Go, Python and sheet files colour
their icons apart. Tabs keep their width; the ones the strip cannot show fold
into "+n ⌄", a list to pick from that closes on Escape or on leaving it.
Motion: a tab that opens after the strip was drawn grows in over 180ms (one
drawn with the strip does not move); closing narrows a tab to nothing over
140ms before it goes; switching fades the canvas in over 120ms and moves the
tab's colours over the same time. Reduced motion stills all of it.
The tab strip's ref is now owned by useOverflowTabs, so the scroll listener
it installs has a receiver the action census can name.
The file bar is path, view toggle, save, then "…", which opens the same file
menu the tree's right-click does (reveal, copy path, copy reference, add to
the conversation) under the button; the separate reveal icon is gone. A
reveal asked for from that menu reports a refusal beside the file, where it
was asked, rather than in the explorer's notice area that may be hidden.
The file page's Diff is drawn by the changes page's renderer, now
UnifiedDiff.tsx: the line before and after numbered, the @@ header kept, the
full width of the page; a click on a number goes to the file view.
The workbench's UI action entries move to actions_workbench.ts, the pattern
actions_feedback.ts set, to keep actions.ts under its line ceiling.
改动, 提交历史 and 文件 each head with a fold arrow and the board's title
weight. The 改动 title opens the changes page (when there are changes), its
count reads "4 个文件", and 提交… is the dark button between 暂存全部 and
推送…. 提交历史 is open from the start, names the branch on the right, and
draws each commit as its short hash, subject and age, with the author in the
tooltip; the lane graph is drawn only when the history actually forks. The
file tree gains its 文件 header. Fold buttons are named "收起改动" / "展开文件",
so they do not share a name with the toolbar's Files button.
A workbench tab "改动 n" lists every changed file of the workspace's own
repository on one page. Its head names the branch, how many files and
+/− lines, and carries 暂存全部, 提交… and 推送… (the explorer's own commit and
push cards). Each file has a sticky header with its fold, staged checkbox,
git status letter, path and +/−; hovering it offers 打开文件 and 丢弃, which
asks once more before the file goes back to the last commit. An edited
file shows its diff at once with the line before and after numbered and the
@@ header kept; a new or untracked file starts folded behind a line saying
what it is. A diff is read only once its file is open and the page shown.
The composer's "n 个文件已更改" pill now opens this page; the Files press
that used to share its signal has its own (revealFiles), so it still opens
the explorer. The workbench-browser race tests keep their guarantee, that a
browser finishing late does not take the page over, now stated for the
changes page.
The file tree moves out of WorkbenchPanel.tsx into WorkbenchFileTree.tsx to
make room under the file's line ceiling.
Tab strip: the explorer is the fixed first tab and carries the session's
change count; a new tab shows as "+ 新标签" in the strip while it is open;
the strip ends with a button that closes the workbench, which the pane maps
to the same view change the top bar's toggle makes.
New tab: left-aligned "在工作台打开" with three entries, 资源管理器 (the change
count on it), 文件… (opens the explorer with its search focused) and 浏览器,
then 最近打开 as a plain list with each file's folder in mono. The separate
"改动" entry is gone, as on the board; the count rides on the explorer.
File page: a status line under the editor names the file's language, the
line the cursor is on (CodeEditor now reports it) and 未保存 while the draft
differs from the file. It states no encoding: the workspace read carries
none, and naming one would be a claim nothing checked.
When a thought this card watched finishes, the running title "思考中 · 6.2 秒"
fades out over 200ms in the same place as "思考 6.2 秒 · 214 字" fades in,
instead of the new title alone fading up; the old one is gone after 240ms.
A running step in the activity group counts its seconds from the kernel's
startedAt, so a row drawn late (a reload, a pane brought forward) shows how
long the command has really run; a call with no start still counts from its
row.
Title: folded while it runs, it says the step under way and how long, with a
spinner and a faint sweep across the row ("执行过程 ⟳ 运行命令 go test ./... ·
12 秒 … 3 项操作"); open, it counts what runs; a failure keeps "1 项失败"; once
done it reads "执行过程 ✓ 4 项操作 · 38 秒", the time measured from the first
call starting to the last ending (else their durations added). The action
count rolls up when it changes under the reader, never on first draw, and
the group folds over 240ms.
Rows: the right side says what a step came to (48 行, 2 处, +12 −3, 退出 1, or
its seconds) instead of a duration for everything. A running step has a
spinner where its tick will be; the tick pops when it finishes under the
reader. A step that fails under the reader shakes once, and its failure sits
in a red box under the row (a host refusal, else the command's last output
line) with "看完整输出", instead of the whole call body opening. A step running
10s with nothing to show says "还没有输出 · 停止这一条". Rows entering together
fade up 4px over 180ms, 30ms apart, at most five apart.
ActivityGroup's title moves to ActivityHead.tsx, which keeps Transcript.tsx
inside its line ceiling. Reduced motion stills every one of these.
A thought the reader opened or closed is remembered per conversation and
drawn that way after a reload. Card ids are minted per load, so a thought is
named by its turn's user message index and its place among that turn's
thoughts; an unsaved conversation remembers for this load only. The pane's
memory keeps one identity per conversation and reads the latest items
lazily, so a streamed token re-renders no card through it.
While an answer streams its caret is drawn after the last word of the last
paragraph or list item rather than on a line of its own, and it blinks once
a second with a hard cut, as Run-Wait specifies.
While the model thinks the title reads "思考中 · 3.2 秒" and a light sweeps
across it every 1.6s; nothing else on the card moves. Opened while live, the
reasoning grows to six lines and then scrolls, the newest line at the bottom
with the caret. Once the thought ends the sweep stops and the title fades
(200ms) to "思考 6.2 秒 · 214 字", seconds first with one decimal; a thought
that opened on its own folds again over 240ms, one the reader opened stays.
A turn stopped or failed while the thought was still arriving marks it cut:
the title turns amber, "思考到一半停下了 · 3.1 秒" or "思考到一半出错了 · …",
with a line saying what was thought so far is inside. The title change only
animates when this card watched the thought finish, never on remount.
Reduced motion shows plain grey text and switches without animating.
The 300ms glide added for the message rail kept queuing frames after the
transcript was unmounted, which nest-mount's settling-landing test caught;
it now stops on the same condition the settling pass already checks.
The dialog opens wide on My feedback: the list on the left carries each
report's title, category and day, a waiting question as an orange
"等你回复" first in the order, and where it stands in one word (已收到,
已登记 #n, 处理中, 已在 vX 修复, 下个版本修复, 暂不做, 和 #n 重复); a report
with new replies is bold with a dot. The right is that report's page: title,
chips, receipt with copy and a "…" menu holding withdraw, the timeline across
the top, the whole exchange as bubbles with "你 · 10-30 14:02" above each,
and the reply box always open at the bottom with 附图 and 发送.
Replies are read once their report is the one on the right, so the folded
thread, its toggles and the Reply/Cancel buttons are gone; an unsent reply
is kept per report while moving between them. The header carries the tabs
(写新反馈 / 我的反馈 with the waiting count), the level line "反馈等级 n ·
已采纳 n 条" that opens level, account and leaderboard in a popover, and the
close button; the full-width toggle is gone. The refresh note and the
earlier-identity warning are small print under the list.
Marks sit at each message's place in the whole conversation (a mounted
message by its own box, an unmounted one by its block), filling the column
instead of a compact cluster in the middle. A grey band follows the viewport
and the marks inside it draw darker; a message that changed files draws
longer; stars are orange. Hovering grows the pointed line and two neighbours
each side (22/16/12px) and opens a card: "你的第n条 · 改了n个文件", two lines of
the message, and the click/right-click hint. Marks closer than 5px join into
segments of at most 56px; hovering one lists its messages with their numbers
and stars, and a press on bare track lands on the mark at that height.
Unloaded earlier messages show as a count pill at the top with a tooltip.
Ctrl+Up/Down rings the mark it walks to and shows its card. A landing on a
mounted message glides over 300ms and the message flashes. The rail needs
three of your messages, is hidden in the folded narrow layout, and reduced
motion switches without animating.
The test for BackgroundJob imported Tool from the port, which does not
export it; it comes from the wire module.
A background start carried its job's id only in the sentence the model reads,
so a window could not tie the call to the job without parsing it. The shell
execution now carries jobId on every layer to the wire. In the execution group
that call reads "放到后台运行", its row shows the job's live state from the
pane's job list — running and for how long, then how long it took and its exit
status — and while it runs a note says the turn goes on without it, with a way
to its output.
The run tabs hook no longer writes its list when a request adds nothing, which
re-rendered without end for a caller passing a fresh setter each time; the job
tab test that did so and hung its worker now keeps one. Execution's comment in
wire.ts states what the type is for rather than how it came to be.
"看输出" no longer covers the conversation with a dialog: the job opens as a
workbench tab, the same way a sub-agent's run does — one list of run tabs, one
open path, one close path. The tab names the job, reads how long it has run or
how it finished ("跑完了,退出 1 · 4 分 02 秒"), stops it while it runs, and
shows the command with what it printed. It follows the newest output until the
reader scrolls up, and an arrow brings it back. The dialog stays the fallback
where the pane has no workbench to open it in.
The status surface carried running jobs only, so a job that ended left no
trace there: the rail could never read "failed", and a person had no way to
see how long a finished build took or how a test run exited. /status now also
carries endedJobs — this session's finished jobs, kept until the session is
purged — and every job view carries the last line it wrote; an ended one adds
when it ended and, for a command, its exit status. The last line is what the
command wrote itself, kept before the finished job's in-memory tail is
released, not the host's note of how it failed. jobs stays the running list,
since that is what its readers take it to be.
The rail's list follows the board: running jobs above ended ones, each with
how long it ran, what it runs and its last line; an ended one with its exit
status; stop asks again before it acts; ended jobs can be cleared from this
window. The reading is a blue pill while one runs, stays red for a failure
until the list has been opened on it, and is grey once all have ended.
Durations pad seconds as the board writes them (4 分 02 秒).
Keying the wording tables also keyed a few strings whose readers never pass
them through t(): two thrown errors and the empty-body line in a memory's
detail. Those now translate where they are made. The test setup watches every
rendered text node and readable attribute, and fails a test whose screen shows
a message key, so a call site that skips t() cannot reach a person as
"plan.action.start".
Tests that check the wording tables read them through the keys they now hold.
The tables a call site hands to t() later — the kernel refusal and notice
wording, the settings index, glyph names, run states and the rest — now hold
keys, and their wording joins i18n/msg, the kernel's under kernel.ts. Search
keywords stay as they are: they are matched against what a person types, not
translated. Text the kernel itself sends in Chinese still resolves through
en*.ts, which stays until the kernel names those by code.
Notice wording reads a message's placeholders from its Chinese, not from its
key, and the checks read both tables through the key. Two test files broken
by the earlier test rewrite (overlapping edits in one line, and an import left
unused) are whole again.
The key batches dropped every en*.ts entry no frontend source still named.
That cannot tell an unused string from one the kernel sends at runtime — a
preset is named 日常搭档 on the wire and the window translates it on arrival —
so those reached English windows in Chinese. The legacy catalogue is back
whole until each of its readers is accounted for; it still carries the three
settings strings and the two de-duplicated keys.
Tests that kept a Chinese template in a constant now keep its key, and
find-paging no longer mocks t() into echoing its argument.
Tests that compute the expected text with t("中文") in an English window
looked up an English entry the key batches had removed, and compared the page
against Chinese. They now pass the message's key, as the components do.
Appearance, onboarding, about, safety, settings, panels and the app shell move
the same way, along with the three calls whose text sat in a nested ternary
and the effort descriptions, which a table now names by key. The table check
counts a key named anywhere in the sources as asked for, since a table handed
to t() is still asking.
Same move as the conversation areas: each call passes a key, the wording in
both languages lives in i18n/msg/<area>.ts, keys already named in common are
reused, and the English entries nothing names any more leave en*.ts.
Every t()/plural() call in these areas now passes a key ("chat.delivered_files",
"composer.task_input"); its Chinese and English sit together in
i18n/msg/<area>.ts, and a message used by more than one area lives in common.
Keys take their meaning from the English already in the catalogue. The
English entries no source names any more are gone from en*.ts.
The catalogue checks pass whole for the first time on this branch: the three
settings strings that had no English have one, and the two keys defined twice
keep the reading that already won at runtime.
A message is now t("plan.action.start"), and its Chinese and English sit side
by side in i18n/msg/<area>.ts, so neither can be edited without the other in
view. The plan card and strip, the subagent group, its rail list and its
workbench tab are the first areas moved; the rest still pass the Chinese
source text, which t() keeps resolving until each area moves.
The new tables are held by msg.test.ts: every key is area.meaning and lives in
its area's table, both languages are written with the same placeholders and no
Chinese in the English, every key the interface asks for exists, none goes
unasked, and none is defined twice. The English entries the moved areas no
longer use are gone from en*.ts.
The tab carries the delegate's colour avatar. Its head names the delegate in
mono with its model, and while the run is live offers "停止子代理", which
stops the call that spawned it. The run's first user message, the task the
main session handed over, heads the tab in its own box instead of reading as a
line of the run. The foot says the conversation is read-only and goes back to
the delegate in the conversation through the transcript's own landing.
While a delegate runs the reading is a blue pill with a breathing dot, "N 个子
代理在跑"; once all have ended it is grey, "N 个子代理 · 都结束了", counting
delegates rather than dispatch calls. Its list is the board's: a title with
running and total, one row per delegate with the same fixed-colour avatar as
the conversation's group, its name, task and state, a row opening its run,
and a stop-all that goes to the port for each running delegate.
Two or more consecutive dispatches in a run of work read as their own card:
"N 个子代理并行" with running, failed and delivered counts and a stop-all,
then one row per delegate with its colour avatar (fixed per name), its name
and task, and where it stands — the step it is on, delivered with steps and
time, or failed with the kernel's reason written out. A running row says what
it is doing now and can be stopped alone; every row opens its run in the
workbench through the pane's existing opener. When all have delivered the card
folds to a summary; a failure keeps it open. A lone delegate stays a row of
the execution group, as the board's fifth case does.
Cause: three parallel delegates folded into "执行过程 · 1 项运行中 · 1 项失败",
which said neither who they were nor which one failed.
Neighbouring behaviours tested: lanes around ordinary calls, a lone delegate,
stop one and stop all through the port, fold after delivery and not after a
failure, opening in the workbench, the reads and plan work around it.
The sidebar foot spelled its files check inside data-action, so the action
census read "files" as an action; the account row lost its last caller when the
account entry moved into settings, and its strings and styles went with it, so
the nav's own account entry names chrome.account where the census can see it;
the memory detail passed its group titles as bare literals. The action, orphan
and literal checks are back to the pre-branch baseline.
A run of consecutive reads is still folded in state; the execution group now
spreads it back into one row per call, so it reads "读取 scripts/export.py"
row by row like every other call instead of one "读取了 2 个文件" summary.
The group's count was already the number of calls.
The plan card follows the Plan-Card and Chat-Plan boards. Proposed, running,
waiting on a person and finished are the same card changing in place: numbered
steps before it starts, then the kernel's live list with a done mark, a turning
mark on the step in hand, and a badge for where it stands. The work after a
started plan is filed under its card, and the plan's own todo_write rewrites
are left out of that work because the card already draws the list.
The strip above the composer is the card's stand-in: a tab inset 14px with the
step it is on and one segment per step, shown only while the card is off
screen and no decision is waiting. Opened, it is the same tab grown upward,
titled once, with a way back to the card through the transcript's own landing.
Cause: the pending card was one sentence and three buttons, because the plan
request carried no steps, and the live list lived only in the strip, so the
plan was shown twice once it ran. The kernel now sends the starter list the
approval seeds as the plan request's input, from the same parser, and the live
event names its kind as the replayed one already did.
Blast radius: plan approvals on every frontend (the TUI keys its plan answers
on kind, which only the replay carried), the wire form of a plan approval, the
transcript's work layout around a started plan, and the plan-progress
preference, now on unless a person turned it off.
Neighbouring behaviours tested: replayed plan prompt, seed equals the card's
list, unscoped calls still send no input, revise/exit cards, fold-work folding
a card's work, refused todo_write kept, strip collapse/escape/outside press,
goal-only strip, progress preference.
Why this layer: the controller is the one place that parses a plan into steps,
so the card shows what approving starts rather than a second parse.
Reasoning heads with a 13px semibold muted line and opens to 13px/1.75 muted text indented 18px, set apart from the answer by 8px. The plan strip is the composer's own top row, as the board's "pinned above the input" state draws it: 36px on the page tone with a hair line under it, the label and count in 13px semibold, the current step muted, and, when plan progress is shown, one 14px segment per step — done in green, current in blue, waiting in the border tone — while the progress bar stays for screen readers.
An activity row whose call has no verb of its own (update plan, save memory, and the like) reads its tool label again instead of "Other".
The send/mine tabs are a segmented control; the progress filters are 26px pills whose chosen one takes the accent border; status reads as a 20px filled pill — amber when it waits on you, the brand tint while in progress or answered, green once fixed, neutral otherwise; a report's title is 13px semibold over an 11px faint line; the current step of its timeline takes the brand tint. The sheet sits on the surface tone. The status chips stay on the decorative brand token rather than the net status colour, and the token test follows the new selectors.
The board's list-and-detail split is not part of this change: every reply box and thread is scoped to its own list item today, and moving them out is a behaviour change of its own.
Spec-Overlays draws a tooltip as 5px/8px padding with the name in 13px semibold and the shortcut in regular faint ink; the shortcut had inherited the name's weight in the muted ink. The Rail-Foot board, which drew a dark tooltip, now uses the spec's too.
Spec-Lists draws a selected NavItem at 600. The settings boards wrote font-weight: 600 before a font shorthand that reset it to 500, so the left column had been matched to the slip; the boards are corrected on the canvas and the column now follows the spec, like the session rows and the memory rail.
The design-layer sheets wrote sizes as bare pixels and weights as bare numbers, so the text-size and weight settings could not move anything they drew, and the fontscale and spec-literals gates never saw them because they read src/styles only. The gates now read src/design too; sizes ride the six size tokens (board in-between sizes like 11.5 and 12.5 snap to the nearest step), weights ride --w-reg/--w-med/--w-semi, and radii ride the radius tokens.
Text is antialiased everywhere, not only in the macOS desktop shell: a browser on macOS drew the same weights visibly heavier than the design canvas, which is antialiased. The property does nothing on Windows or Linux.
Confirm and edit are 24px pills in the detail's header beside the title, with archive as an icon button there, so the actions stay in view instead of sitting under the facts. The facts drop their icons and read as two groups of four, content and use. A list row is title, the new badge, and a word only when the memory is fading or forgotten; the clear/fuzzy/faded glyph is gone, and the second line names the type before the galaxy.
Workbench (WB-Frame, WB-Parts): a raised panel with the float shadow; a 42px tab strip on the page tone with the file-tree tab first, the active tab a raised folder tab of 35px that sits into the panel, others 34px and quiet; a 40px path bar whose segments are faint 12px mono with the file name in semibold text; the read/edit/diff choice as a 24px segmented control; Save as the 32px primary button, outlined while there is nothing to save. The editor sits on the raised surface. Its default width is 520 and the drag starts at 360 with a 900 ordinary limit, as the board's width rule states.
Command palette (Cmd-Palette): 14px corners, a 52px search row at 15px with an Esc key, 11.5px section labels, 36px rows that light with the float highlight, and a 36px footer on the page tone.
A delivered file is one row in a single column: 16px file icon, 13px semibold name, 12px faint description, an Open pill and a 28px more button, 10px/12px padding in a 10px bordered card; the one open in the workbench takes the accent border. The two-column grid and the 22px badged icons are gone, and the more button keeps its "More actions: name" label.
The empty conversation drops the grid and glow it painted under the hero and sits on the plain surface, as both editor boards draw it.
Your message is a 15px overlay bubble at 14px/1.6 and at most 78% of the column; the reply head is a 20px accent R tile beside the 13px semibold name. The execution process is one card with a hair border and 10px radius: a 30px header carrying the running pill and the step count, then 28px rows of kind glyph, the board's verb (读取, 编辑, 运行命令…), the target in 12px mono, the reading, and the status mark at the right end. The raw tool id stays on the verb's title, where it is still one hover away.
The legacy !important rules on .activity-body, .activity-group and .call[data-call] > .c are deleted so the design layer can draw them; src/design/turn.css holds the replacements.
One 15px box with the composer's only shadow; a top row that exists only while a turn runs or files have changed, holding the run pill and the changed-files pill; 14px/1.6 input with 10px/16px padding; a toolbar of 28px borderless pills in the 12px meta size; the model name in 13px semibold text beside its effort; 32px round send and stop, stop drawn as a danger ring. Under 590px of conversation the pickers tighten and under 420px the effort reading folds into the model button, as the narrow-window state shows. The status line keeps 11px faint labels with muted semibold values.
The legacy !important rules on .compose, the pickers and the attach buttons are deleted, since an important declaration in the legacy layer outranks every design-layer rule; the replacements live in src/design/composer.css.
The Rail-Foot board labels each icon with its current reading; usage and remote already did, and the tools entry now reads "Tools & permissions · Ask" from the focused session's approval mode, through the same approvals table the composer uses.
The window bar is 48 high on the surface with a hair line under it; the breadcrumb is 13px and the four icon buttons are 28px, ordered find, device access, theme, workbench. The sidebar foot is the Rail-Foot board's single toolbar row: usage, files, tools, extensions, memory and remote as 28px icons whose names carry their reading, settings at the right end, and feedback as its own 30px row with the unread count. The account entry leaves the rail for settings, as the board says. The empty state's mark sits in a 44px accent tile above a 22px title.
The legacy .chrome, .railfoot, quick-nav, wallet, user-foot and hero rules are deleted rather than overridden; the replacements live in src/design/shell.css in the design layer.
Local and remote project rows carry the ds-proj class and the legacy .wsrow, .wsname, .wsacts, .rowswap, .wsadd and .wsmore rules are deleted. Rows are 30px with a chevron, folder, name and hover actions as in the board.
The --ui stack named a system Inter that is rarely installed, so Latin text fell through to the platform CJK font. The bundled Inter faces also declared font-weight through var(), which is invalid inside @font-face, so every weight resolved to the last face (SemiBold). The faces now declare 400, 500 and 600, and the stack starts with the bundled family.
Session and project row styling now lives in src/design/rows.css, the same file the design boards embed, and the legacy .sessrow, .sesstitle, .sessword and quick-action rules are deleted rather than overridden. Hover swaps the time for a more button and a pin sits right of the title, as in the board. Also repairs three tests left red by the earlier filter dropdown change.
Every stylesheet the app imports now sits in the legacy layer, and the measured settings and sidebar rules live in a design layer that wins by layer order instead of selector weight, so they no longer need :root[data-theme] prefixes. Rail pixels are identical before and after.
Four tiny segmented buttons sat in the rail, with no room for a count and no
way to ask for what was waiting on the person or what was unread.
One button now reads the current filter and opens a list of All, Running,
Needs you, Unread, Pinned and Archived, each with its count where it is known
(Archived is paged, so it shows none). Anything but All shows a clear mark
beside the button. Needs you lists conversations whose turn is waiting on an
answer; Unread lists the ones with a reply not yet seen. The rows keep the
data-action and value the old buttons had.
Blast radius: Sidebar, a new SessionFilter, the scope rule, Workspaces' call
into it, the rail's stylesheet, and two older tests that now open the menu.
Why this layer: the scope rule owns what each filter lists.
Settings pages started straight at the first card, so after a scroll or a
search jump nothing in the page said which page it was; the left list was the
only landmark. A request in the settings-redesign thread asked for a compact
page title or breadcrumb.
Each page now opens with a one-line title: the group it belongs to in the
light step, then the page name as the single h2.
Blast radius: SettingsTitle in SettingsNav, one line in Settings, four rules.
Why this layer: the nav already owns the group and page names.
Only the permission card said it was waiting ("Waiting"); a question or an
extension's request sat in the transcript looking like anything else, and the
tags counted questions instead.
The permission, question and extension-request cards now carry one tag,
"Needs you", as an outlined accent pill, which turns to "Handled" once
answered.
Blast radius: three cards, one string, one rule.
Why this layer: each card names its own state; one style draws them alike.
Cause: ten fixtures in serve, ext/plugin and platform/repair built a symlink
with os.Symlink and failed the test on error. A non-elevated Windows run
without Developer Mode returns ERROR_PRIVILEGE_NOT_HELD there, so ten tests
failed and the whole serve suite went red, though CI's elevated runners never
saw it.
Fix: skip those fixtures with the underlying error as the reason, the pattern
the boot package already uses; the refusals they pin down still run wherever
symlinks can be built.
Blast radius: ten test sites in five files, no production code.
Neighbouring behaviours tested: the same tests run unchanged where symlinks
work (ext/plugin, platform/repair, serve), and the packages vet for windows.
Why this layer: the fixture owns whether it can build its own precondition.
Fixes#14969
Selecting sessions opened a small bordered box in the middle of the list, its
buttons 5px-padded to no standard height.
It is now a bar fixed to the bottom of the list, on the float surface with its
shadow and a top edge, entering with the panel timing, and its buttons are the
small 24px pills.
Blast radius: the session-batch stylesheet.
Why this layer: these rules are the bar's only definition.
The spec sets the reply body at 14px. The reading dial defaulted to 13px in
Chinese and 12.5px in English, and its steps were built around that, and a few
execution-row sizes were multiples of it that landed between the six steps.
The standard step is now 14px in both languages (small 12; large and larger
16.5 and 19 in Chinese, 16 and 18 in English, chosen clear of the old retired
values the saved setting still migrates). The small multiples in the execution
rows name the meta and micro steps instead.
Blast radius: look.ts steps and default, the first-frame fallback, four rules.
Why this layer: look.ts owns the reading dial; the stylesheets only read it.
The stylesheets wrote about 230 weights as bare numbers (300, 400, 450, 500,
550, 600, 620, 650), so text carried weights the spec does not have and the
weight setting could not move them together.
Each now names the nearest of --w-reg, --w-med and --w-semi (up to 550,
up to 650, above), and a test keeps it so. Where the settings page re-points
--w-med to --w-semi the same rule holds.
Blast radius: 12 stylesheets and the theme test that pinned two old spellings;
450 and 550 text moves to the regular and medium steps.
Why this layer: the tokens own the weights; rules only name them.
The spec has six type sizes, 11 / 12 / 13 / 14 / 17 / 22. The stylesheets
still wrote about 300 sizes as a bare calc of pixels, among them 117 at 9 and
10px and some at 7.5, 10.5 and 11.5, so text sat between the steps and a size
setting could not keep them in proportion.
Each now names the nearest token (9 to 11 up to micro, 11.5 to 12.5 meta, 13
ui, 14 and 15 lead, 17 to 19 title, 20 to 23 display); 16px on coarse
pointers and the display figures of 25px and up are left. A test keeps it so.
Blast radius: 17 stylesheets; small text grows by one or two pixels.
Why this layer: the tokens own the sizes; rules only name them.
The task list wrote "741s" and stopped a task on one press of a small square.
It now reads "12 min 21 s", and the stop control turns into "Stop? Confirm"
for three seconds, so a stray press cannot end a running task.
Blast radius: Jobs, a duration helper with its own test, one action id, the
strings and one rule; the deck-jobs test presses twice.
Why this layer: Jobs owns how a task row reads and stops.
An audit of the live interface found small actions at 20, 23 and 26px where
the spec has one small size of 24. Stage all and Commit in the explorer
header (20), the changed-files pill (26) and the model filter chips (23) now
draw at 24, with the spec's meta size for their text.
Blast radius: four stylesheet rules.
Why this layer: each rule was the control's own size.
The palette was 560 wide at 14% over a blurred wash, drawn with the card
timing. It now follows the board: 600 wide, 12% from the top, a plain 16% dim
behind it, the large corner, and the panel timing on entry.
Blast radius: the two palette rules in studio.css.
Why this layer: these rules are the palette's only frame.
The palette searched quick actions and eight conversations only, always
mixed, with no way to look at one kind.
A leading > # or , now narrows to actions, conversations or settings (Tab
cycles, Backspace on an empty field drops it, and the scope shows as a tag in
the field). A search puts the single best match alone first, then conversations,
settings and actions, five of each at most. Settings come from the existing
settings index and open at their block. The footer prints the scope keys.
Not here: file search (there is no file index to ask) and the search-all-
conversations fallback, which needs the sidebar's content search wired in.
Blast radius: Palette, the Sidebar that hosts it, three strings and one rule.
Why this layer: Palette owns how results are grouped; the sidebar only hands
it the settings index.
Every line on the message rail was the same length, so a message that changed
files looked like any other. Those lines are now longer.
Blast radius: one attribute in Rail and one rule in app.css.
Why this layer: Rail knows each mark's file count; the stylesheet draws it.
The hover card spaced numbers from their words ("your 6th message") and gave
no hint what a click or a right-click does.
The header now reads without those spaces, names how many files the message
changed beside its number, and a line under the text says click jumps and
right-click stars.
Blast radius: the card in Rail, one rule, four strings.
Why this layer: Rail owns its card.
Workspaces.tsx reached 802 lines, over the 800-line ceiling. The choice
between a state word and the time moves next to SessionAge, which already
owns the time.
The blank page was titled in English and did not say how to tell the model is
operating a page.
It is now titled "Built-in browser", says to enter an address and that a blue
dot on the tab shows the model at work, and that a site that refuses
embedding opens from the toolbar.
Blast radius: the blank-page markup in BrowserPanel and its English strings.
Why this layer: BrowserPanel owns its own blank page.
The new tab was three bare buttons, so a first-time visitor had to guess what
"Files" or "Changes" would open.
It now has a heading and a line under each entry saying what it opens. The
buttons keep their names and actions.
Blast radius: WorkbenchLauncher, its stylesheet and four strings.
Why this layer: the launcher owns its own words.
A run with eighteen steps listed all eighteen, so the few that mattered were
buried. Past six, the earlier steps now fold into one line that says how many
and of what kind ("14 earlier steps: read 9 · search 3 · edit 2"); the last six
stay listed and the line opens the rest. A failed or still-running step is
never folded, however old.
Blast radius: the activity group in Transcript, a new pure helper, one action
id, strings and one rule.
Why this layer: the group owns what it lists; the helper only decides the cut.
Tabs were small outlined pills floating in a strip, so the current one was
hard to tell and nothing tied it to the page below.
All tabs now share one height with top-rounded corners; the current tab takes
the content's colour and edge so it joins the page, and reads heavier. The
strip sits on the page colour so the joined tab shows.
Blast radius: the workbench tab rules in studio.css.
Why this layer: these rules are the tabs' only definition.
The list said nothing about which conversation was running and had to be
tabbed through row by row. A running or waiting row now reads "Running" or
"Needs you" where its time sat, Up and Down walk the rows (and back to the
search field), and a line under the list says so.
Blast radius: RecentSessions, the Sidebar prop that feeds it, two strings and
one rule.
Why this layer: the dialog owns its rows; the sidebar already holds the runs.
The pill had a row of its own, centred under the run badge. The board puts it
on the badge's row, at the right edge.
Blast radius: one rule in changedpill.css.
Why this layer: the row is the pill's own container.
The note above the input read "No model service connected yet" with a bare
link, which did not say what it meant for what the person had typed.
It now leads with a warning icon and says "no model service is connected, so
nothing can be sent" (or the same for a missing API key). The typed text is
kept as before.
Blast radius: ConnectNote, one rule in app.css, two strings, one test.
Why this layer: ConnectNote owns this note's words.
The design spec sets three weights, 500 / 600 / 700, and the input box at the
lead size. English held 400 / 500 / 600 and the box followed the transcript
reading dial, so the two disagreed with the board.
The weight tokens now carry the spec on every language (light still steps
down, heavy steps up), and the input box draws its text at --fs-lead on
every pointer. The reading dial keeps the transcript.
Blast radius: tokens.css weights, two input-box rules, TOKENS.md, one theme test.
Why this layer: the tokens own the weights and the size.
The row's dot said a conversation was running or waiting, but only colour
told which. A running row now reads "Running" and a waiting one "Needs you"
where the time sat; every other row keeps its time or unread mark.
Blast radius: the row in Workspaces, two strings and three rules in treerows.css.
Why this layer: the row owns what sits on its right edge.
The bottom readings were bare words and numbers. Cache and the session cost
now lead with a 12px icon, the number reads in the text colour and its label
in the light one, and the warm context level keeps its number warm.
Blast radius: the MeterRail markup for two readings, two icons, studio.css.
Why this layer: MeterRail owns the readings; the stylesheet names the colours.
The context ring and percentage looked the same at 19% and at 90%, so the
limit arrived without warning. From 80% the reading and its ring now turn the
warning colour; nothing opens or interrupts.
Blast radius: the context button in MeterRail and two studio.css rules.
Why this layer: MeterRail owns the reading and states the level; the
stylesheet only names the colour.
While a turn runs the stop control was a dark labelled pill, the send control
appeared only once something was typed, the placeholder kept saying "describe
a task", and the model button carried no mark of who serves it.
Stop is now a 32px red-ringed circle and send stays beside it (grey until
there is text). With text typed during a run the send control becomes a dark
"Steer" or "Queue" pill. The placeholder reads "You can keep typing while it
runs". The model button leads with the vendor's mark. Both buttons carry an
aria-label because their text is hidden in the circle form.
Blast radius: Composer, its two button rules in studio.css, one app.css rule
(a disabled send is no longer hidden while running), one string.
Why this layer: the composer owns what the send and stop controls say.
The model and the reasoning effort were two buttons with two menus on either
side of the input box, though effort is a capability of the model and the
level was written twice ("Auto" in two languages).
One button now reads the model and the effort once, and opens one panel: the
models on the left as before, the efforts of the current model beside them.
Picking an effort applies it and closes the panel; picking a model applies it
and leaves the panel open so an effort can follow. A model that reports no
levels still points at where they are declared. Picker gains an aside column
with its own close handle, a stay-open rule, and Left/Right to cross columns.
Blast radius: Picker/MenuRow, Composer, the effort helpers and the composer
menu, narrow and studio stylesheets (the old effort button's rules are gone).
Neighbouring behaviours: tests that opened the old effort button now use the
merged one; the new panel has its own interaction tests.
Why this layer: Picker owns menu behaviour; the composer only says which two
lists go in it.
The attach, side and refine buttons were 29px, the branch chip 29px and the
mode and permission chips 32px, so the row of controls under the input
stepped up and down. The board sets every selector at 28px with the send
button at 32px.
Blast radius: the studio.css rules for the toolbar controls; the narrow-window
icon-only mode picker follows (30px to 28px).
Neighbouring behaviours tested: the style suites and the full desktop suite;
the box was rendered in both schemes.
Why this layer: the sizes live in these rules and a test now pins them.
The design spec steps corners 5 / 7 / 10 / 15 px, sets the lead size at 14px
and times a line, a card and a panel 120 / 180 / 260 ms. The tokens held
4 / 6 / 9 / 12, 15px and 140 / 220 / 340 ms, and 199 corners and 34 timings in
the stylesheets were written as bare numbers, so moving a token reached none
of them.
The tokens now carry the spec's values, every bare corner of 3px and up draws
from the nearest radius token (so a pack's corner recipe reaches it too), and
every bare 100-150ms timing uses --t-line. Two ratchet tests keep it so.
Weights are left alone: the language and weight settings already own them.
Blast radius: all stylesheets; corners grow by about a pixel and 15px text
becomes 14px where --fs-lead is used, motion is a little quicker.
Neighbouring behaviours tested: the style suites and the full desktop suite;
chat and workbench were rendered before and after in both schemes.
Why this layer: tokens own the value, stylesheets only name it.
The list ran together from the first row to the last, the search box had no
icon, and the environment choice took a labelled row of its own.
Rows now sit under "today" and "earlier" headings, the search box carries its
icon and shares one row with a compact environment select (still named
"environment" for assistive tech), and the empty state and paging are
unchanged. The board's running-state dot and arrow-key hint are not built:
this list has no running state and no arrow-key navigation to back them.
Blast radius: RecentSessions and treerows.css; picking, paging, retry and the
unsupported-environment message are untouched.
Neighbouring behaviours tested: paging with retry, locating a collapsed
project, environment switching, plus the full desktop suite.
Why this layer: the dialog owns how its own rows are grouped.
The dialog asked for the target in a drop-down and put what travels and what
does not into two run-on paragraphs, so the consequence was read last.
It now lists the target projects as a radio list with their paths, sets what
travels and what stays behind in two short columns, and with no other project
offers to add one right there. Footer buttons use the shared medium buttons.
Blast radius: MoveSessionDialog, the hook that hosts it, one action id and
the English strings; the other pick dialogs keep their own button styles.
Neighbouring behaviours tested: move succeeds, picks the chosen project, is
refused with the kernel's reason, partial move, empty state with Add project,
focus on open, plus the full desktop suite.
Why this layer: the dialog owns its own content and the host passes the
add-project action through the hook rather than the dialog reaching for it.
The menu was one flat list: close and branch sat between rename and archive,
export appeared twice, and why an item was unavailable lived in a tooltip.
It now has five groups (position, name, content, destination, ending), one
export entry with Markdown and JSON as choices, pause first while the
conversation runs, and the reason a close or move waits written under the
item and tied to it with aria-describedby. The menu is 260px wide.
Blast radius: SessionMenu and its styles; every action keeps its data-action
and accessible name, so the sidebar, sync, move and close suites are untouched.
Neighbouring behaviours tested: new group-order, export, running-state and
spaced-path tests, plus the full desktop suite.
Why this layer: SessionMenu owns the order and wording of the row actions.
Failures, notes, offers, the update reminder and the size question were five
hand-made boxes of different widths, with the failure told apart only by red
text and its actions drawn as underlined links.
They now render through one ToastRow: a fixed 380px column, a leading icon
that tells a failure from a note, and the shared small pill buttons for the
actions. The size question stacks its choices under the text.
Blast radius: ToastStack, UpdateReady, SizeAsk and toasts.css; behaviour
(stay until dismissed, at most three, report, restart, zoom) is unchanged.
Neighbouring behaviours tested: the existing toast, update-ready, versions and
appearance suites plus the full desktop suite.
Why this layer: one component owns the notice shape so a new kind of notice
cannot be drawn a sixth way.
Cause: Studio's inbox is schema 2 while 1.x writes schema 3, so a session
that had been used in 1.x opened as "written by a newer version" and its
queued messages could be neither sent nor deleted.
Fix: accept schema 3 and keep its fields (fingerprint, attachment identities,
image inputs and source refs) through every rewrite so nothing 1.x wrote is
dropped. This build cannot read 1.x image inputs, so an entry carrying them is
held with the foreign_images block code on both the run and steer paths
instead of being sent without the images.
Blast radius: state/sessioninbox (envelope, request hash, images flag), the
two dispatch paths in session/control, and the block-reason tables in the
desktop and terminal frontends.
Neighbouring behaviours tested: queue tests of the inbox, control, tui, serve
and i18n packages, plus the desktop queue and translation tests.
Why this layer: the envelope owns what is persisted and the shared
applyInboxReferences owns whether an entry may run.
The box under a question's options only switched to Other once
something was typed, so a click into it left the radio empty and
the card read as if nothing were chosen. Focusing the empty box now
selects Other; a single pick's note box keeps that pick, and
multi-choice picks stay alongside.
kit.css draws the text inputs, textareas and unit fields inside the settings
pane: 32px high, 7px corners, 13px text, one focus ring and one error ring;
the per-model rows of model services use the 24px size. The unit field's box
is the outer span, its input stays borderless inside it. The rules that drew
these fields per page (session limits, providers, network, remote, backup,
appearance, peers, presets, advanced fields) keep only widths and the mono
family for addresses and keys. Two label-span rules in model services no
longer reach the unit field. field.test.ts holds those page rules to it.
kit.css owns single-choice selects: 32px high, 7px corners, 13px text and one
12px arrow set in 10px from the right edge, with the same hover, focus ring and
disabled look everywhere; data-size="sm" is the 24px variant for list rows
(permission rules, the phone-access address). The page rules that re-set the
native look for model roles, providers, permissions, network, remote, phone
access, memory editing, presets and the recent-machine picker keep only their
widths. select.test.ts holds every other stylesheet to that: none may set a
select's size, corners, padding or arrow.
CI's Windows full suite saw the index answer State=ready with Indexed=Total=1
while a rescan was still pending, so SearchIndexProgress honestly reported
building and the test, which had only waited for the hit to appear, failed.
The test now retries until the projection reports every source indexed and
nothing pending, fails at once on any unreadable-source signal, and only then
asserts that no incomplete-index signal is raised.
Not reproduced locally: the base passed 20x on macOS, 90x under six concurrent
processes on Windows, and a full package run on Windows.
Small buttons are 24px pills, medium ones 32px with the 10px corner, icon
buttons 28px squares with the 7px corner. kit.css carries the shared rules;
the page-scoped rules that re-set height, radius, font size and weight for
the title bar, the project tree, reply actions, model services, remote,
appearance and automation now use the same values. A browser sweep of the
main window and all sixteen settings pages measures 324 buttons, 0 off spec
(105 before).
The step runs only ./internal/runtime/agent, but its jq still required a
passing package event for internal/assembly/boot after the task budget
tests there were removed, so the step failed on every pull request.
The memory page drew 参考, 用过, 生效, 范围, 低, 高 and 所有项目 without
an English entry, and its new demo facts were not on the English-window
guard's fixture list, so browser guards failed for every pull request.
The memory page lists facts by type and galaxy with a search box,
shows each fact's retention, uses and last recall, lets a newly
learned fact be confirmed or archived from the list, and edits its
type, galaxy and importance.
The memory list now carries each fact's galaxy, importance, retention
state, use count, days left before it turns fuzzy, whether it decays,
and whether it is new (written in the last 14 days and not yet reviewed).
POST /memory/confirm reinforces a fact and marks it reviewed; a save from
the page also marks it reviewed and may move its galaxy, importance and
type. The review mark lives in the usage sidecar, so it never rewrites
the fact file.
Each fact has a retention of e^(-days since last use / strength). Reading a
fact multiplies its strength by 1.8 and an automatic recall by 1.2, capped at
365 days; constraints, pinned and evergreen facts never fade. Recall keeps a
clear fact's score, scales a fuzzy one down and holds a faded one at 0.35 so a
strong match still comes back. Facts recalled in the same turn build a link,
and a link of weight two brings its neighbour along. Usage lives in a
.usage.json beside the facts, so a recorded use never rewrites a fact file or
moves its revision. memory search and read report the state.
Automatic recall scores the name, title, description, galaxy and body as one
field and the synonyms as a second field at 0.6, then weights the result by
importance (0.85, 1.0, 1.15) and a 1.1 boost for facts used in the last 30
days. A constraint is brought whenever its galaxy is named in the turn or is
the galaxy of one of the three best hits, or when its own score reaches 8% of
the best, and it leads the block; at most three are held and an expired one is
never recalled.
Compacting by hand accepted any real saving only while the context was still
below the automatic trigger. Above it, the request had to land below the
trigger or was declined, so the moment compaction was most needed the user
was told there was nothing to compact. A manual request now accepts any fold
that is strictly smaller; an unchanged context and too little new history are
still declined as before.
task_cost_budget, task_time_budget_minutes and task_token_budget no longer
exist: a task is not capped by what it spends. Old config files carrying them
are cleaned on start with a one-time notice, and the settings declaration and
config template drop them. The budget machinery a Goal run injects for itself
(goal_token_budget, off by default) and the run usage accounting stay. The
effect tests that only exercised the configured budgets are removed, and the
Windows CI steps that named them are pruned in the same commit.
Facts gain optional galaxy, importance (1 to 3), strength (days), last-used
time and use count in their frontmatter, and a constraint type that never
fades. Absent fields resolve to defaults on read, so existing files stay
valid and are not rewritten; an update that omits them keeps what is stored.
A constraint is written with a project routing type so an older binary
still files it correctly. The remember tool accepts galaxy and importance,
and memory read reports them.
Context maintenance follows one trigger: a share of the model window, now
0.90 by default for new configs (existing files keep what they saved).
Removed the absolute-size economic boundary and its setting, HTTP route and
settings UI, the draggable fold handle in the context panel, the retired
multi-threshold and cold-resume keys (old files are cleaned on start), and
the failure-output elision applied to retained messages during a fold,
including the extra model call that picked the lines to keep.
The pull-request-only Windows steps listed tests by name and failed when one
was missing. The sub-agent, isolation, background-job and carried-proof
rewrites deleted a number of them, so every pull request failed its Windows
check. Drop the steps whose whole list is gone, prune the removed names from
the rest, and remove the concurrency inventory runner whose two inventories
named nothing that exists.
Rewrite internal/runtime/delegation around one Runner and one tool.
spawn_agents runs 1 to 8 independent tasks in parallel, each in its own
session with three base tools (read_file, write_file, bash); plan mode and
read-only skills keep a read-only child. A child's full answer is archived;
the caller gets the opening, a reference, and the files the child read and
wrote, and pages the rest through read_subagent_result.
Removed with it: task, read_only_task, parallel_tasks, fleet,
list_subagents, background sub-tasks, steering and its limits and settings
UI, continue_from, write_paths claims, isolated worktrees, best-of-n, the
run graph and the runtime-resume benchmark. Stale writes by children stay
refused through the per-agent file views.
The use_capability description no longer names task:fleet, so the stable
prefix and its golden files change once.
complete_subtask, the host adjudication of its criteria and the 'lowered by the
host' note are gone, with the handoff audit channel and its metrics, trajectory
field and benchmark columns. A delegated child ends the way any turn does and
the parent reads its last reply.
The host's own receipts appended to a child's reply and the write-scope check
stay: they need a record of what the child changed, which is replaced with the
evidence ledger later.
complete_step now only says a step is done: it names the step and what is now
true, and the host advances the task list as before. It no longer asks for
commands, files or criteria to cite and no longer checks them against the
session's receipts.
conclude_blocked existed to let the readiness gate through and goes with it;
the prompts that pointed at it now ask for a sentence in the reply. Plan steps
lose their acceptance and verification fields: the submit_plan schema, the
planner and executor prompts, the rendering and the re-approval rule all
stop mentioning them, and plans saved with them still load.
The executor's no-action nudge now keys on a plan naming files only.
With the final-readiness gate gone, FinalReadinessError, the readiness audit
and the structured readiness on TurnDone have no producer, and neither do the
contract-shadow, completion-report, evidence-bundle, outcome-progress,
protocol-recovery, verification-drift and project-check-probe audits. Removes
the types, the sink capabilities and their forwarding in every wrapper, the
trajectory and metrics fields, the final_readiness outcome and notice code,
and the delivery-recovery submit port.
The ACP status shape and the desktop reader of old sessions keep their
readiness fields: external editors and recorded sessions still carry them.
Both only steered the final-readiness gate, which no longer exists: the flag
chose exit 3 for an unmet readiness verdict and the setting chose whether a
changed turn was held for a check, a review and a sign-off. Neither can do
anything now.
`run` still exits 4 when an untrusted folder refused its edits. A user config
that still carries host_verification loads unchanged; the key is ignored.
Removes the HoldOwedChecks and WaiveOwedChecks plumbing behind them.
A turn that ends with its answer is done. The agent no longer asks, at the
point of stopping, whether a check ran after the last write, whether a
high-risk change was reviewed, or whether captured test criteria still hold;
it no longer ends a run with a readiness error, and the controller no longer
re-runs the turn to satisfy one.
Goal keeps its update_goal report and the evaluator, and still holds a
complete claim while the task list is unfinished; it no longer reads a
readiness result or freezes a verification contract. Review sub-agents are
ordinary sub-agents: nothing requires them to file a typed report.
Removes the readiness, owed-review, baseline-criteria, carried-proof and
review-report code, the delivery checkpoint, the readiness continuation, the
orphaned helpers they left in evidence, and the tests that asserted the removed
behaviour.
Cause: the catalog hides a source with health<>'ok' from the query
(internal/state/historycatalog/catalog.go:520) and drops a candidate whose
transcript fails to load after RecordReadFailure, but /history/search answered
with hits only. The rail renders nothing for zero hits, so a search over an
incomplete index reached it as the same answer as a query that matched
nothing. The Agent history tool already carries the incomplete notice; the
desktop half lost its equivalent when the Wails history panel
(partial + status.indexed/total) was replaced by frontend-next.
Fix: /history/search answers {hits, index{state,indexed,total,failed,partial,
reason}}, read from the counters the catalog already publishes after the
search, with one shared predicate (history.SearchIndexProgress) that the
history tool now uses too. The rail renders one line while index.partial:
"could not be indexed" for reason=failed, the catalog's own progress for
reason=building. A complete index renders nothing extra.
Blast radius: the /history/search wire shape changes from a bare array to
{hits,index}; its only consumer is the Electron rail in this tree. A degraded
in-memory projection no longer reports "still building" once its counters
agree it answered for every source.
Verification: new serve tests drive an unreadable event log end to end and
assert the response carries failed/partial with zero hits; a control asserts a
healthy index adds nothing for a matching or an empty query. The rail tests
cover the failed line, the building line, matches kept beside the line, and the
silent healthy case.
A session method refused because another runtime owns the transcript answered
-32600 and prose, the code a malformed frame gets; an ownership probe that could
not decide answered -32603, the same as a disk that would not write. A client
could not tell "held" from "unreadable" from "your request was broken", so it
could not decide whether to ask the person to close a window or retry.
session/new, session/load and session/resume now answer -32022 with data.code
busy.session_in_use for a held lease and -32023 with data.code
session.lease_unprovable when the store's probe cannot prove ownership either
way; -32600 is left to a genuinely malformed request. The human message stays.
The HTTP frontend, the kernel, the lease and the inbox are untouched.
docs/ACP.md states the table and what a client does with each answer. repolint's
family-vs-writer check is now parameterised by family; it reads the lease
sentinels beside the inbox ones, so a lease sentinel added without a coded case
fails by name against the ACP writer.
A turn no longer builds a shadow task contract, a completion report or a
sealed evidence bundle, takes a workspace snapshot, or hands the host a
completion receipt. Removes the agent-side producers and their wiring in boot,
the observation and verdict packages that only they used, and the
write-protection and unconfined-launch probes that only boot read.
The receipt and audit shapes on the event wire, the terminal and the desktop
settings stay for a later cleanup; nothing sends them now.
The loop no longer classifies a tool-call turn that arrived without
reasoning, retries it, or keeps a file-backed cooldown for it. Removes the
watch and the cooldown state, the deferred stream sink they needed, the
provider policy interfaces and their implementations, the descriptor field on
the extension wire (schema and SDK regenerated), the state-dir path, the
run-metrics counters and the protocol-recovery kinds.
The protocol-recovery audit channel itself stays for now.
Electron 45 ships electron_wer.dll, unsigned, and no longer ships dxil.dll.
Sign electron_wer.dll with the other unsigned PE images, and stop requiring
a Microsoft-signed dxil.dll. Checked against the 45.1.0 win32 x64 and arm64
archives: both hold exactly the declared set.
A long wait keeps the running state, and its three infinite animations, on
screen for minutes; the R mark's stroke-dashoffset trace alone costs a style
recalc every frame. Reduced effects, the mode meant for a window that keeps
the GPU busy, now draws the mark whole and stops the row sweep and spinners.
Fixes#14919
The assertion said only that the prefixes differed. It failed once on the
Windows full suite and passed on rerun; report the first differing byte with
context so the next failure says what moved.
.docx and .xlsx were classed unsupported, so a delivered report or sheet
could only be opened in the system app. The kernel now serves them as an
office preview kind (binary, 32 MB), and the workbench renders them to static
HTML (docx-preview; ExcelJS with SSF number formats) shown through
staticDocument() in a sandbox="" frame, so nothing in a file runs or fetches.
Both parsers are lazy chunks, and a machine setting turns the preview off.
Refs #14622
--password put the serve password in argv, where ps and /proc/<pid>/cmdline
show it to every local user; --hash-password needed it there too. Add
--password-file, held to the same single-line, owner-only rule as
--token-file, and accept it for --hash-password.
Fixes#14913
seedAged set file times only, but listings read activity from the branch
meta, which SaveSnapshot stamps with the write time. Every seeded session
was therefore 'now', ordered only by milliseconds of write order, and the
recent-order tests failed whenever two writes shared a millisecond.
Write the intended time into the meta as well as the transcript and event
log, so the seeded ages are the ones the listing reads.
a2ui 0.14 types SurfaceModel.defaultCatalog as possibly undefined. Studio's
surface always names its catalog in createSurface, so the catalogue checks
still hold; an absent catalog now fails them rather than the type check.
The host already opens a turn when a background job ends, but bash's launch
message, bash_output and wait only offered more polling. A long build outlived
several capped waits and the model closed the turn reporting it unfinished.
The manager now answers whether a completion will resume a given session, and
the three readers state it for running jobs only where that holds.
Refs #14879
At phone width the sidebar menu button touched the logo, Settings put Close on
its own row under the search box, and each settings tab kept a 6.5em name
minimum so only three fit. The logo gets a left gap, the header grid holds
expand and Close side by side, and tab names size to their content.
The settings type rule recoloured every enabled primary button's text to the
accent foreground and relied on each container to supply the fill. A container
with no fill rule of its own, such as the scheduled-run form, showed light text
on no fill, so Preview and Confirm looked blank. The rule now carries the fill
and border with the foreground.
The check counted open pages and then created one, so concurrent agent opens at
the limit could all pass. A reservation counts pages still being created, and is
released once the page is in the list.
A model that opened a page per search result left dozens of renderer processes
running; one report shows 30 pages and about 2 GB held. Only a prompt hint asked
the model to close pages, and the shell's own sleep cap cannot keep up with a
burst of opens. The session now refuses a new agent page once 12 are open and
says how to make room; a page the person opens is never refused, though it
counts toward the total.
On Windows a regular file at the sessions path is reported as absent, which is
the normal fresh install, so the fixtures could not make an unreadable source
and the two tests failed on every Windows push run. The fixtures now skip there
and no longer claim the file fallback works on every platform.
clean() clipped attribute values to VALUE_CLIP but left keys unbounded, so a page could return thousands of long attribute names. Keys are now clipped with the same limit, with a unit assertion for a 600-character key.
The Windows full-suite step failed twice within a few hours on push runs, with
internal/assembly/boot at 1630s and then at 1680s, which is its 28m package
limit. The package alone can take over 25 minutes on a runner, so the limit
becomes 36m inside a 45m step budget.
verify() now asks the isolated world like lands() does, the attribute map is capped at 64 entries, and a held Ctrl+Shift+C no longer re-arms the picker on every repeat.
The sidecar is JSON written by json.MarshalIndent. Reading it through the
GB18030 detection cascade meant a single stray non-UTF-8 byte re-decoded the
whole file as GB18030, where a lead byte before a Windows path separator
swallowed the backslash of an escaped "\\" and left an invalid \W escape.
Decode the bytes as written first; transcode only when that fails.
Signed-off-by: ShryukGrandhi <[email protected]>
`doctor runtime` always passed a nil BuildResult, so it read the empty
process-local DefaultRuntimeOwner and printed clean=true/cleanRollback=true as
facts about a session it could not observe. The receipt ledger is in-process
only, so a fresh CLI cannot know whether a crash or mid-migration left
irreversible work.
Report recoverability and resume as nil (JSON null) when no runtime was
available to ask; the text says unknown and names why. The BuildResult seam
still computes and prints the observed verdicts exactly as before.
Problem: upstream studio changes made the model-health UI PR conflict in Composer.
Root cause: studio added WorkspaceInfo while the health hook removed the former
inline ModelEntry loading state in the same import declaration.
Fix: retain studio's workspace information, running-message intent and composer
hint behavior alongside the consent-gated health hook. Remove the unused
ModelEntry import. The grouped picker, default-off setting and old-server hints
remain unchanged; the feature diff remains 514 additions across 22 files.
Verification: production frontend build, frontend tests and census; go vet,
pinned lint and repository checks; HTTP health authorization/settings tests,
builtin tests and inventory-reconciled boot partitions.
The page wrote the answer, so the shell now rebuilds it from an allow-list, clips every string to the page script's own limits, drops unknown fields and treats a shape it cannot read as gone. A rect that is not numbers is dropped; a negative origin is clamped by the screenshot.
`git worktree add` creates the new entry's directory under
<common>/worktrees before it writes the entry's files, and every worktree
command resolves each entry's HEAD, reading its commondir and dying on one
it finds empty: "failed to read .../commondir: Success". Two isolated
tasks started in parallel each create a candidate from the same
repository, and on a loaded Linux runner the second add read the first's
entry in that window (TestPreparationConcurrentCandidatesDoNotShareResultState,
run 40689344657).
Every `git worktree` command in the package now goes through runWorktree,
which holds one gate per repository, keyed by its common Git directory,
and gives up waiting when its context ends.
An earlier version or recovery copy has no row menu; its × is the only
way a pointer deletes it. `.rail .sessrow > .wsdel { display: none }`
came in with the Studio rail when session rows moved deletion into their
menu, and since then the copy row is the only one that renders .wsdel,
so the rule hid exactly that button. The hover, focus-within and
hover:none rules beside it were already there to show it.
perf/sessionrows.mjs now puts the button on its earlier-version row and
holds it shown and inside the row on hover and on keyboard focus.
The shell half: the in-page picker script, pick / cancelPick / verify on BrowserViews, the three IPC handlers and preload methods, and the unit and live suites. Inert until a control calls it.
An unsigned package cannot be installed at all, so publishing one put an
unusable file on every release page. The wait was also shorter than the
release actually takes.
The fragment lost its issue reference when the lines were cut to one
sentence each, and the release-note lint asks every change to name the
issue or pull request it belongs to. Both lines now end with the
reference, and both stay under the 200-column limit.
Kernel half of session batch delete. The handler validates the whole batch up front (bad path -> 400, empty -> 400), refuses a running session (409), a leased transcript (409), and a path outside the adopted workspace (403), then erases all guarded sessions.
No desktop UI in this change; the management view lands in a follow-up PR.
Go tests cover the handler, including two new edge cases:
- a path inside the session dir but belonging to another project (refused wholesale, 403, the in-workspace file left untouched);
- a running session in the middle of the list (refused with 409, nothing erased before the refusal).
Cuts the release-note fragment to one sentence per line and drops the
self-reference, so each line stays within the 200-column limit.
The settings pane schedules a tally of the disk usage a beat after it opens,
and that walk reads the port. The search test measures the port as it stood
before the search, and reads it again afterwards to prove the search read
nothing. When the two moments fell more than a beat apart — which the full
suite's load makes likely — the tally landed in between and the test failed
on a read the search never asked for.
Seeding the tally before the pane opens means no walk is scheduled at all, so
the search is measured against the port as it stood, with nothing else reading
it on the way past.
The failing assertion is unchanged: the search still has to read nothing.
stderrSuffix capped git's stderr at 500 bytes with a plain slice, so a localized
(multi-byte) git message was split mid-character and the error ended in invalid
UTF-8. Back the cut up to a rune start.
A conversation branched from a compacted session reopened on the full
history. createBranchPrefix copied the transcript, the branch metadata and
the checkpoints but never the projection sidecar, and resumeInto binds with
loadSidecar=true, so the fork found no sidecar and visibleByFullScan handed
the model the whole transcript. Recovery forks and 1.x continuations already
go through InheritCompactionState; the user-facing branch path never called it.
BranchAt now inherits inside the branch's own save window, next to the
checkpoint copy, so a branch is never published with a fold it cannot read.
Controller.Branch, which swaps panes in place, binds with loadSidecar=true so
the same-pane path loads what it just inherited.
The inherit helper keeps its fail-soft contract: no source sidecar, a corrupt
or unsupported one, a projection whose covered prefix reaches past the fork
point, and a failed write all leave the fork on the full history. Recall
budget, blocked fields and a non-applied receipt belong to the source
session's runtime generation, so the copy clears them; the projection,
generation, lineage key and an applied receipt still travel.
A batch printed its aggregates and kept nothing they could be recomputed
from, so a published figure could not be tied to the runs behind it after
the temp directory was gone. -batch DIR now writes results.jsonl (one row
per attempt) beside batch.json (manifest v1), and -mode audit re-derives
the report's headline figures from those rows with the harness's own
aggregation, printing the claim beside the recomputation and naming every
figure that moved.
The manifest records what the figures are over (command axes, a task-id
digest, filters, and a denominator defined in tasks), which run produced
them (commit or an explicit unknown, model/provider names, seed when
known, timestamps, harness and runtime), and why they can still be
checked (results_sha256, the claimed figures, and a ledger of planned /
attempted / completed / scored / failed / unmeasured / excluded that the
audit checks for consistency). Absent figures are omitted and read as
unknown, never as zero.
This is an aggregation audit, not a replay: it proves the published
figures follow from the retained rows. It does not re-execute the model,
tools or environment, and a real replay (commit, corpus fingerprint,
model/provider configuration, seed, flags, raw results, runtime version)
is not implemented.
Problem: Accepted total-only usage remained visible to reporting but did not
cross token budgets, allowing additional tool rounds after the known spend.
Root cause: Both local and shared-root enforcement added only input/output
components, so missing or partial splits hid a known reported total.
Fix: Use one accounting helper for the larger of the nonnegative total and
component sum, and saturate both counters. Keep usage, pricing, shadow component
telemetry, context ownership and existing soft-gate/finalization behavior.
Verification: Real OpenAI HTTP/SSE adapter and Boot execute real file tools.
Final19named regressions pass; exact preceding owners fail10 events with9controls.
Root240packages once:24330named passes/215existing skips. Complete Agent race
2162pass/10existing skips; focused Boot race7pass. Vet, pinned lint, check, format,
workflow and release gates pass; four Win/Linuxamd64 checks compile only.
Problem: An isolated candidate deletion could follow a source directory link
and remove a file outside the repository while reporting success.
Root cause: Leaf comparison followed ancestor links and publication deleted
through an ordinary pathname, without an ancestor precondition or held root.
Fix: Check ancestors before path-by-path leaf hashing and before publishing
deletions, and delete through an open repository root. Preserve whole-workspace
file-to-directory conversion and the Store's existing same-ID conflict retry.
Extend the existing Windows isolation inventory with junction regressions.
Verification: Final root inventory covers 240 packages once with 24324 named
passes and 215 existing skips. Both required package commands exit zero.
Complete worktree/isolation race has 242 passes and no skips. The final focused
13 names pass; the exact preceding owner fails 9 events with 4 controls.
Vet, pinned lint, check, format, workflow and release-note gates pass. Windows
and Linux amd64 compile checks pass; native hosted verification is pending.
The implicit legacy-session import passed no report, so a ReadDir failure
became a no-op skip and returned (0, nil). /migrate then told a user whose
legacy sessions directory existed but could not be read that there was
nothing to migrate.
- sessionstore: return a typed LegacySourceError (a SkippedSession plus the
cause) from the implicit path; an absent directory stays the silent no-op.
The explicit --from path keeps reporting through its LegacyReport.
- migration: /migrate names the directory and the read failure; the summary
counts it as a warning instead of "no legacy data needed migration".
- boot: unchanged, stays quiet — the boot caller is non-verbose, so the new
error is recorded but not emitted, exactly like every other failed import.
- markers: the failure returns before the marker write, so a source that was
never read is never recorded as imported; a repaired source imports and
stamps on the retry.
Problem: Linux coverage could not establish the carried-proof fixture;
CodeQL also flagged the shared inventory parser for exponential backtracking.
Root cause: the fixture inherited an unavailable host bash sandbox, and
repeated name-suffix groups could repartition the same slash sequence.
Fix: select sandbox off only in the isolated test user configuration;
parse the entire optional suffix once and add a bounded long-input test.
Verification: required builtin/Boot exit0, focused coverage four named
passes, 14-name shared contract, two Node tests, vet, pinned lint and check.
Original regex fails the bounded regression with one passing control.
No production Go, assertion, workflow, timeout, skip or baseline change.
Hosted Linux coverage, CodeQL and own-head Windows evidence remain due.
Problem: The govulncheck job on #14676 reports GO-2026-6860 through
the HTML parser used by the price-checking tool.
Root cause: This branch still selects golang.org/x/net v0.61.0, although
upstream #14685 has already upgraded both Go modules to the fixed version.
Fix: Apply upstream commit 80ebd88bf7
unchanged, selecting x/net v0.62.0 in the root and desktop modules.
No workflow bypasses or suppression rules are introduced.
Verification: govulncheck v1.9.0 with Go 1.26.10 exits successfully for
both modules with zero reachable vulnerabilities. Desktop build, network
and environment package tests, full builtin/boot and pricecheck tests,
go vet, make lint, make check and whitespace checks pass. The staged
four-file patch is byte-identical to the upstream commit.
Co-authored-by: esengine <[email protected]>
Problem: CodeQL reports exponential backtracking in the shared Windows
inventory test-name parser.
Root cause: repeated suffix groups can partition the same slash sequence.
Fix: accept the entire optional non-whitespace suffix once, preserving the
previous accepted language. Cover nested names and a long malformed suffix.
Verification: two Node tests pass; the original regex fails the bounded
regression while the existing contract test passes. Go and inventory bytes
are unchanged; prior exact-head Go qualification remains separate.
infoExcludeFile now reads .git, commondir and HEAD with the same reader
At uses for the ignore files, instead of its own os.Stat/os.ReadFile calls.
A .git directory or a missing file reads as nothing, so the plain-repository
path is unchanged, and a pointer is still followed only to a directory with
a non-empty HEAD file. This drops the new path sinks CodeQL flagged.
SessionLeaseHeldByOtherRuntime folded every lease-lock probe failure into
"held", so a lock it could not open became the same answer as a live holder.
Redaction then skipped a session nobody held and reported an active lease,
leaving the secret scrub silently incomplete; session and task listings
claimed active/running on the same non-evidence.
Add ErrSessionLeaseUnprovable beside ErrSessionLeaseHeld and expose
SessionLeaseOwnership as the three-state probe. Only ErrSessionLeaseHeld
proves a holder; any other lock failure is unprovable with its cause kept.
The bool wrappers keep their old "may be busy" answers, so callers that only
skip work are unchanged.
Redaction refuses an unprovable session, reports it as a skipped file with
the reason (and exits non-zero), and never calls it an active lease. Machine
session state and running task/subagent status become unknown when ownership
cannot be proven; held and free keep their current answers.
The registry declares 324 pairs now: the 202 it already had and 122 page
copies that agree field for field with the struct their handler writes. The
table moved to a second file, wireparity_table.go, rather than taking
wireparity.go past the 800-line ceiling; the checker still reads one list.
Left out on purpose, because the writer is not a named struct and a guard
over the lookalike struct would not see the response drift:
account.DeviceGrant (/account/login assembles a map), market.Page
(/market/packages assembles a map) and control's McpDraft (/mcp/parse
assembles a map). host.ts's Refusal and ViewRect are the Electron preload's
bridge, not kernel wire; workspace.ts's WorkspaceEntry has no writer.
Tests: the added half resolves, is exact today and is unique per reader; a
dropped field on createBackupRequest -> BackupCreateRequest names the field.
The terminal's hand-written HistoryMessage dropped images, via and modelRef.
A turn that was only an image has empty text, so Restore saw nothing to draw
and dropped it from the transcript, and every reload lost the device and the
model of turns it did keep. Carry the fields that decide that a turn exists
and what it was, restore the image-only row with an honest line, and register
the pair in repolint so a dropped field fails make check.
Problem: Release notes did not describe the snapshot ownership fix or its
possible one-time cache-prefix change for previously affected sessions.
Root cause: The impact was documented only in the PR and user guide.
Fix: Add Chinese and English unreleased entries preserving the first-request
cache-reuse caveat requested during review.
Verification: Release-note rendering/reference tests (12) and make check
pass; the staged diff contains only the two-line documentation fragment.
The navigation already shows the section and page, so the first card now sits
at the top of the page. The setting count and the section's value stay in the
navigation row.
Problem: Saved connections lack health evidence when selecting a model, while
automatic credential-bearing checks require an explicit user opt-in.
Root cause: The previous UI slice also redesigned the disabled picker, hiding
account endpoint headings and reducing density outside the requested scope.
Fix: Preserve the original grouped rows, hosts, ordering and modelLabel behavior.
Add only short accessible health chips after reading backend consent. Put the
global switch outside the service editor, and explain unsupported older servers
and settings-read failures without enabling probes. Rebase onto current studio.
Verification: 640 frontend tests, production build, census fixtures, vet, pinned
lint, repository checks, HTTP authorization/route tests and builtin tests pass.
All 849 boot cases accounted for: 846 pass, 3 skip. Negative controls fail the
group-layout and old-server regressions. Native Electron light/dark wide/narrow
captures and 390px browser inspection verify retained row density and hosts.
Native disable/reopen issues no new requests; no generation requests occur.
Problem: Competing continuations could create two copies of one ancestor in
the same parent session, leaving later continuation ambiguous.
Root cause: The source ref lock ended before a newly allocated copy published
metadata, and each preparation acquired a different destination ref lock.
Fix: Reserve the canonical nearest-source and target-parent pair before
searching for copies. Transfer the reservation to the returned run's existing
release lifetime, and release it on failed preparation. Preserve independent
parents, sources, current-reference locks and persisted formats.
Verification: Real TaskTool publication interleavings and Store regressions;
exact old-owner overlay fails 9 named events with 5 controls. Root 240-package
coverage, delegation/sessionstore race, vet, pinned lint, make check,
actionlint and Windows/Linux compile-only checks pass. Windows named-step
inventory validation rejects missing package completion and skipped cases.
Refs #14025
Invalidate persisted Delivery proof when file mode changes without a byte edit.
Use the existing content-version reader and verify surrounding identity, mode,
size and mtime observations. Refuse links, directories and special files as
regular-file proof inputs.
Version fingerprint values so legacy byte-only proof conservatively requires
fresh evidence in both reader directions, while goals and transcripts remain
readable. Cover real Boot persistence/rebuild, compatibility and Unix kinds,
and add a required native Windows inventory with bilingual documentation.
Refs #14025
Refs #14133
Problem: A rebuild can render another data home's environment probe answer
instead of its own still-valid persisted prefix, invalidating compaction.
Root cause: Process-cache lookup preceded durable snapshot lookup, while
shared-result adoption left existing snapshots untouched and did not merge
transient failures against previous successful observations.
Fix: Prioritize fresh trusted snapshots and merge/persist shared refreshes
at the environment owner. Preserve the snapshot format and trust checks.
Keep this prefix change separate from the remote-project UI as requested
in the review of #14615.
Verification: All 219 root Go packages with tests passed on this independent
branch, including full boot; environment race, go vet, make lint, formatting
and diff checks passed. Public-probe and cross-process boot regressions fail
with the original production implementation and pass with this fix. The
split patches reconstruct the combined source with an identical Git tree.
Preserve every workflow step, condition, timeout and assertion. Move the
status-owned pair away from concurrently added budget inventories so both
sets compose, and collect the named evidence before broad smoke can fail.
Refs #14656
Carry the accepted UsageQuoter event owner alongside Goal usage scope capture.
Retain both optional capabilities in wrappers and their shared inventory while
keeping scope-specific fixture additions independent of the quote additions.
Verify that nested bound usage reaches the occurrence-time quote owner once,
keeps the earlier Goal attribution, and preserves the emitted JSON. Document
the two owners and retain existing Windows required-test coverage.
Refs #14661
Refs #14671
Problem: local task cost gates could count a base rate while the accounting
event charged the vendor's occurrence-time peak rate, allowing extra tools.
Root cause: Agent budgeting recomputed spend with the legacy float adapter
instead of consuming the quote owned by the existing pricing sink.
Fix: forward an optional usage-quoter capability, capture one authoritative
quote for both delivery and budgeting, and accumulate fixed-point original
currency buckets. Preserve known subtotal and incomplete-usage semantics.
Keep display currency, provider requests and cancellation separate.
Verification: actual Agent and Boot peak/off-peak/relay regressions, preceding
owner negatives with controls, full root package inventory, relevant owner
and Boot race suites, static gates, cross-compilation and a pinned Windows
inventory with verifier mutants. Hosted native and independent review remain
separate. Shared root money and reservations are not implemented here.
Refs #14025.
Every model request carries the whole conversation, so upload grows with
context size times request count and nothing in the app showed it. Each send
attempt now reports its body size, a per-process meter keeps it per provider
and day beside the usage records, and Settings > Usage shows today, the last 30
days, the average request and the providers.
Own one disabled-by-default shortcut in the local Electron profile, with immediate authorized IPC and visible registration failures. Preserve old registration and saved state when rebind or persistence fails; restore at ready and unregister during quit.
Keep browser/network viewers unsupported and in-app bindings unchanged. Cover lifecycle, authorization, failure rollback, bounded native input and settings neighbours; add a real Linux/X11 OS-key smoke and transparent platform limits.
Fixes#8290. Refs #7623; stopped-app launching is not supplied.
A session whose message payload passes the replay bound could not be opened
and the window offered no way forward. The refusal now carries a code, and
the window offers to continue from the most recent part as a new session; the
original files are only read.
The two notices a fold leaves when user turns survive only through the summary
carried no code, so a Chinese window showed the English fallback. They now
carry a code and a typed payload like the other coded notices.
Problem: background children from an older chat or Goal could charge late
usage to a newly active Goal while still retaining their original root budget.
Root cause: the usage tee selected its current recorder at event delivery,
so recorder epoch checks were applied to the wrong owner.
Fix: capture an opaque process-local accounting scope at the shared tool-call
boundary. Forward the optional capability through sink wrappers, preserve it
for nested delegation, and select the captured Goal recorder at consumption.
Keep session tally, quoting, title exclusion, provider requests and wire data
unchanged. Document between-turn accounting and persistence limitations.
Verification: real Boot delayed-background and nested fixtures, same-Goal
controls, Goal lifecycle and JSON tests, exact preceding-owner negatives,
root package inventory, owner/Boot race tests, static gates, cross-compilation
and a pinned native Windows inventory with fail-closed verifier mutants.
Hosted native execution and independent review remain separate gates.
Refs #14025.
Every inbox handler answered a store refusal with -32600 and prose, so a client
could not tell "gone" from "wrong state" from "may already have run" — and one
that retried on any error re-sent work the kernel refuses precisely because the
turn may already have run. The HTTP frontend already names 14 conditions; ACP
now answers the same names, with -32020 for "the queue refused this call" and
-32021 for "the work may already have run", and leaves -32600 to a malformed
frame alone.
docs/ACP.md states the table and each refusal's class (terminal,
retry-after-repair, do not retry). repolint's family-vs-writer check now reads
both writers, so a sentinel added to the store cannot reach ACP unnamed.
A row already records why its money may be incomplete (`cost_estimated`,
`incomplete_reason`), but RangeStats kept only a bool, so a total could say
"this folded in an estimate" and never "of these calls, N were never priced,
because …". Both read paths now count those calls and name the distinct
reasons; the projection carries the reason as schema 8, which reindexes from
the daily files; an old or reason-less row still counts, it just adds no
reason to the vocabulary.
Problem: Saved child records with missing or unknown status could reach
continuation and transcript-copy preparation.
Root cause: The shared metadata validator rejected three known bad states
but implicitly admitted every other string, including future enum values.
Fix: Permit only the existing completed/cancelled states before unchanged
identity checks. Keep metadata decoding and writers unchanged so refusal
preserves unknown fields and transcript bytes for diagnosis.
Verification: Real on-disk store/reopen matrix covers nine state shapes and
five preparation routes, artifact preservation and reference-lock release.
Exact preceding owner fails 25 named events with 31 passing controls.
Qualification results and compatibility limits are recorded in the PR body.
Problem: Explicit slash, composer and CLI profile children could exceed a
configured root token/wall budget because they bypassed the normal Agent turn.
Root cause: Those Controller entries invoked skill runners without preparing
the executor-owned scope; Goal skills installed observation without the spend
limit. Synchronous profile calls also lacked turn admission serialization.
Fix: Bind the existing Goal/root scope after blocking hooks for interactive
skills. Reuse synchronous Controller admission and root preparation for CLI
profile run/preview, retaining child pause and cancellation semantics.
Verification: Actual Boot HTTP/file-read and Controller lifecycle regressions;
exact old-owner overlay fails 13 named events with four passing controls.
Complete 239-package coverage includes the retained original Boot 15m alarm
and two disjoint remainder groups; four owner race suites and focused Boot
race pass. Root vet, full lint, check and actionlint pass. Windows/Linux
compile-only checks and the literal native gate contract pass locally.
Problem: The setting catalogue described health consent as applying on session open.
Root cause: The declaration did not match the controller's per-request global read.
Fix: Mark the guarded setting Immediate; disable and authorization changes are
checked before cached evidence is returned.
Verification: Settings package tests and the registered HTTP opt-in/disable
regression pass. No payload or request logic changed.
Problem: automatic picker probes need explicit user consent and must not
borrow credentials for project-defined destinations.
Root cause: the combined implementation used the merged configuration and
had no backend-enforced default-off setting.
Fix: use a strict user-global configuration snapshot, require the opt-in and
provider access before every cache read, refuse all redirects, and expose a
guarded settings API. Cache only fixed-size digests and status/timestamps;
never retain upstream error bodies or log credentials.
Verification: authorization/default-off/disable and malformed-config HTTP
regressions, previous-reader save overlay, desktop setting round trip,
provider-health race tests, and frontend route contracts passed. Full lint
and inventory-partitioned boot validation are tracked separately in the PR.
Refs #14617
Problem: Directory labels were inconsistent across the header and composer,
and the default green badge looked like a success status.
Root cause: Separate presentation strings described the same workspace
state, including an unnecessary badge for the ordinary directory case.
Fix: Show neutral labels only for isolated worktrees, retain explicit
current-folder/worktree choices with checkmarks, and share explanatory
tooltips while preserving full paths and branches. Update translations,
usage documentation, and the release note.
Verification: Frontend interaction, translation and style checks;
TypeScript; make check; gofmt; go vet; make lint; browser comparisons in
light/dark themes at wide and phone widths. Required Go package and
production build results are recorded in the pull request.
The boot package ran 726s on that job against 264s here, and this change does
not reach it: projectiondb.OpenReadOnly/Inspect have one production caller and
it is the doctor command. No code change.
Problem: `reasonix doctor catalogs` printed the projection's integrity_check
string as the catalog's state, so a history catalog whose history_sources row
recorded health='corrupt' printed `ok`, and a future-schema database -- which
the runtime opens as StateDegraded -- printed `ok` too. The command a user is
told to run to check their data agreed with neither the catalog nor
`catalogs reindex --json`, which reports the same state as "failed": 1.
Root cause: the state column came from Inspection.Integrity and nothing else.
The command never asked the catalog what it recorded, and never compared the
recorded schema against the schema this build migrates to.
Fix: read the facts reindex already reads. refreshStatus and refresh now share
one readStatusCounts with the new read-only Inspect readers, so the
diagnostic's failed is the number reindex reports, and the reason is the one
the catalog recorded. A newer schema reports degraded against
projectiondb.SupportedVersion, which ApplyMigrations also uses. --json gains
state, failed, pending, corruptLines/lagBytes and failures only when there is
something to say, so a healthy catalog's output is unchanged. Text stays one
line per catalog. Exit stays 0: like `catalogs reindex --json`, this command
reports rather than repairs.
Verification: red-first. A reindexed undecodable transcript printed
`history ok`; it now prints `history failed ... failed=1 source=<path>:
<recorded reason>`. A schema-2 database printed `ok`; it now prints
`degraded ... supported=1`. Controls: healthy line and exit unchanged, missing
line unchanged, healthy JSON keys unchanged. Mutation: dropping the failure
read turns the corrupt-source test red, dropping the degraded read turns the
future-schema test red. projectiondb.OpenReadOnly reads a live WAL, which
immutable=1 skipped. No projection schema, history index or reindex path
changed. Refs docs/USAGE_CATALOG.md:25, docs/HISTORY_SEARCH_CATALOG.md:42-43.
Problem: A dual-model Goal resumed after a spend-budget pause can stop its
new planner research after one tool round despite the user renewing the slice.
Root cause: ResumeGoal resets only the executor's local budget. The tool
planner keeps its own Agent accumulator and stricter local gate, so renewing
the shared executor root does not clear that old planner spend.
Fix: On a successful budget-spend resume, also reset the existing companion
planner Agent through the runner's established accessor. Preserve manual
pauses, refused resumes, cumulative Goal statistics and delivery evidence.
No new API, default, cancellation authority, prompt or persisted format.
Verification: Actual Controller/Coordinator loop reproduces two resumed
planner requests instead of four on the preceding owner. Actual Boot plus
HTTP/SSE reads three fixture files per renewed slice; the exact old owner
overlay compiles and fails both regressions with four passing controls.
Full 239-package coverage reconciles 24061 unique test/subtest passes and
215 existing skips. The original Boot command hit its 15m package alarm;
all 846 top names close via 842 original terminals and disjoint 2/2-name
remainder groups, without raising timeouts. Complete control/coordinator
race1758 passes/4 existing skips, actual Boot race1 pass, static gates and
Windows/Linux compile-only checks pass. New-head hosted/native gates remain.
Problem: The Windows fallback control missed its recorder marker and could
not remove the still-running taskkill test executable.
Root cause: exec.Command("taskkill", ...) preserves the bare command in
argv[0]. The recorder compared that argument with taskkill.exe, missed its
special entry point, and entered the complete package suite instead.
Fix: Identify the recorder with os.Executable, require LookPath to select
the fixture's same file, and fail closed on an unexpected recorder entry.
Add a bare-command alias probe whose fallback selector runs only the inert
helper. Run/Wait completes before temporary-directory cleanup. Keep the
production ownership repair unchanged and extend the native inventory to
seven pass events versus four expected failures and three passing controls.
Verification: Windows cross-compile, Windows vet and pinned Windows proc
lint pass; full root vet/lint and make check pass at unchanged baseline.
All 6268 selected Darwin inputs match the previous qualified commit; this
append changes only the Windows fixture, verifier inventory and docs.
The complete original Windows failure log is preserved. Fresh native
Windows execution on this new commit remains required, not inferred.
Problem: Repeated cancellation or shell reaping can discard an established
Windows Job Object owner and perform a new PID-based stop attempt.
Root cause: TrackedCommand removes its one-shot TrackedJob on the first kill
and also starts a redundant PID tree observer after successful job assignment.
Fix: Keep the published owner across repeated and late cancellation, and let
an established Job Object own its tree. Preserve existing non-job fallbacks.
Add native Windows owned-child probes with a harmless taskkill recorder and
an exact preceding-owner counterfactual inventory gate.
Verification: Root inventory reconciles 239 packages, 24060 test/subtest
passes and 215 existing skips. Original Boot command hit its 15m package
alarm; all 845 top-level tests close through the recorded disjoint remainder
groups without a timeout increase. Complete proc and shell race suites pass.
Root vet, full lint, make check and actionlint pass. Windows new/old owners
compile and Windows vet passes; actual native regressions remain a hosted gate.
No full PID-identity, descendant-quiescence or physical-scope claim.
Problem: semantic routing runs before Agent/Coordinator root preparation, so its reported usage can permit an extra executor tool round.
Root cause: Controller binds no root before routing, and SemanticRouter does not report finalized usage to the accepted context observer.
Fix: share executor root preparation after admission/hooks and before routing in headless and interactive turns. Observe semantic terminal usage once, independent of audit/sink and answer success. Preserve cache hits, new-task/scope/reset lifetimes, normal accounting and cancellation authorities.
Verification: disjoint required/rest commands pass all 239 root packages; four complete owner race suites pass 3939 events with 14 existing skips; actual Boot race passes 12 events. Exact preceding owners compile and fail 15 regression events with six passing controls. Format, vet, full lint and make check pass at byte-identical baseline. Six Windows/Linux binaries compile; local literal Windows gate verifies 21 required events/two package terminals and rejects skip/missing-terminal controls, not native execution.
Problem: ambient Git index/object variables can redirect host worktree preparation and object access.
Root cause: pinned Git/common/worktree directories do not override GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY or GIT_ALTERNATE_OBJECT_DIRECTORIES. The shared constructors retained them.
Fix: remove those three ambient overrides in Env and Detached. Keep deliberate host private-index/object overrides and repository-declared alternates. Cover actual delivery/candidate preparation and a Boot isolated-write/apply flow.
Verification: all 239 root packages pass across disjoint required/rest commands; complete gitcmd/worktree/isolation race 306 passes; Boot race four passes. Exact preceding owner compiles and fails 18 regression events while seven controls pass. Full lint, vet and make check pass with byte-identical baseline. Windows/Linux builds compile; local literal Windows inventory verifies 25 required events and three package terminals, not native execution.
Problem: configured tasks used independent planner and executor root
counters, allowing an extra executor tool round after planning spend.
Root cause: Agent root binding began only inside each role's Run. A
plan-only turn never opened executor Run and could lose its accounting.
Fix: prepare the executor-owned root before routing, bind companion Agent
runs to it, and account legacy raw-planner usage at its logical terminal.
Track preparation by session object and host task scope. Adopt only the
preparing executor's binding after its own evidence reset; preserve clock,
immutable limit snapshots and captured old-job counters across continuation.
Keep local per-Agent gates/telemetry and all approval/cancellation boundaries.
Verification: required2 plus disjoint rest237 cover239 packages exactly
once,23980 test/subtest passes215 existing skips. Four full owner race
suites2786pass10skip; actualBoot shared and delegated token/wall16racepass.
Precise preceding Coordinator overlay compiles9fail4controls/2pkgfail.
LiteralWindows20named events/3pkg passes locally;6Win/Linux builds compile
only. Rootvet/full lint/makecheck/gofmt/diff pass,2663 baseline unchanged.
Refs #14025
Problem: Anthropic error, EOF and cancellation exits could discard usage
already reported by message_start or message_delta. Bounded consumers lost
the counts, while normal recovery reconstructed counts it already knew.
Root cause: the cumulative usage emitter was reachable only after
message_stop; consumers may stop immediately upon receiving ChunkError.
Fix: give each response one cumulative accumulator and delivery attempt.
Flush partial usage before explicit errors with existing Estimated identity,
retain cancellation-aware best-effort delivery on other exits, and compute
the original error classification before accounting changes sink state.
Keep exact committed usage, request construction and retry policy unchanged.
Verification: required builtin/boot plus disjoint rest cover all239 root
packages once,23966 test/subtest passes215 existing skips. Six full owner
race suites2615pass10skip; actualBoot4race events0skip. Exact preceding
decoder compiles and fails14events with4controls passing. Native-step
contract18events/2pkg passes locally;4Win/Linux binaries compile only.
Rootvet/full lint/makecheck/gofmt/diff pass; baseline2663 byte-identical.
Refs #14025
The prefix is read off the entry's own model list up to its last slash and left
out of how models are drawn, in every listing; ids and refs never change, an
alias wins, and models that would read the same keep their prefix. Fixes#14573.
Problem: an explicitly bounded Agent root omitted known native search usage,
allowing an extra normal tool round after a reported search answer.
Root cause: the search terminal finalized its independent request count and
reported usage without calling the accepted optional root observer.
Fix: observe the finalized record once before optional reporting. Preserve
query isolation, request counts, callback copies, unknown usage, native
completion, paid-backend consent and zero-limit behavior.
Verification: all 239 root packages pass through disjoint commands; four
complete owner race suites and real boot OpenAI/Anthropic HTTP regressions
pass. The compiled preceding owner fails seven events while nine controls
pass. Static gates, six cross-compiles and the literal 16-event Windows
contract pass locally; own native execution remains a hosted gate.
The sheet now covers the title bar too, its header standing in for the bar
(drag region, macOS light clearance, Windows buttons); the enlarge button's
arrows turn inward while enlarged. Refs #14418.
When a turn that ran a task from the session that dispatched this one ends,
its outcome and final answer go back as a message to that session. Only the
opener is answered, so a reply never starts a reply. Refs #13341.
A newly held message stamps the receiver through the existing unread fact and
announces itself with a quiet operator notice, which makes the window read the
session list again. Refs #13341.
Problem: a Windows run rejected three correctly refused capability calls
because the child conversation took 2.138s against a 2s wall-time check.
Root cause: the metric spans provider rounds and persistence, so it does
not establish whether target resolution refused before execution.
Fix: assert error/refusal guidance, Executed=false and no shell receipt for
all three results. Remove unused timestamps and preserve the existing 60s
completion guard. Production behavior and permission policy are unchanged.
Verification: the single root command passes all 239 packages, selected
boundary race checks pass 17 events, and a compiled executed-receipt test
mutant is rejected. Formatting, vet, lint and repository checks pass;
Windows/Linux boot binaries compile, without a native-execution claim.
Problem: an explicitly bounded root omitted billed command classifications
and external-content screening, allowing extra normal tool rounds.
Root cause: the shared triage terminal emitted usage without charging the
existing context-bound root counter.
Fix: observe terminal usage through the accepted private root helper before
the optional display guard. Preserve verdicts, screening notices, event
conditions, missing usage, normal telemetry and cancellation authority.
Verification: all 239 root packages pass in disjoint commands; full Agent
race and real boot HTTP/SSE matrices pass. The compiled preceding owner
fails ten events while five controls pass. Static gates, four platform
cross-compiles and the literal 15-event Windows contract pass locally.
Problem: an explicitly bounded Agent root omitted reviewer/advisor usage
already emitted by the shared bounded-call terminal, allowing extra rounds.
Root cause: normal-round accounting does not own auxiliary request usage.
Fix: connect the bounded terminal to one copied, replaceable context
observer bound to the original root counter. Preserve job cancellation,
normal telemetry, missing usage and zero-limit behavior.
Verification: all 239 root packages pass in disjoint commands; nine complete
owner/caller race suites and real boot HTTP/SSE regressions pass. Exact old
owners reproduce the gap; selected accepted auxiliary composition passes.
Formatting, vet, lint, repository checks and eight cross-compiles pass.
The 20-event Windows contract is locally verified, not native execution.
A local pane rides the hub's multiplexed stream, which attaches it only
once that stream is open. Until then the subscription has no position,
and the attach's watermark became its baseline as if nothing before it
mattered. A pane that read its transcript mid-turn and was attached after
the turn ended never received the reply or turn_done, and went on showing
the person's message alone.
The subscription now reads where the stream stands when it subscribes,
with the attach read of /events/replay. An attach that finds the stream
has moved on since reports what went by as a gap, and the pane rebuilds
from /history as it does for any other hole.
open_projects raises a fresh card naming every folder; approving adds the
folders to the project list and trusts them, vetted per folder like an extra
workspace root. Off unless the user's dispatch switch is on. Refs #13341.
Fold summary and failure-diagnosis terminal usage into the existing
context-bound root scope, including billed failures. Preserve local
normal-round telemetry and manual maintenance outside a root context.
Presets declare whether they relay other makers' models; the model menu
heading and the settings service list draw the service mark, while a relay's
model rows fall back to the letter chip. Refs #14538.
Pass the existing TaskBudget conversion to planner Agent Options. Cover
real HTTP planning reads, context overrides, disabled and unknown-cost
cases, executor fallback and request-prefix stability.
both ignored everything after the subcommand (or the name), so a typo like --jsno printed the human output and exited 0. now they exit 2 like mcp browse does.
The kernel answers POST /agent-presets/default with 204 and no body, but the
port read the answer as JSON, so a change that had landed reported
'Unexpected end of JSON input'. The call is status-only and uses post().
The plan bar sat inside the composer box and the open list had no obvious way
back out. It is now a card floating just above the composer; the open list has
a header (running and pending counts) and a collapse button at its top right,
and Esc or a press outside closes it through the shared dismiss path. The jump
buttons move up so the card does not cover them, and the conversation gets a
little bottom room while a plan exists.
Problem: restart-style MCP tests keep prior controllers alive while changing
server definitions or constructing a later session. Their background handshakes
and schema writes are therefore outside the test's advertised phase boundary.
The macOS job on #14531 observed a missing followed-tools notice, although its
precise interleaving was not captured and local old/new stress both pass.
Fix: close the owning controller before each transition in follow/unfollow and
accepted-tool restart fixtures. Existing idempotent Close drains the fixture's
MCP host and queued schema writes. Retain every notice, held-tool, provider
surface, actual-call and refusal assertion without retry, sleep or weaker gate.
Verification: six MCP effect test/subtest events and four existing deterministic
Close barrier tests pass under race. Forty fresh compiled processes pass each
before and after; they do not reproduce the hosted nil notice. Full lint, vet,
make check and four Windows/Linux amd64 builds pass. Full builtin/boot plus
disjoint root inventory continue separately; no production source changes.
Refs #14025
Refs #14133
Refs #14531
Problem: broad validation found three adjacent fixtures requiring selected
cost aliases or complete quotes even when their usage was reconstructed.
The shared quote repair correctly takes the existing incomplete-display path.
Fix: assert preserved original amounts, incomplete/partial coverage and absent
selected aliases for estimated wire, ACP status and persisted stats rows. Keep
reported-usage alias and legacy-row controls. Expand the existing Windows
inventory to all eighteen named events across seven packages.
Verification: affected consumer tests pass, with complete eventwire/ACP/stats
race suites at 351 passing events and no skips. The compiled preceding pricing
owner now fails fifteen regression events with three controls passing. Final
local Windows-step contract passes eighteen events; required-skip and missing
package-terminal controls fail. Full lint, vet and make check remain clean;
final original root validation continues against unchanged frozen Go sources.
Refs #14025
Refs #14133
Interrupted provider streams can reconstruct positive usage without terminal
reported counts. The shared quote owner forwarded that provenance but ignored
it when certifying complete cost facts, allowing recovered usage to make a
fully covered ledger.
Use the existing incomplete-usage result while retaining fixed-point amounts
and occurrence-time valuations. Reported usage, including rate-card estimates,
keeps its completeness. Existing quote fields, reasons and ledger versions stay
unchanged; no provider-visible request, permission or budget default changes.
Cover shared event/config adapters, persisted ledger coverage and real boot SSE
recovery with an actual file read. Add a Windows fourteen-event inventory in the
existing job. This is a monetary-fact prerequisite of the full concurrency plan,
not shared root-money admission or hard reservation.
Verification: fourteen focused events and the real boot race pass; compiled
preceding pricing fails twelve regression events with two controls passing.
The prior reader accepts the incomplete quote fixture. Root vet, full lint and
make check pass with the baseline unchanged. Eight Windows/Linux amd64 owner
builds compile; full root inventory and complete owner race continue separately.
Refs #14025
Refs #14133
Problem: a host wall budget could land the root Agent while delegated Agents
and Manager-owned background jobs kept independent start times or no wall bound.
Root cause: the opaque inherited root scope carried only token accounting; the
existing task time gate read each Agent's own clock after its tool rounds.
Fix: attach an immutable root task start/limit pair to that existing scope.
Preserve the start across continuations and snapshot new root overrides without
rewriting old job bounds. New tasks/reset use a new clock. The existing task and
fleet projection helper also carries this sidecar while keeping Manager-owned
cancellation/deadlines. Use existing time soft landing and finalization; keep
defaults unlimited, with no hard deadline, new timer, schema or permission.
Verification: frozen6730Go/fourmodules; required2+rest237 cover239packages,
23754test/subtestpasses/215existingskips. Complete Agent/delegation/jobs race
2652passes/10existing skips; actual boot background task/capability/fleet4race
passes; accepted14523sixGo selectedoverlay54racepasses; prefixguards4passes.
Exact preceding owner compiles and fails9events/2controls across2packages.
Windows17-event step passes locally and rejects skip/missingterminal controls;
six Windows/Linuxamd64 test binaries compile, not native execution.
Rootvet/full lint/makecheck/format/diff pass; baseline byte-identical2663.
Refs #14025
Refs #14133
Problem: a priced Agent could report complete task costs after a round omitted
usage, and estimated usage could certify a cost threshold.
Root cause: the shared HTTP attempt helper synthesized request-only usage
without its existing Estimated identity; the budget observer also treated
missing or estimated token accounting as a fully priced reading.
Fix: preserve exact HTTP counts while marking synthesized usage incomplete.
Keep only reported priced costs in the Agent subtotal, retain uncertainty for
the task lifetime, and land when the known subtotal alone reaches the limit.
Keep defaults, schemas, cancellation and finalization unchanged. Add real
OpenAI HTTP boot coverage and an explicit Windows test inventory.
Verification: frozen 6729 Go files/four module files; required2 +rest237 close
239 packages, 23754 test/subtest passes and215 existing skips. Complete five
owner race2407passes10existing skips; real HTTP boot race4passes; cache guards4.
Exact preceding owners compile with13failing events/5controls; producer-only
rollback4fails/1control; deliberate unknown-erases-known mutation1fail.
Root vet, full lint, make check and formatting pass with baseline unchanged;
six Windows/Linux amd64 owner/boot binaries compile (not native execution).
Refs #14025
Refs #14133
Problem: configured task token limits counted only each Agent's own provider
usage. Delegated Agents could continue spending outside the parent's reading.
Root cause: local runBudget instances never shared a root token total, while
background task/fleet closures start from Manager-owned contexts that omit
caller values.
Fix: bind a private atomic root counter to the existing task ledger and pass
its immutable per-run threshold through derived contexts. Project only this
scope into both background producers, preserving job cancellation authority.
Keep local telemetry, cost/wall gates, defaults, prefixes and soft landing.
Verification: compiling preceding owners9fail/2controls; preceding job-context
only4fail/2foregroundcontrols. Complete3owner race2639pass/10existing skip;
realboot5scenes+parent6racepass, explicit cacheguards4pass. Final239 package
inventory23710pass/215existing skip, required builtin/boot, rootvet, full lint,
makecheck, formatting and6Windows/Linux builds pass. New native inventory
requires20namedpasses/no skips; local jq and required-skip negative verified.
No hard reservation, root money aggregation, process kill, remote/durable
accounting or Coordinator cross-root scope is claimed. Refs #14025, #14133.
Cause: internal/runtime/isolation and the isolated task tool refuse with
isolation.unknown_id, unavailable, not_git, apply_conflict and
unsupported_arg, but the kernel catalogue words none of them. A tool card
renders say(code), so these refusals showed no reason in either language.
Fix: word the five codes in kernel.ts and en_kernel.ts, in the terms of the
tool path that emits them.
Refs #14025
Problem: whole-file rewrites can discard changes hidden by a later overlay
version, a paged read, or an oversized read that clears the previous guard.
Root cause: read_file renders one read and records another. Missing and
incomplete observations also collapse into the permissive never-seen path.
Fix: observe the renderer's decoded stream with bounded capture, refresh only
complete returned reads, retain older guards, and track first partial reads
explicitly. Keep never-seen compatibility and disabled protection behavior.
Verification: compiling old-owner controls fail five behavior regressions;
fixed focused race tests and real boot.Build checks pass. Full builtin,
root-package tests, go vet, make check and full lint are recorded in the PR.
Written bilingual security bounds and release notes accompany this slice.
A first partial read refuses replacement with the file's line count and the
read_file limit that certifies it. Without that, a default-limited read of a
file longer than 2000 lines can never be certified by rereading as told.
Refs #14025
Refs #14133
Problem: the no-index history search test could inherit a process catalog from
a temporary runtime fixture, so its asserted precondition was not established.
Root cause: shared catalogs deliberately outlive controllers. Several actual
boot fixtures and the shared unread harness omitted their existing fixture
cleanup, allowing the process pointer to outlive the temporary home.
Fix: use the existing shared cleanup in those owners, establish no-index state
explicitly, and preserve coverage for real active-catalog failures. Production
code, workflow gates, timeouts, defaults and baseline remain unchanged.
Verification: preceding fixtures compile and fail the lifecycle regression;
the provider-editor sibling control passes. Complete serve1798pass/5existing
skip and complete disjoint race inventory1798pass/5skip; root239 package
inventory23696pass/215existing skip, required builtin/boot, rootvet, full lint,
makecheck, formatting and Windows/Linux amd64 compile pass. Original Linux500
and the local fullrace5m alarm remain separate evidence, not claimed fixed by
an external rerun. Refs #14025, #14133; followup to #14505.
A mark that kept focus after a press set the rail's focus again whenever the
window handed focus back, so its neighbours stayed magnified and its peek stayed
open with the pointer elsewhere. Focus now moves the rail only when it is
focus-visible.
Foreground panic unwinding could publish a completed worker graph before its error was known, while legacy nil panics were swallowed by both foreground and background exits.
Classify return versus panic at one private delegation boundary, persist the failed transcript while owned, and release before terminal callbacks without letting old cleanup retire a reentrant replacement. Keep foreground panic propagation and accepted Manager finalizers. Cover real boot direct/capability dispatch, compiled nil-panic modes, error identity and ordinary outcome controls.
Settings and feedback refused with 'add a folder first' when no pane was open and
no folder was known, and the empty main area offered only Add folder. The kernel
already opens a session with no root in the app's own workspace, so the sheets
and the empty state now ask for exactly that.
Problem: legacy nil-panic recovery can publish a successful job after its
invocation or cleanup panics, and error-valued panics lose their cause identity.
Root cause: two boundaries treat a non-nil recover value as the only panic
signal and format panic errors without wrapping the original error.
Fix: share a private normal-return guard across invocation and finalization,
retain typed panic causes, preserve finalizer error classification and sibling
cleanup, and keep the existing invocation stack message without adding stacks
to finalizers. Move invocation recovery into its cohesive owning file.
Verification: compiling preceding owners fail six test/subtest events with two
controls passing. Actual compiled Manager child tests cover explicit panicnil
modes zero/one, invocation, cleanup and no-launch. Frozen required2/rest237
partitions close all239 packages with23656 passes/215existing skips; complete
jobs/delegation race549 passes and accepted14464 three-owner composition671
passes have zero skips. Independent required command has2349passes/66existing skips.
Vet, complete lint, make check and formatting pass at unchanged2664baseline.
Windows/Linuxamd64 owner builds are compilation only; exact-head hosted/native
and independent review remain separate gates. No Goexit, other-goroutine panic,
process-scope completion or crash recovery is claimed.
Problem: accepted job finalization in #14469 conflicts with the pending
cancellable writer registration paths in task and fleet.
Root cause: both sides own background cleanup. Closure-only cleanup misses
proven no-launch, while ID-only cleanup cannot protect replacement writers.
Fix: retain Manager-owned terminal callbacks and pass each returned writer
registration token through the shared jobCleanup option. Remove the obsolete
ID-only helper. Preserve the foreground token lifetime, accepted transcript
and progress finalizers, and every other upstream change in this ordinary merge.
Verification: the original full 239-package root command passes with 23668
test/subtest passes and 215 existing skips. Complete checkpoint/delegation/jobs
race passes 663 events, zero skips (one unchanged package cached); real boot
handoff and no-launch race passes ten events. Independent required builtin/boot
passes 2354 events with 66 existing skips. Compiling preceding callers fail
four no-launch scenarios plus parent; the Manager/token generation regression
passes. Vet, full lint, make check, formatting and diff gates pass, baseline
2664 unchanged. Six Windows/Linux amd64 owner compilations are compilation
only. Exact new-head hosted/native validation and independent review remain.
The reader menu opens from a promise callback, so its commit runs at
default priority and the effect that focuses the first item is a
separate Scheduler task (setImmediate). findByRole resolves from the
MutationObserver that sees the menu, then RTL drains with setTimeout(0);
when a millisecond passes before the next loop turn, the timer wins and
the assertion reads the reader, which still has focus.
Wait for the focus the way the other focus assertions in the suite do.
Problem: candidate groups need a common baseline while preserving the result
Store's source, managed-root and identity ownership.
Root cause: ordinary Begin captures afresh, while arbitrary snapshot/path
adoption would bypass the owning Store and its existing settlement contract.
Fix: add an opaque process-local Preparation, recheck source/workspace/Git
directory identities, and create independent results from the captured
snapshot. Extract the existing Begin bodies into private shared helpers.
No best-of, runtime, default or persisted-format activation is included.
Verification: complete 239-package root inventory, full isolation race,
independent builtin/boot command, vet, full lint and make check pass with the
2664 baseline unchanged. Compiling unsafe controls distinguish baseline
refresh and replaced-directory admission; the preceding Store implementation
passes the new preparation tests. Linked-source overlay passes; Windows and
Linux amd64 compile only. Native/hosted validation and independent review
remain separate gates.
Problem: local settings cannot load the file-backup catalog because both
machine settings URL forms return an untyped 404.
Root cause: file-backup handlers are registered only with account backup
routes, while the machine settings router registers a separate route set.
Fix: register the existing file-backup handlers with machine settings.
Normal runtime routes still register that same set once. Preserve host-only
checks, paired-device rejection, local-owner selection and mutation gates;
do not change payloads, encryption or persisted formats.
Verification: new regressions fail before the fix with plain 404 responses
and pass afterward for both machine prefixes. Focused machine settings,
file backup, account backup and mutation-gate tests pass. gofmt, go vet,
make lint, make check and the required builtin/boot tests pass.
Problem: switching settings categories retains an unrelated scroll offset, while a searched setting that loads later never receives navigation feedback.
Root cause: categories share one scrolling element and landing resolves the anchor once before asynchronous content arrives.
Fix: key the panel by category, clear pending landing only when a guarded category change succeeds, and observe target arrival until its feedback lifetime begins. Keep existing categories, layouts, disclosures and unsaved-change guards.
Verification: five regressions fail on the unmodified baseline; all 28 settings test files and 215 tests pass with the fix. TypeScript/Vite and repository checks pass. Browser verification confirms a tools panel at scrollTop 5747 switches to network at 0 and search opens the existing advanced contrast disclosure.
Problem: Network settings could be saved without edits, and pending reads,
saves or diagnostics could overwrite the state of a newly selected connection.
Connection tests also appeared to exercise unsaved form values.
Root cause: The form used one settings object for both saved state and edits,
with async completions that were not scoped to the owning connection.
Fix: Keep saved settings and the editable draft separately, disable no-op
saves, freeze submitted drafts, and identify diagnostics as testing saved
settings. Remount the form on connection changes and ignore completions from
unmounted owners. Preserve the existing expanded connection details layout.
Verification: The seven extracted regression cases failed on the baseline.
Network and i18n tests pass (10 files, 181 tests), including edit reversion
and loading/error coverage; tsc -b, make check and git diff --check pass.
Problem: the ordinary test synchronization append conflicts with upstream's
independently accepted fix for the same passive draft-persistence boundary.
Root cause: both sides replace the immediate heldDraft assertion with waitFor;
upstream additionally explains why the effect may follow the thumbnail commit.
Fix: resolve the sole conflict to the exact accepted upstream test including
its comment. Preserve all incoming dependency/UI changes and every original
runner-panic implementation Go file. This merge is required by a verified
content conflict, not unrelated base drift; no rebase or force-push.
Verification: final inventory of 239 root packages is reconciled from disjoint
required2 and rest237 commands, both exit0, with frozen Go/module inputs.
Isolation race covers all 51 top-level tests through the original completed
39 trees plus the remaining12 under the same5m budget; original full owner
command hit its cumulative alarm and is retained as exit1, not called pass.
Five affected frontend files have99 tests pass; production typecheck/build,
formatting, root vet, full lint and make check pass without baseline widening.
Exact merge-head hosted/native checks and independent review remain pending.
NewStreamDialer is how SSH reaches a remote host under the user's proxy
(remote/attach), and none of dialer.go was exercised: netclient sat at
47% against a 47.2% floor on a declared-sensitive path.
Four tests drive it against fake proxies on loopback:
- an http proxy gets CONNECT to the target with Proxy-Authorization, and
the credentials never also travel as Authorization;
- a proxy that answers 407 yields an error, not an open stream;
- an https proxy sees a TLS handshake first, so CONNECT and credentials
never cross in cleartext;
- a SOCKS5 proxy gets username/password auth and the target by name.
Each fails when the matching guard in dialer.go is removed. The
netclient floor rises from 47.2 to 65.8.
Problem: Studio job 121205496051 fails after the pasted image preview appears,
because the retained draft still contains no image.
Root cause: findByAltText observes the DOM commit, while holdDraft runs in a
passive effect. The test assumes those distinct boundaries complete together.
The unchanged merge passed a later external rerun, which does not repair the
scheduling assumption.
Fix: keep the exact single-image assertion for both heading and tab targets,
but await the draft condition with waitFor. Production UI and Go source stay
unchanged.
Verification: full Feedback interaction file 65 pass, restored focused cases
2 pass, typecheck pass. Two independent no-op-save controls each fail both
targets, proving persistence is still checked. Root vet, full lint, make check
and required builtin/boot pass at unchanged baseline 2664. The preceding
native/hosted evidence remains attached to af958f; the new head needs fresh CI.
Problem: ambiguous private registry records can be admitted or scavenged as
known dead-owner state.
Root cause: struct JSON decoding merges repeated members and folds case;
unknown-field rejection alone does not preserve the original declarations.
Fix: validate unique decoded names and exact schema spelling for records and
capacity objects before typed decoding and liveness checks. Keep writer
encoding, legal spacing/order, escaped unique names and existing resources.
Join a killed process before closing cooperative input in death fixtures.
Verification: complete 239-package root inventory and required builtin/boot
commands exit 0; mutation race 88 pass, pending wait/opaque composition 116
pass. The compiling preceding decoder fails 22 behavior test/subtest events,
with two controls passing. Formatting, vet, full lint and make check pass
without changing the 2664 baseline. Windows/Linux amd64 compile only; decode
microbenchmark overhead is measured and disclosed separately from product
performance. No runtime or default activation.
`.railhost` sets pointer-events: none, and only `.srail` opted back in. The
unloaded-history marker and the opened run inherited the none, so presses fell
through to the transcript. A style test now holds the rule.
Problem: an isolated task could write files, then lose its discoverable result
identity when a host runner or provider callback panicked.
Root cause: Store.Execute called Finish only after an ordinary runner return;
panic unwind bypassed both completed-result and inspection publication.
Fix: recover at the runner invocation boundary, retain an error-valued panic
as a cause, and publish the existing inspection identity under the result
operation owner. Do not infer physical completion, inspect for emptiness, or
delete work on a panic. Preserve ordinary outcomes and truthful live-only
recovery when bookkeeping cannot be admitted or persisted.
Verification: final 239-package inventory closes using 238 unchanged broad
packages and the final isolation race package; 111 owner race and 3 real boot
race cases pass without skips. The compiling preceding owner fails 11 cases
with 3 ordinary-error controls passing. Required builtin/boot, root vet, full
lint and make check pass at unchanged baseline 2664. Linux/Windows amd64 are
compile-only; hosted/native and independent review remain separate gates.
Refs #14025
Refs #14133
Problem: Windows PR smoke did not run jobs/delegation unit tests, and pure
delegation changes could skip native platform legs. Linux PR race coverage
also omitted the delegation owner tests.
Root cause: package routing predates the shared background resource lifecycle.
Compiling these owners as boot dependencies does not execute their tests.
Fix: run both complete packages on Windows with a retained JSON inventory,
require all15 portable lifecycle cases to pass, route pure delegation changes
to native legs and include delegation in the existing Linux race sweep.
Verification: platform-scope tests and pinned actionlint pass; the inventory
gate accepts actual local owner output and rejects missing-case, skipped-case
and missing-owner controls. Make check passes at unchanged baseline2664.
All6699 Go files match the qualified implementation commit; no product Go
source or deadline changed. Exact-head native/hosted qualification is pending.
Problem: background task/fleet can strand checkpoint writers, transcript locks
and progress resources when the Manager never invokes the supplied closure.
Root cause: resource ownership moves to closure-only defers before the Manager
checks invalid identifiers or its closed root context. Those branches bypass
the closure and its cleanup, while the caller suppresses its own ending.
Fix: register optional host finalizers at the Manager's managed-execution
boundary. Run all callbacks once before completion after actual closure return
or proven no-launch, preserve cleanup failures/panic and transfer the actual
task/fleet resources and uninvoked terminal projections to that owner.
Verification: final239-package inventory uses238 broad packages plus the final
jobs package after a deterministic liveness-test-only change:23596 test/subtest
passes/215 existing skips. Full owner race648 and real boot race5 pass without
skips; required builtin/boot2346 passes/66 skips. Original public regressions
and compiling predecessor controls fail. Selected token-adapter composition
passes40 race cases. Vet/lint/check pass at unchanged baseline2664.
The liveness pair uses a controlled clock; production thresholds are unchanged.
Managed-closure completion is not descendant scope or durable crash recovery.
Refs #14025
Problem: cancelled background task/fleet calls can remain behind checkpoint
admission while the shared writer registry also blocks queries and cleanup.
Root cause: registration holds the registry mutex across the barrier, durable
undo retirement, notification and Store persistence. Callers discard their
admission context and defer cleanup by reusable string ID alone.
Fix: reserve identified pending registrations, prepare outside the registry
mutex and serialize latest snapshot publication separately. Pass context at
all task/fleet sites and transfer token-bound cleanup to admitted jobs. Preserve
legacy APIs, completed undo retirement and active work across parent turns.
Verification: original root239 passes with23590 test/subtest passes/215 existing
conditional skips; required builtin/boot2346 passes/66 skips. Complete owning
race487 passes/zero skips; real boot handoff race5 passes/zero skips. Compiling
preceding-owner/caller controls fail the intended assertions. Vet/lint/check
pass without raising baseline2664. Linux/Windows compilation is not native QA.
The jobs-owner no-launch finalization gap remains explicitly separate.
Refs #14025
Problem: A foreground command can return bounded cancellation while cmd.Wait
is still blocked, so call return cannot establish resource-lifetime completion.
Root cause: The tracked wait owner exposes its logical result and cleanup
diagnostics but no separate observation of the still-running root Wait.
Fix: Register a stable root-wait channel before the waiter starts and close it
after Wait returns, before result publication. Expose it with the explicit
RootWaitCompleted name. Preserve existing execution, cancellation, retry and
error behavior. No production lease consumer or whole-scope claim is added.
Verification: Deterministic cancellation/retry/publication tests, portable Go
child execution and a real blocked-output process/lease regression pass. Root
Wait remains distinct from descendant or external-scope completion; nil means
no registered observation. Full local/static and platform evidence is recorded
in the PR without inheriting previous heads' native results.
Problem: Directory-scoped coordination cannot exclude writers that address a
source file through a hardlink outside that directory.
Root cause: Path containment and named inode identity do not represent an
unresolved write effect; reinterpreting old directory records would silently
change their resource promise and nested admission could reuse a weaker hold.
Fix: Publish explicit version-3 opaque effects in the existing registry,
coordinate them symmetrically against all path-bearing transactions, and
require effect coverage when reusing a parent lease. Preserve ordinary named
parallelism, model capacity, previous directory semantics and final OS-locked
grant authority. No runtime or default requests the new effect here.
Verification: Full 239-package root inventory and independent builtin/boot
commands exit 0; mutation race 82 passes/zero skips; preceding-reader binary
preserves live/dead new records; weakened-owner control fails eight cases;
selected wait-snapshot composition has 36 race passes/zero skips. Root vet,
full lint, changed-file checks and formatting pass at unchanged baseline 2664.
Windows/Linux amd64 compile only; local execution is macOS.
Problem: unfamiliar coordination records can be ignored during admission and
deleted after their OS owner exits.
Root cause: registry decoding discarded unknown fields, and version-1 resource
validation accepted every effect except capacity.
Fix: reject unfamiliar object fields and unsupported path effects before
liveness/scavenging, retain original bytes, and include record identity in the
typed protocol error. Preserve known version-1/2 records and omitted-effect
legacy semantics without changing writers or activating the runtime protocol.
Verification: complete 239-package root inventory, full mutation race,
compiling preceding-owner counterexamples, actual owner-process death,
known-record controls, selected waiting-observation composition, required
builtin/boot and static gates. Windows/Linux builds are compile-only local
evidence; exact-head hosted/native review remains separate.
Problem: A durable future settlement record must stop ordinary result
writers, including stale queued operations and destructive siblings.
Root cause: The per-ID mutex serializes operations but carries no durable
knowledge that a recovery owner has published a settlement fence.
Fix: Check host-rooted external fence metadata after operation ownership,
refuse any existing entry without interpreting its bytes, recheck context
and release only this attempt on refusal. Cover tools, Finish/Abort, empty
inspection, sweep, a real child process and actual boot/controller rebuild.
No journal writer, schema or complete recovery is introduced. Preceding
writers ignore this fence; future activation needs a cooperating protocol.
Verification: Full isolation race100 passes, zero skips; real boot race3
passes. Preceding owner compiles and fails15 test/subtests, including a
child process; predecessor boot owner actually writes source in both cases.
Final root inventory/required and static qualification recorded in PR.
Linux/Windows amd64 compilation is separate from native evidence.
Refs #14025
Refs #14133
User turns, retained failures and [[keep]] turns were budgeted as shares of
the window. On a 1M window under the 100K cost trigger those shares plus the
tail passed the trigger, so every fold was refused as still at or above it.
Each budget is now also held to a share of the trigger.
The folder and host labels on a pane tab were flex: none, so a long one
could not shrink: the title, the only part that could, went to zero width
and the close button was pushed past the tab's edge onto the next tab,
where a press activates that tab instead.
Both labels now shrink to an ellipsis and take at most 40% of the tab, and
the title keeps at least 4em. perf/tab-close-inside.mjs checks, in the
real stylesheet, that the close button stays inside its tab and is what a
press at its centre reaches, with a long title, a long folder, and both a
long folder and a long host.
Only a serve with --port-file or --open listened first; a plain serve printed the
banner, share link, token path and the non-loopback warning and bound afterwards, so
an address that can never work still looked like a running server. Every command now
binds first, the report uses the bound address, and the now-unreachable
bind-inside-RunGraceful branch is gone.
.prefs-col had a max-width and no inline margin, so once the settings window was
enlarged the column hugged the navigation and every extra pixel piled up on its
right. In the enlarged mode the column is now centred in the room beside the nav.
Problem: a candidate filename could act as a Git wildcard or magic pathspec,
writing an undeclared source file or dropping the actual literal result.
Newline-delimited hashing could also reinterpret a single filename.
Root cause: option separation was mistaken for literal path selection, and
ambient Git pathspec flags could override the host's intended selection.
Fix: centralize literal pathspecs for tree/index inspection and the shared
restore owner; C-quote hashing input by bytes; remove inherited selection
flags in the common host Git environment while preserving explicit flags.
Whole-workspace application reuses the shared writer. Keep the portable
stdout/stderr Git fixture repair aligned with the native CI follow-up.
Verification: complete 239-package root inventory, three-owner race, real boot
race, uncached required builtin/boot, compiling predecessor controls, six
Windows/Linux owner cross-compiles, formatting, root vet, lint and make check.
Native hosted checks and independent review remain separate gates.
Refs #14025
Refs #14133
withinExact used filepath.Rel, which ignores case on Windows, so a different
spelling of an absent file was reported as held there while heldWithin's comment
and TestHeldPathsCoverOnlyTheSameFileAcrossCaseSpellings require the filesystem to
prove two spellings are one file. Require the prefix bytes to agree as well.
The terminal's copy of /history's tool call had no argumentNormalization
field, so a restored transcript rebuilt the card from Arguments alone —
the proxy's effective text — and drew it as what the model wrote. The
original was not merely hidden; it never left the decoder.
Carry the host's normalization through HistoryToolCall into the
eventwire.Tool the restored transcript draws and inspects, following the
serve payload and the desktop copy that already do. The card still draws
only the effective arguments: showing the original is a UI decision for a
later change, and changing the card would also change a live call.
Register the pair as a declared subset of the serve historyToolCall in
wireparity, naming the six fields the terminal decides from. The exact
table is the desktop's and is read as a TypeScript interface, so a Go
reader belongs in the partial table, which compares both ways over the
declared names.
The helper used CombinedOutput, so on Windows, where core.autocrlf is true, git's
'LF will be replaced by CRLF' warning on stderr was returned with the blob id and
TestApplyPathsWithUntrustedModesPreservesTheIndex/candidate_type passed the pair to
update-index --cacheinfo. Standard output is what a caller reads; standard error
now only joins a failure message.
Problem: the newly executed native Windows owner inventory failed while
constructing its candidate-type fixture, before product application ran.
Root cause: gitIn returned CombinedOutput, so a successful hash-object CRLF
warning on stderr was concatenated with its object ID and fed to update-index.
Fix: keep stdout and stderr separate at the shared fixture owner, retaining
both in failure diagnostics. Pin autocrlf=true in the compatibility regression
so this contract is exercised on every host. No production or gate changes.
Verification: the preceding helper compiles and reproduces the same diagnostic
pollution locally; the complete worktree race, Windows compilation, required
builtin/boot, vet, lint and make check pass. Hosted native revalidation remains
separate from local and earlier full-root evidence.
A live holder keeps the OS lock for its whole lifetime, and its `.lease.json`
sidecar can be deleted by a person or a file scanner while the process still
owns the session. The probe answered free on a missing sidecar without looking
at the lock, so session list said idle, task list flipped a running item to
interrupted, and redact-sessions would rewrite a live transcript. It now asks
the lock whenever a lock file exists, and still creates nothing on the common
uncontended path.
A live holder keeps the OS lock for its whole lifetime, and its
`.lease.json` sidecar can be deleted by a person or a file scanner while the
process still owns the session. The probe answered free on a missing sidecar
without looking at the lock, so session list said idle, task list flipped a
running item to interrupted, and redact-sessions would rewrite a live
transcript. It now asks the lock whenever a lock file exists, and still
creates nothing on the common uncontended path.
Problem: green PR checks could omit complete isolation/worktree owner tests on
Windows and under Linux race; pure isolation changes could skip native legs.
Root cause: the smoke and race package allowlists lacked both owners, while
the platform classifier treated untagged isolation Go as neutral.
Fix: classify isolation paths as platform-relevant; execute complete Windows
owners with an uploaded JSON inventory, require both package terminals and
portable boundary tests, and include both packages in PR race. Replace the
redundant rollback subset. Preserve existing permissions, deadlines and cache
rules; do not alter product code.
Verification: compiling old-workflow/classifier regressions, exact inventory
positive/negative controls, final classifier/testenv suites, complete 238-package
root inventory, three-owner race, required builtin/boot, vet, lint and check.
Local execution is macOS; native hosted outcomes remain a separate gate.
Refs #14025
Refs #14133
The width a hand set is kept in --flow-w and outranks the stylesheet, so once the
reading column had been dragged, choosing the full-width preset changed --doc but
not the column and did nothing. Choosing a preset now drops the dragged width.
Problem: executable-only changes and equal-byte regular/link changes could be
discarded as already applied, and source metadata edits could be overwritten.
Root cause: baseline, candidate and live file versions retained only blob IDs,
collapsing distinct Git modes and file kinds into one identity.
Fix: compare Git mode and blob together, follow the checkout's trusted/index
mode rules, hash real links and emulated links with their actual Git semantics,
and reject unsupported transitions before any source write. Keep the user's
index and configuration unchanged.
Verification: complete 238-package root inventory, worktree/isolation race,
real boot race, required builtin/boot, compiling predecessor controls and
operation-lease composition. Formatting, root vet, lint and make check pass.
Cross-compilation is recorded separately from pending hosted/native review.
Refs #14025
Refs #14133
Problem: the rooted-lock rename regression failed on Windows before reaching
the lock operation.
Root cause: Go's direct os.OpenRoot uses a Windows handle without delete
sharing, while production opens the lock directory relative to its managed
Root using an Openat handle with delete sharing. The fixture conflated them.
Fix: open the test directory through its parent Root, matching the production
constructor. Retain the real rename/replacement and physical lock-location
assertions on every platform. Document the direct-root Windows limitation.
No production code, timeout or platform skip changes.
Verification: complete filelock race (25 passes), Windows amd64 compilation,
required builtin/boot, root vet, full lint and make check. The prior full root
qualification is retained separately; new hosted Windows evidence is pending.
Problem: concurrent Stores could inspect one retained result and then write
or remove it while another operation was still using its candidate.
Root cause: reinspection checked authority without retaining a common owner.
Paths, first Finish, Abort and expiry cleanup could bypass Apply/Discard too.
Fix: add directory-handle OS file locks and one persistent per-result mutex
namespace. Hold admission through reinspection, mutation and synchronous
cleanup; preserve lock identity, active owners and live-only Finish retries.
Forward path-query cancellation and keep legacy writer limitations explicit.
Verification: complete 238-package root inventory, owner and real boot race,
compiled predecessor regressions, actual prior reader/sweeper compatibility,
Windows/Linux owner cross-compilation, formatting, vet, lint and make check.
No new native-platform or independent-review result is claimed here.
Refs #14025
Refs #14133
actions.ts sat exactly at the 800-line ceiling, so any pull request that added a
single action id failed repolint. The 44 feedback.* ids are one concern and move
to actions_feedback.ts, the way the market, schedule and document groups already
live in their own files; the registry content is unchanged.
Problem: cancelled calls remain parked behind checkpoint exclusivity, delay
their provisional resource cleanup, and may later enter hooks or execution.
Root cause: condition waits do not consume the invocation context. Observer
cleanup also exposes a shared writer-count decrement that can run twice.
Fix: centralize context-aware barrier admission, recheck cancellation after
durable preparation, return idempotent permits and bind the Agent caller.
Preserve completed undo retirement and every other active owner's admission.
Verification: deterministic cancellation and owner-preservation schedules,
compiling predecessor behavior failures, real boot direct/deferred coverage,
durable undo reloads, full root inventory, owner/boot race and required suites;
gofmt, vet, full lint and make check with unchanged gate baseline.
The literal branches of applyOldStringEdit wrote the replacement verbatim, so a
multi-line new_string inserted bare LF into CRLF files while only the fuzzy paths
converted it. Each literal replacement now takes the ending of its own line, which
also keeps an LF region of a mixed file free of stray CR.
Problem: a Store could apply an old cached Git tree after another Store had already applied or discarded that result, resurrecting its bytes in the source workspace.
Root cause: cache hits skipped durable publication and candidate admission; Git objects survive removal of the worktree. Refreshing without comparing the previous observation would also silently widen an earlier path declaration.
Fix: always reuse durable recovery/inspection for lookups, keep finished caches out of Pending, and refuse changed baseline/candidate/target trees until fresh path admission. Preserve first-Finish live-only and unsupported-version checks. No persisted, permission, tool-definition or default changes. Cross-process settlement locking and journals remain separate.
Verification: final complete 238-package root inventory passes (217 pass/21 no tests;23310 test/subtest passes,215 existing skips,194 valid cached packages). Full isolation race52 passes and real boot focus31 passes with no skips. The accepted predecessor Store compiles and fails9 behavior events. Gofmt, vet, full lint, make check and diff checks pass. Additional required builtin/boot command is running; new hosted/native evidence remains pending.
Problem: worktree task dispatch silently discarded effort, step limits and configured subagent models; parent model aliases also lost live session effort overrides.
Root cause: isolation branched before ordinary task runtime resolution, and alias resolution reloaded config instead of retaining the canonical parent runtime entry.
Fix: reuse shared task/model/effort/budget resolution, pass ephemeral options to the unattended Build, and canonicalize parent aliases to their runtime entry. Existing unsupported isolation restrictions remain refused; best-of keeps its prior policy. No persisted, wire, tool-definition or default changes.
Verification: complete 238-package root inventory passes (217 pass/21 no tests; 23289 test/subtest passes, 215 existing conditional skips, 198 cached packages). Owning delegation/isolation race has 400 passes; real boot focus has 39 passes, no skips. Compiling old owners fail 18 runtime scenarios plus parent, with two automatic-budget controls passing. First-Finish composition race has 32 passes. Gofmt, vet, full lint, make check and diff checks pass. Supplemental required builtin/boot is still running; hosted/native results remain pending.
Problem: Windows CI rejected successfully recovered isolated output because the new boot assertion required LF bytes.
Root cause: Git checkout may apply core.autocrlf conversion; recovery preserves the expected text with CRLF.
Fix: explicitly enable autocrlf in the disposable repository and normalize CRLF for the content assertion, retaining same-ID recovery and source-before-apply checks.
Verification: the old assertion compiles and deterministically fails with CRLF on macOS; the repaired four boot regressions pass under race (6 test/subtest events). Required builtin/boot pass (2291 passes, 66 existing conditional skips); vet, lint, make check and formatting pass. Native Windows replacement CI remains pending.
Problem: isolated work could lose its recovery ID or be removed when the
first candidate inspection or completed-result publication failed.
Root cause: Finish treated inspector/persistence errors like successful
empty results, and Execute hid Finish errors behind a runner failure.
Fix: retain the candidate and original ID, publish a separate versioned
inspection marker without overwriting existing records, reuse rooted
admission and same-ID inspection, and report live-only retry explicitly
when recovery publication is unavailable. Join both errors. Keep normal
completed records unchanged and refuse unsupported inspection versions.
Verification: complete 238-package root inventory, isolation and real boot
race tests, compiling preceding-owner behavior failures, actual previous
reader/sweeper compatibility controls, formatting, vet, lint and check.
Local evidence is macOS only; hosted checks and independent review remain.
Problem: A reader's hooks can change files without participating in rewind
exclusion, retiring undo, or recording the checkpoint's coverage gap.
Root cause: The invocation caller selects checkpoint admission using only the
concrete tool's read-only flag, even though it already knows that its hook set
may write. The checkpoint owner is correct but this caller bypasses it.
Fix: Include possible tool-hook writes in the two existing admission/coverage
conditions. Reuse the existing writer permit and deferred cleanup. Keep empty
hook readers unchanged and keep tool execution, coverage, and effects separate.
Verification: Compiling original owners fail the actual callback/undo and four
boot regressions; fixed owner/checkpoint and boot race tests pass. Full root
inventory, required builtin/boot, formatting, vet, lint and make check are
qualified separately in the PR. Asynchronous hook process lifetime and the
barrier's existing cancellation limits remain explicit follow-up work.
Problem: A hook can change files before refusing a tool or outside a reader's
declared paths without notifying an opted-in workspace sink. Nested writers
also omit publication until an outer writer happens to complete.
Root cause: Invalidation is gated by concrete tool execution and classification,
and publication belongs to the batch caller rather than the invocation owner.
Fix: Track possible tool-hook effects separately from Executed, publish through
the shared invocation exit after cleanup, and share result metadata projection
between batch and nested calls. Unknown hook scope invalidates content, tree and
Git metadata without claiming a confirmed write or widening authorization.
Verification: Full root 238-package inventory passed; complete agent/event race,
eight boot direct/capability hook cases under race, original-owner compiling
negative controls, and provisional-lease composition passed. Formatting, vet,
full lint and make check passed without changing the baseline. Local evidence
is macOS only; optional sink wiring and evidence accounting remain separate.
Problem:
Aborting provisional admission can expire an unrelated live user wait or
retake paths deliberately yielded to a user. A pending wait is also lost
when the last writer or an earlier restoration finishes afterward.
Root cause:
Physical release reset run state, and one retained extent represented both
active and parked ownership. Restoration dropped its temporary protection
before reholding the original caller. Expired queued grants and retention
callbacks could contribute resources to the next run.
Fix:
Separate active and parked retention, advance generations at the final
participating run end, and reconcile outstanding yields whenever writing
holds drain. Keep restoration protection through rehold, fence late
admission and retention, and clean expired grants without dropping current
writers. Actual in-flight writes stay protected after their run ends.
Verification:
The submitted owner compiles and fails all 14 selected test/subtest failure
events across the new public-API schedules. Corrected full workspacelease
and agent race pass, as do actual boot race, all 238 packages in disjoint
required/remaining partitions, required builtin/boot, vet, lint and check.
Local evidence is macOS only; hosted checks and review apply to the new head.
Refs #14025
Refs #14133
Problem:
A refused write can pin a never-executed path through later model requests.
Repeated failed restoration can also report success to an expired caller.
Root cause:
Admission merges every request into the run-retained extent before local
preparation succeeds. Call cleanup only decrements in-flight accounting.
Resume keeps its once-only result in per-invocation locals, and refused
upgrades omit the terminal wait notice after registrar contention.
Fix:
Track identified provisional calls separately from retained extents and
publish their remaining union through the existing claim registrar. Retain
before possibly writing hooks or execution. Preserve earlier writes and
parallel claims, report failed cleanup conservatively, validate lifetimes
before and after reacquisition, and cache terminal callback outcomes.
Close registrar waits on refusal and clear completed cleanup's wait state.
Verification:
Complete 238-package inventory and required builtin/boot pass on the final
source; full workspacelease/agent race and focused real boot race pass.
Compiling original execution, resume and upgrade-wait owners fail the
selected deterministic regressions. Formatting, vet, lint and check pass.
Local platform evidence is macOS only; hosted checks and review are separate.
Refs #14025
Refs #14133
Gathers the 38 fragments since 2.50.0 into one note: video wallpaper, find
case/regex switches, shortcut sheet, paged transcript loading, a narrower
reading column, dispatch trust and the delegation write-safety fixes.
Problem: standalone UI route checks require the paging API to have landed.
Maintainer guidance confirms that this check should remain a landing
dependency until #14365 is merged and the UI branch updates its studio base.
Restore the original route scanner and keep the HTTP fallback integration
tests, which prove runtime compatibility independently of landing order.
Verification: the original route test passes in the combined API/UI tree.
The standalone failure remains expected until the API base update. Vet,
complete lint, check and builtin/Boot validation pass without changing
production code, timeouts or coverage floors.
Problem: optimistic children can write paths held by live sibling tasks,
including through dynamically resolved local capabilities.
Root cause: whole-workspace optimistic grants bypass explicit-fence wrapping,
child options omit the shared scheduler, and parent admission skips children.
Fix: inherit the existing scheduler and admit actual named targets at execution,
using the actor grant's root and one complete resource set. Retain parent and
explicit-fence ownership, opaque tool availability and existing permissions.
Leave observation roots, provider prefixes and tool definitions unchanged.
Verification: compiling old owners write held files in four real boot task/fleet
direct/capability cases; fixed cases refuse overlap and preserve disjoint writes.
Full238-package root, required builtin/boot, complete three-owner race and focused
real assembly race pass. Vet/lint/check pass without raising the baseline.
Native hosted checks and independent review remain separate delivery gates.
Problem: the API-only PR failed the English refusal catalogue check.
Root cause: splitting the implementation retained the refusal codes but left
their English wording only in the UI branch.
Fix: include the same two English translations in the API branch, without
adding UI implementation.
Verification: reproduced the catalogue failure; all 170 i18n tests, typecheck,
vet, full lint, check and builtin/Boot suites pass. Existing catalogue
assertions and budgets are unchanged.
Place the shared refusal translations in EN_KERNEL on both branches so the
independent API and UI changes compose without a translation conflict. All
170 locale tests and typechecks pass on each branch.
Problem: standalone paging UI failed the CI serve route contract.
Root cause: the scanner assumed every frontend endpoint was mandatory, while
paging explicitly supports older servers through full-history fallback.
Fix: declare only the exact paging call optional, require its fallback route,
and exercise the HTTP adapter and hook across unsupported and real failures.
Verification: reproduced the original failure; route tests, 19 frontend tests,
typecheck, vet, lint, check and builtin/Boot suites pass. Covergate tests pass;
unchanged computer coverage is 88.2% on macOS versus the 89.8% floor, also
reproduced on the sibling branch. Ubuntu coverage remains for CI.
Place the shared refusal translations in EN_KERNEL on both branches so the
independent API and UI changes compose without a translation conflict. All
170 locale tests and typechecks pass on each branch.
Problem: CodeQL found two new uncontrolled-path stat alerts in record admission.
The first validator also read a claimed Git administrative backlink before
confining that path to the host-opened common directory.
Root cause: comparing embedded paths with global os.Stat did not itself provide
a filesystem namespace boundary, and marker discovery still used path globs.
Fix: enumerate and read slots through the opened managed root, perform identity
stats through root-relative handles, and constrain candidate/common metadata
reads before opening a backlink. Derive effective identities from checked host
roots, retain unknown fields unchanged, and treat brackets in root names literally.
Existing alias resolution remains metadata-only and grants no content-read scope.
Verification: final 238-package root inventory and required builtin/boot pass;
complete isolation race has 32 passes and real boot isolation race has five,
without skips. Six old-owner behavior groups compile and fail, namespace and
literal-directory regressions pass, and vet/lint/check retain the same baseline.
The replacement hosted CodeQL verdict and independent review remain pending.
Problem: the transcript paging UI conflicts with the upstream first-save bookmark fix. Root cause: both changes update the Pane-to-Transcript boundary, and retaining the runtime-id fallback loses unsaved stars on first save. Fix: merge studio f96ec2f869, retain session-path-only bookmark ownership alongside paging, and exercise both full and paged restoration. Verification: the upstream regression fails with the old scope and passes after resolution; 37 focused tests and 791 frontend files / 6343 tests pass, as do the production build, go vet, complete make lint and builtin/Boot suites. No lint baseline or deadline was changed.
Problem: a rebuilt store can trust a copied or mismatched durable record and
use its embedded candidate paths outside that record's actual managed slot.
Root cause: discovery checks the claimed workspace but does not bind snapshot
and candidate identity to the host-opened repository and observed marker slot.
Comparing Git markers only by inode also accepts cross-candidate hard links.
Fix: centralize record admission before every durable consumer; verify managed
directory membership, source identity, prefix roots and the linked candidate's
actual marker/backlink. Retain rejected records without rewriting metadata,
and resolve legacy identity through filesystem checks without launching Git.
Verification: five compiling old-owner negative-control groups fail as expected;
final 238-package root inventory and required builtin/boot pass, owning isolation
race has 30 passes and real boot isolation race has five, all without new skips.
Formatting, root vet, full lint and make check pass with the baseline unchanged.
Native hosted checks and independent review remain separate delivery gates.
An unsaved conversation scoped its rail stars by the pane's runtime id, and
the first save switched the scope to the session path, which reloaded from
an empty key: the star disappeared. The id is also a per-launch counter
(r1, r2, ...), so the key it left behind was read again by whichever unsaved
conversation pane r1 held after the next restart.
An unsaved conversation now keeps its stars in memory, and the first save
writes them under the session's path, merged with anything stored there.
The pane passes the session path alone as the scope.
TestFinalObservationIsBoundedAfterACompletedAnswer gave Run one second
after cancel (three after the deadline) to come back. Run still seals the
contract and the bundle after its context ends, because the first lock
attempt does not wait; locally that takes about 18ms, but on a loaded
windows-latest runner it overran the window and the test reported a held
observation that was not there (studio runs 40110025629, 40064086851).
The observation the test holds never returns until it is released, so any
finite wait catches the regression; use testenv.Budget for both waits.
Recovery conflated a failed Git inspection with an empty result and erased
the durable marker before attempting worktree removal. Return inspection
errors without cleanup and report the existing unavailable refusal instead
of forgetting a valid result ID.
Exercise apply path discovery, Apply and Discard against real retained
worktrees after restarting the store and temporarily hiding Git. Preserve
marker bytes and work, then retry the same ID after Git returns. Keep age
expiration and pre-publication Finish recovery as separate lifecycle units.
Separate review-role FileViews from the executor and bind direct/deferred
local tools to one fresh record for each role. Install the planner frontend
before binding so its ledger/audit and view ownership survive boot wiring.
Keep the generic read-only filter a same-actor operation, and preserve the
existing nil-runtime planner adapter without changing its tool surface.
Add four real-boot stale-overwrite regressions, runtime/clone accounting
checks, a generic-filter compatibility case, and written security bounds.
The compiling old-owner overlay reproduces all four overwrites.
Guardian's visible registry was filtered, but its read-only capability proxy
could resolve an executor writer or a declared host mutation. Set the
permanent execution flag in the session constructor so actual targets are
checked before they run, without changing the review's executor approval.
Add local resolver and real boot regressions, with compiling old-owner
failures for both mutation classes and an actual file overwrite. Document
the authorization boundary and the separate file-view ownership concern.
The drag floor was the readable measure itself (760), so the two presets were the
only widths a hand could reach on a typical window. A hand now reaches down to
640; the readable measure stays the untouched default and what Home returns to.
Problem: a child's read can refresh the parent's stale overwrite guard,
allowing the parent to discard an edit it has never read.
Root cause: copied file tools share FileViews, while deferred local tools
resolve through the root registry even inside nested children.
Fix: bind one fresh view per child to its direct and deferred file tools.
Keep disabled protection, tool identity, grants, read-only execution and
stable schemas intact. Install bindings on fresh capability frontends only.
Verification: original-owner controls reproduce the real boot overwrite;
a parent-pointer mapping control fails four nested cases, while the actor
binding passes all six shared-runtime/clone combinations. Root package,
owning race, composition, vet, check and lint evidence is recorded in the PR.
Refs #14025
Refs #14133
A dispatch target is resolved against the user's sidebar and the dispatch
switch is already the user's consent, so stopping on the folder's trust card
asked the same person twice. The folder is now recorded as trusted and the task
goes through; a folder that cannot be trusted whole still keeps its pane open on
the card with the task unsent.
Problem: Both extracted PRs became modify/delete conflicted while their local
qualification was running.
Root cause: Upstream removed FIRST_RUN_WORKSPACE.md in #14367 after the slices
had repaired its inherited lint metadata and paragraph violations.
Fix: Accept the upstream deletion and remove the now-obsolete prerequisite
repair. Retain every feature, regression and security-document change.
Verification: make check passes. The staged merge changes no Go file relative
to the independently qualified slice heads; their full package/race evidence
remains explicitly scoped to those source trees. Hosted checks must run on
the new merge heads. No unrelated production change or baseline relaxation.
Refs #14025
Refs #14133
Problem: Both extracted PRs became modify/delete conflicted while their local
qualification was running.
Root cause: Upstream removed FIRST_RUN_WORKSPACE.md in #14367 after the slices
had repaired its inherited lint metadata and paragraph violations.
Fix: Accept the upstream deletion and remove the now-obsolete prerequisite
repair. Retain every feature, regression and security-document change.
Verification: make check passes. The staged merge changes no Go file relative
to the independently qualified slice heads; their full package/race evidence
remains explicitly scoped to those source trees. Hosted checks must run on
the new merge heads. No unrelated production change or baseline relaxation.
Refs #14025
Refs #14133
Problem: A snapshot-only regression does not establish that the shared tree
reader also protects the source workspace before Apply publishes a candidate.
Root cause: The old cached index can hide a user's intervening edit from the
whole-tree drift check, allowing Apply to return success and overwrite it.
Fix: Exercise actual snapshot/candidate/Apply assembly on disposable real Git
repositories. Require ErrWorkspaceMoved and unchanged user bytes. Document the
existing Git driver confinement and the additional rehashing cost explicitly.
Verification: A compiling overlay of the original production file fails for
both wrong success and actual overwrite; the repaired owner passes. Full final
worktree race passes (48.247s). Broader root and static qualification records
are retained separately; production code is unchanged by this test follow-up.
Refs #14025
Refs #14133
Problem: A candidate snapshot can contain old bytes after the working file
changed while preserving its size and mtime.
Root cause: Copying the source index carries Git's trusted stat cache and
assume-unchanged state into StageAll, so staging can skip the actual bytes.
Fix: Preserve the copied index's staged tree, rebuild the private index from
that tree, then stage current content. Handle an unmerged private index first.
Keep staged ignored inputs, the real conflicted index, HEAD and refs intact.
Document concrete Git/path/authority boundaries and the unchanged need for
source coordination. Include the inherited documentation-lint prerequisite.
Verification: All three cache-policy variants fail against the original
function through a compiler overlay and pass with the repair. Full worktree
race passes, plus final expanded cache and conflict regressions. Check, lint
and root vet pass. Full builtin/boot exits zero (22.243s / 444.076s tests),
using a measured 15-minute package alarm with no product-deadline change.
The exact-commit full root sweep and hosted checks remain separate evidence.
Refs #14025
Refs #14133
Problem: Task-length write claims cannot independently represent permission,
assignment and the physical mutation lifetime across cooperating runtimes.
Root cause: Shared admission, input preconditions, cancellation and release
have no common short-transaction owner that callers can adopt independently.
Fix: Extract the mutation resource/queue/capacity owner and content versions
from the preserved concurrency implementation. Add minimal non-blocking legacy
claim compatibility and exception-safe publication cleanup. Keep all runtime,
isolation, hook, controller and default activation outside this review slice.
Document authority, actual sinks, compatibility and unenforced boundaries,
and map the remaining source into dependent review units. Normalize an
inherited design-document lint prerequisite without relaxing its baseline.
Verification: make check, full lint and root vet pass. Owning-package race
passes, including real subprocess death and a strengthened cleanup regression
that fails without the cleanup via compiler overlay and passes with it.
Builtin passes. The full boot invocation reaches its 600-second package alarm;
all 811 top-level tests close in four inventory-verified groups (808 pass,
3 conditional skips, no missing/unexpected/failing selected tests). Linux and
Windows amd64 test binaries cross-compile; native platform and hosted CI
evidence are separate gates. The remaining root-package sweep is in progress.
Refs #14025
Refs #14133
Problem: searching for a message outside the initial page could remain incomplete after one fast response. Root cause: the search effect depended only on busy/hasMore flags that React may batch back to their previous values. Fix: advance search on committed page revisions and release the completed single-flight request before publishing that revision. Verification: failing-before revision regression, 28 focused tests including scroll and anchor neighbors, and a browser replay fetching four pages and finding the first message.
Problem: full transcript projection and transfer scale with conversation length. The session store now owns stable page snapshots and cross-page context, exposed through optional controller reads and an additive HTTP route. Existing full-history clients are unchanged. Validate through real Boot/resume, full-projection equivalence, cursor invalidation and append stability, request authority and foreground preemption. Focused history tests, race checks, Go lint and release-note tests pass; broad suites are recorded in the PR.
Rebase the UI portion of #14087 onto current studio, preserving regex/case search and session-scoped navigation. Use optional paging with explicit unsupported-route fallback and retain mounted controls when prepending. Browser verification exposed zero-box display-contents wrappers; share rendered-box geometry with the landing writer and add a failing-before regression. Verification: 37 focused frontend tests, TypeScript checking, production build and wide/narrow light/dark browser renders; full-suite results and backend dependency are documented in the PR.
Define the optional history-page response and typed refusal translations shared by the API and UI splits. No caller switches to paging in this commit. Verification is performed on each independent split before publication.
Documents the last coupled first-run decision: with nothing remembered,
the first pane's workspace root falls back to the user's home directory
(pickLaunchWorkspace), so a session that was never given a chosen project
reads as temporary. Proposes a kernel-provisioned auto workspace instead,
modelled on MiMo Desktop's session-first behaviour (auto-created session
directories, global provider config, optional projects), and maps it onto
the existing launch chain, session store and workspace switch.
Keep the install slot claimed until a cancelled download finishes cleanup, preventing it from cancelling a new install or deleting its files. Add regression tests and a release note.
Expose download cancellation through the update API and Versions UI. Cancellation waits for the transfer to stop, removes its partial and cached artifacts, and reports a distinct non-failure state. Automatic updates do not retry a cancelled release but can proceed when a newer version is offered.
Off means nothing sleeps, cap included: a person who never turned the
setting on kept losing the least recently used page's renderer when a
seventh page opened. The cap now runs only on a finite threshold, the
same one the sweep reads.
The rx-page-sleep desktop preference decides it, default off: prefs.js
owns the threshold, the shell reads it at boot and on every preference
save, and Settings -> Appearance carries the toggle. A woken page also
comes back fitted into its panel at the scale it had before the sleep.
Five real Chromium pages through BrowserViews at a tiny threshold: the
working set they cost, the working set the sleep returns, a wake that
must answer against the page as it loaded, and a command the sleep
outlives that must fail rather than hang. Run with pnpm sleep-live.
An agent page's renderer is hundreds of megabytes the Studio pays for
whether anyone is watching. A page untouched for fifteen minutes and not
on screen is closed whole — address and a picture kept — under the same
targetId, so the kernel's handle survives; the next command, show, or
typed address rebuilds the view, replays the domains the kernel last
enabled (a domain switched off mid-session stays off), and answers only
once the page has loaded — a mid-load answer would describe the page it
was, and the kernel could not tell. A command the sleep outlives is
failed fast, for the session that dies never settles it and the kernel,
told nothing of a sleep, would wait on a page that will not speak. Six
awake pages at most — a wake yields no room: the page asked for
outranks the pages left alone — and eighteen asleep ones; past that the
oldest is buried and the kernel is told, the way a browser discards
abandoned tabs. REASONIX_NO_PAGE_SLEEP puts the feature away.
It sized itself 42px inside the pane on purpose; pulling that to 34px to match
the text moved the mirror's wrapping off the caret's in composer-reflow.
Opening the todo list grew the composer, which shortened the scroller, which
re-measured the rail: every mark moved up and the two jump buttons went with
them. The panel now floats above the composer instead of taking its room.
`--flow-w` went to `:root`, where a committed value on `.app` outranks it;
the sidebars only gave way on release, and the composer never followed at all.
Each key cell was as wide as its own text, so the descriptions started at six different positions. A shared minimum on the key cell gives them one column.
`groups` is a state slice; after a rewind shortens `marks`, a stale fold still pointed past its end and `marks[v.from]` was undefined. The fold is filtered to what exists.
`useSidebarOpen` dropped the promise with `void`, so `Workspaces` never saw the rejection and the row stayed on its old selection. It now returns the open's own promise.
`en_export.ts` repeated 我 and 会话, and `i18n.test.ts` rejects a key that lives in two catalogue files. The headings now use the entries already there. The menu test follows the second export row.
`^` and `a*` match the empty string, so the counter reported hits the painter had no range for. Both sides now skip them. Transcript.tsx keeps its 800-line ceiling by typing the mode inline instead of importing it.
The painter still searched the DOM for the literal text while the counter honoured the new modes, so a regex hit had no range to scroll to and a case-sensitive count did not match the painted one. Both now share one finder, a regex match advances past its own length, and a pathological pattern is cut short instead of freezing the window.
prompt_tokens_details with cached_tokens: 0 is the endpoint reporting the
split, so miss is derived from prompt - hit instead of staying zero and
vanishing into the unknown bucket; a hit above prompt_tokens is a broken
report and clamps to it. The overview and the meter floor the rate, so 100%
means every split token hit.
A shortcut sheet in the settings.
Every chord the window answers to, on one page: find, search, the three views, stepping through
your questions, the workbench toggles, send, close, and the width keys. Chords are written the way
the platform writes them, so macOS shows ⌘ where Windows and Linux show Ctrl. It is a page of the
settings table of contents, so searching for "shortcut", "Esc" or "运行分析" lands on it.
The strings live in `en.ts` with every other catalogue entry: the i18n guard requires one
catalogue file, and an earlier revision of this branch split them out and failed it.
Make run analysis easy to enter and to leave.
- The entry in the window bar now carries its name. It needs an inline layout for that: the
button is a 30px grid cell, so the icon and the label stacked and the text broke one character
per line. `.chrome .thbtn.top-action` takes over the layout for this variant and keeps the
label on one line; below 1180px the label steps aside so the other controls stay put.
- The analysis page has a back arrow beside its title.
- Picking a conversation that is already on screen returns its pane to the reading view instead
of re-opening it. That rule now lives in `sidebaropen.ts` — partly because it is a rule about
panes rather than window wiring, partly because `App.tsx` was 11 lines over its ceiling with
it inline. It has its own test: open pane returns to reading, unopened opens a pane, a folder
request opens a pane, and asking twice lands twice.
`Pane.tsx` differs from `studio` in one line (`onBack`), so the `useAgentRequest` wiring from
#14291 stays intact.
Match case and regular expressions in the find bar.
`usefind` now hands back `regex`, `caseSensitive` and `setMode` with the rest of its state —
the previous push declared them on `Finding` and never returned them, which is what
`frontend-next` caught. Changing either reading restarts from the first hit.
Rewritten against the shape the kernel actually writes. A transcript line is a provider message
(`role`, `content`, `tool_calls`, `host_authored`), so the export keys on `role`: user and assistant
only, skipping host-authored user lines, tool results and system lines, and skipping assistant turns
that carry only tool calls. Headings go through `t()`. Native archives keep their `records` instead of
exporting an empty list. The test now uses real JSONL lines and covers the native-archive case.
The controller runs only `/mcp connect` (#14167), so `/mcp add x` now
answers "unknown /mcp subcommand". Both guides and `reasonix mcp help`
still sent a running chat there to save and connect a server. They now name
`n` in the terminal /mcp panel, which installs through the same
POST /mcp/install as Desktop's Add and connect. The guides also name `p`,
the panel's project install, which InstallMCPServer writes to the
project's `reasonix.toml` (MCPConfigPathForEntry -> ProjectConfigPath).
TestTheDocsNameOnlyMCPAndSkillsSubcommandsThatRun reads docs/*.md and the
READMEs and holds every `/mcp <sub>` and `/skills <sub>` they write to the
subcommands completion offers, which are the ones the controller runs.
TestTwoAsksRetakeTheClaimOnlyAfterTheLastIsAnswered answered the first ask
as soon as two asks were registered. Ask registers before it yields the
workspace claim, and Yield reads the claim log from disk before it counts
the waiter, so on a slow Windows runner the second ask could still be short
of the yield when the first one resumed. The first resume then saw no other
waiter and took the claim back, and the test reported the claim retaken while
the second ask waited (studio 39941591112, windows-latest).
The test now waits for the second ask's prompt-queued notice. Ask reaches
lockPromptFor only after its yield, so the notice is the point at which the
second ask is waiting on the user.
CI on Windows failed both new assertions: the detail named
`c:\outside-watch\reasonix cli.desktop` and the temp deliverable came
back lowercased, while the tests compared against the path spelled as
constructed. `evidence.normalizePath` lowercases on Windows, because the
ledger needs one identity per file and Windows hands back either case.
Compare against that identity instead of the constructed spelling: the
rule under test is about which paths are reported, not how the host
happens to capitalise them.
The gap names writes the host watches nothing of. A folder the person
granted as workspace is walked with the root and observed the same way,
so a write there is work product and must not carry the caveat.
This pins the rule at the boundary extraRoots decides, so a later change
cannot start reporting exactly the deliveries the grant exists to allow.
`Caches` read the process's home directory itself, which put the package two
reads over its `process-input` budget and the repo one over its ceiling. Both
lists answer from the same fact, so it is read in one place and passed to each.
The cache list also gained the two entries `Dirs` already had on the platforms
that have them: `$HOME/.cache` everywhere, and `Library/Caches` on macOS. A
cache filter that named only `go`, `.cargo` and `.npm` would report a Unix build
cache as out of sight on the one platform whose caches live under a different
directory name.
Two defects, both found by CI on Windows and both in this PR's own new code.
`underScratchOrCache` asked `hostwrite.Dirs()` for the toolchain caches. That
function answers what a jail grants a command, and Windows jails nothing, so it
returns nil there: every build cache write was reported as out of sight on the
one platform where the list is empty. `hostwrite.Caches()` answers the question
actually being asked — where does a build write through — and is defined on every
platform. The two questions stay apart because conflating them is what made a
Windows build produce a caveat about its own module cache.
The one-off directory `sessiontemp` hands a turn is recognised by its position,
not by its name. It is a direct child of the host's temporary root, so that root
is read from the host and the first component below it is compared against the
prefix. The previous version walked every component, which exempted any folder
deeper in the tree that happened to spell the prefix, and it assumed the root was
named `/tmp`, which is not where Windows puts it.
The tests that pinned these edges set `HOME` and `TMPDIR` and then read the
result, which is why they agreed with the code on Linux and disagreed with it
everywhere else. `HOME` and `TMPDIR` are POSIX-only names: on Windows the
temporary root comes from `TEMP`, so `t.Setenv("TMPDIR", …)` changed nothing and
the assertions ran against the real root. They now call `disposable` with the
roots as arguments, and build paths from the host's own temp root, so each
assertion is about the rule rather than about an environment.
Tested: the thirteen out-of-scope tests, the packages this PR touches, and
`GOOS=windows go vet` on `internal/runtime/agent` and `internal/base/hostwrite`.
A turn that wrote a file outside the workspace and then answered ended with a
`done` receipt and no gaps. The host saw the tool call succeed, but its
observation only covers the workspace and its granted extra roots, so the write
was dropped before the report was built: no mutation, nothing to verify, nothing
to say. `final_readiness` agreed — it judges a turn by whether it has a writer,
and a write it cannot see is not one — so the turn ended as a quiet success
while the work it claimed had landed nowhere the host could check.
Record those writes as a gap instead. `outsideObservedWrites` reads the turn's
own successful write receipts and keeps the paths that are outside the workspace
and outside what the host owns: the session scratch directory, toolchain caches.
Two exemptions carry the weight. A public temporary root is not exempt — a
person who asked for a file there asked for something to keep — and neither is
`bin/` under a cache, because `go install` puts the user's own executable in
`~/go/bin`. A scratch directory earns its exemption only as a direct child of a
temporary root, so a folder is not exempt for spelling the prefix.
Reported, never enforced. The verdict drops from done to partial and the receipt
names the gap; the turn ends exactly as it did before. A person who asked for a
file outside the workspace got what they asked for, and what they are owed is
the line saying that part is out of sight, not a turn that will not end. For the
same reason the gap is a notice, not a warning.
CompletionReceipt also stops discarding the report's own gaps when the verdict is
unknown. It returned only the claim-yield and mod gaps there, so a turn the host
could not judge — a plan with no criteria, writing only outside the workspace —
went back to saying nothing at all.
Blast radius: the completion report on turns that write outside the workspace,
and the receipt the host renders for them. The readiness gate, Delivery, Goal,
cancel and budget paths do not read the report, so none of them is affected.
Neighbouring behaviours tested: a turn that writes outside the workspace is not
reported as finished; one that stays inside keeps its quiet ending; a plain
answer and a read-only investigation end with no caveat and no continuation; a
scratch probe carries none; a deliverable in the public temp directory does; a
lookalike directory and an installed executable are not exempt; an unfinished
todo list still holds the turn; an unjudged turn keeps its gap. Desktop
`ReceiptCard` gained one case, with the three catalogue entries the i18n gate
requires; typecheck and the full vitest run pass.
Scope: this reports a write the host cannot see. It does not decide that a turn
which never wrote anything was unfinished — a read-only multi-step turn that
stops early still ends as it did.
A background sub-agent writes its work into its own transcript and its tool
calls, never into the job that started it. The job's activity stamp therefore
never moved, and the watchdog judged the run by bytes on streams that stay
empty: a busy sub-agent read as a job that had been silent since it started, and
a stalled notice went out about work that was progressing.
Bind the child's progress to its job at beginExecution — the one boundary every
delegated run crosses, foreground included, where a context with no job binds
nothing. A finished tool call is one reported step; token deltas refresh the
activity stamp at most once every two seconds, because the watchdog reads
minutes and the child emits far more often than that.
Reported steps are kept apart from output text rather than folded into it. They
answer different questions — whether the child advanced, and whether it wrote
here — and a caller decides from the pair. `wait` says both, and a stalled
notice says what the job had reported before going quiet, since a job that
worked and stopped needs a different next move than one that never got going.
Tested: internal/tools/jobs (a job that writes nothing stays alive while it
reports progress; wait reads steps separately from output), internal/tools/builtin
(progress wording per reading), internal/runtime/delegation (binding at
beginExecution, a finished tool call reports one step, deltas are paced, a
foreground run stays unbound), internal/contract/event, and desktop
frontend-next (typecheck; full vitest run, 6120 tests). The desktop stalled-notice
sentence had no English for its new wording and the i18n gate caught it; the
three entries are added here.
`wait` accepted any `timeout_seconds`, and omitting it meant blocking until
every job finished. A sub-agent that never exits then holds the turn open with
no way for the caller to tell a long wait from a hung session: the tool result
is the only thing that returns, and it returns only when the job does.
Cap one wait at 300 seconds and apply that cap when the timeout is omitted too,
so omitting it means "wait for the jobs" rather than "wait forever". The result
says when the ceiling was reached, and only then: a job that finishes first
returns before it, and claiming a 300s wait that never happened would be a
second false report in a message whose whole job is to be accurate.
The schema states the same bound the runtime applies. It previously promised an
unbounded wait, which the tool no longer performs.
Tested: internal/tools/builtin (wait ceiling applied and reported, a 1s wait
returns progress instead of holding), internal/tools/jobs, and the boot golden
recapture for the changed tool schema. internal/assembly/boot has one
pre-existing failure on this host (TestEffectSessionScratchIsReachableByItsSpelling)
that reproduces on the unmodified base: bubblewrap cannot create a namespace
here, so the sandboxed bash call the test relies on refuses to run.
absorbInterruptedCopy called os.Lstat itself, which CodeQL reports as a new
path sink in changed code. sessionArtifactExists is the store's pre-existing,
baselined reader for the same question: the copy exists when its transcript or
an owned sidecar does. It answers bool, so missing and unreadable look alike;
the import's callers already accept that, and a copy that is there but cannot
be read still reports its error where it is loaded.
CodeQL flagged the two sidecar paths as new sinks once copyBranchMetaSidecar moved into migrate_copy.go: the os.ReadFile(BranchMetaPath(srcPath)) body was baselined in migrate.go, and a moved copy is not. The function moves back to its original position with only the error return changed, and completeBranchMetaSidecar now asks the pre-existing LoadBranchMeta reader whether the destination sidecar is there instead of calling os.Stat itself: ok means present and never rewritten, an error means unreadable and never clobbered, and only a clean absence proceeds. sameDirPath (a pure path comparison, no filesystem access) moves to migrate_passes.go so migrate.go stays inside its ratcheted file-size budget.
An import publishes the transcript first and the branch sidecar that carries
what it means second. A run stopped in that window left a transcript with no
sidecar, and the retry treated the transcript as completion: the flat import
healed only subagent artifacts and stamped the marker, and the v4 import only
healed the title. Both now finish the sidecar, refuse a failed sidecar write
as incomplete so the next run retries, and leave a finished copy untouched.
A v4 copy that is still a prefix of 1.x is brought up to the source; a copy
continued in 2.x keeps its own turns.
A power loss on NTFS can leave a .meta sidecar extended but never flushed, so it holds only NUL bytes. LoadBranchMeta reported that as a decode error, which made the recovery-lineage walk refuse to archive anything in the folder and kept UpdateBranchMeta from ever rewriting the file. The file records nothing, so it now loads as a missing sidecar and the next save replaces it.
Signed-off-by: ShryukGrandhi <[email protected]>
loadMemory returned (false, nil) for a read error, so every caller treated a
present-but-unreadable fact file as missing: it vanished from /memory, the
desktop panel, the memory tool and recall, and a save by name then minted a new
id at revision 1 over it with no snapshot, losing the old identity and history.
loadMemory now returns a typed FactReadError. ListActive reports unreadable
facts beside the readable ones, and the listing, read-by-name, panel, tool and
recall audit all name them; recall still serves the readable facts and keeps
its old sentence for a whole-directory failure. A save by that name refuses
before snapshotting or writing anything.
The terminal counted and drew a host-authored inbox entry as the person's
own pending line, so `d d` on it reached DELETE /inbox/items/<id> and the
kernel dropped the runtime's continuation. The desktop has always filtered
these out; the terminal never decoded the field it filters on.
The mirror now carries origin, readonly and capacity, and a declared-subset
wire-parity check holds it (and the page's copy) to the fields a decision
reads, so dropping one is a `make check` failure instead of a silent zero.
A host continuation is out of the pending rows and out of the count, except
where it is blocked or uncertain and needs a decision only the person can
make; there it is drawn marked as the runtime's. A read-only snapshot offers
no mutation key and says why.
The force-push to the restacked head did not start the test/build workflows
(closing and reopening only re-ran the labeler), so this empty commit emits a
synchronize event. No code change.
A line that carried a currency and a quote of zero was priced by the exact
JSONL read but discarded by the projection, which treated amount 0 as "no
price". A single-day range reads JSONL while a multi-day range reads the
projection, so one session showed [{CNY 3},{USD 0}] on 今日 and [{CNY 3}] on
30 天, lost costEstimated on a day whose only quote was zero, and could emit
the false "usage but no cost record" note.
A currency is the whole test for "priced"; zero is a genuine free quote. Both
build paths now keep it: projectRecord no longer drops amount 0, the rederive
view no longer filters cost<>0, and the rollup read no longer skips amount 0,
while a row with no currency still carries no money. No schema column was
needed, so SchemaVersion is unchanged; every launch reconciles the day files
through rederive, which repairs a projection built by the old filters.
Tests cover the fixed two-day history through the exact read, the incremental
append projection and the rebuilt projection, the zero-amount-only day's
costEstimated in both, and controls for a currency-less row and a non-zero
amount.
<active-goal contract="full"> carries the whole goal-mode contract into the
model-visible history, and nothing retired it when the goal stopped: the next
request carried no goal block while the contract still said to keep pursuing
it, and active-goal being a superseded tag made a fold keep that stale copy.
A stop the model did not report now renders a tombstone under the same tag:
reason paused, cleared, budget, or host (the completion check could not run).
A model-reported block and a completed goal compose nothing, because the model
already knows. Delivery is asked of the visible history through the delivery
PR's projectionDebt: the tombstone is owed while the newest active-goal block
in view is a contract or reminder, and not once it is the tombstone itself, so
a rewind or fold that drops it owes it again with no remembered state.
SupersededUserBlockTags is unchanged, so the tombstone is the newest copy a
fold keeps.
The boot harness's blockOf now matches a tag prefix, since a variant block
opens with attributes after its tag.
Retain legacy DAG payload locations instead of discarded message bodies, preflight JSON without copying large strings, and refuse oversized records or retained histories without modifying source data or falling back to stale checkpoints.
Refs #14116
A call that spent tokens but recorded no money was counted in the token
and request totals and contributed 0 to Cost, with nothing in the payload
saying its money was unknown. Two paths produced that shape.
Title generation wrote such a row live: the serve title model builds its
own stats.Recorder instead of emitting through the assembled sink, so no
CostQuoteSink sits in front of it. The title event carried a rate card but
no quote, and the recorder writes cost fields only for a non-nil quote.
It now prices the call at emit time with event.EnsureCostQuote, using the
same QuoteContext construction the assembled sink uses
(boot.QuoteContextFor), so it is quoted like any other call.
Rows written before the cost fields existed (and any row that simply
lacks them) had the same effect on reads. The incompleteness trigger is
now "no cost fact": an explicit cost_complete=false, or tokens counted
with no cost_amount. One predicate (usagecatalog.CostIncomplete) is
applied on all three read surfaces, so they agree.
No price is invented and no legacy row is backfilled.
The flat-import re-home took the save locks but never asked the session
lease, so it could move a WAL-backed transcript out of a live session root
and delete the old path while another runtime (or this one) still owned it.
Gate every move/remove in the migrate family on the same predicate
renameOverlongSession uses: sessionLeaseHeldLocally OR
SessionLeaseHeldByOtherRuntime. A deferred base is not counted as imported
and leaves the one-time marker unwritten, so a later run settles it.
Tests: audit red/green pair, unleased control, same-process lease control.
Problem: the post-answer finalization fix note would be missed by the current release fragment collection.
Root cause: it used the retired shared unreleased.md path and did not name the fixing PR.
Fix: move the bilingual entry into unreleased/14140-bound-post-answer-finalization.md, preserving #13953 and adding #14140 to both bullets.
Verification: release-note and Markdown-reference tests pass (12 tests); make check, fragment discovery, bilingual references, release rendering, and whitespace checks pass. Only release-note documentation changes.
Problem: the compaction fix note was outside the release fragment glob and cited the related finalization PR instead of its own fix.
Root cause: the note used the retired shared unreleased.md layout.
Fix: move the bilingual entry into unreleased/14144-compaction-eof-vs-cancellation.md and reference #14144 in both bullets.
Verification: release-note and Markdown-reference tests pass (12 tests); make check, fragment discovery, exact bilingual references, release rendering, and whitespace checks pass. Only release-note documentation changes.
Problem: compaction could report success and accept a partial digest after
the summary deadline or caller cancellation.
Root cause: providers close their channel when context ends. Selecting EOF
instead of the simultaneously ready cancellation accepted nonempty output;
the deferred ceiling classifier only corrected non-nil errors.
Fix: keep EOF validation at one result boundary and check the request context
before accepting a digest or classifying truncation. Preserve typed ceiling
and cancellation failures, usage accounting, and stable provider requests.
Verification: force EOF/cancellation overlap with a progress barrier and fake
clock, cover normal completion/truncation and charged usage, and exercise
the real Build-to-notice path. Baseline production source fails on macOS
and Linux; fixed Linux cases pass 20 repetitions. Focused race/cache tests,
full root tests, required tool/boot tests, vet, lint, make check, and release
script checks pass. A separate combined tree also validates this fix with
the post-answer finalization change from PR #14140.
The seer controller.New bound was the executor's method value, and that value
reads the session in place: a SetSession from /new, resume, a branch switch, an
extension system prompt or a fold is already visible to it. Those sites answer
from the current session. A model switch is different — it builds a new
controller, and ApplyRuntimeMigration adopts the old conversation into it. The
carried turns still hold the standing blocks the old model was sent, so the
derived rule reported them delivered and the new model's first request carried
no listing.
The seer now resolves through the controller and answers "nothing is visible"
for a generation that has delivered nothing yet. ApplyRuntimeMigration marks
that state when the replacement's model ref differs from the outgoing one; the
hand-off to the runner clears it, so exactly one request restates the standing
blocks and the visible history answers after that. A rebuild that keeps the
model keeps the derived answer.
Problem: A completed model answer could leave the controller running while
workspace observation or an evidence stream lock kept finalization alive.
Root cause: Post-answer observation and sealing detached their deadlines
from the turn context. Observation called filesystem operations synchronously,
so its existing timeout did not actually bound the caller's wait.
Fix: Carry the run context through every finalization entry point. Bound
observation with a buffered result that captures no mutable turn state,
record missing observations as gaps, and cancel unused initial snapshots.
Keep optional finalization cancellation from discarding completed answers.
Verification: Deterministic cancellation, deadline, observation-budget,
held-seal-lock and real boot/controller completion regressions fail before
the fix and pass on macOS and Linux arm64 after it. Related race checks,
121 frontend tests, formatting, vet, lint and changed-file checks pass.
The required builtin-tool and full boot package suites also pass.
Two rows of the picker table described the 1.x CLI: an "MCP import picker"
the 2.x terminal does not have (`reasonix mcp import` imports without one),
and an "MCP manager" whose `r` refreshed and whose number keys chose actions.
The 2.x /mcp panel (mcppanel.go, mcpmanage.go) moves with Up/Down or j/k,
opens a server's tools with Enter, toggles with Space, adds with n through a
review that installs with y, l or p, removes with d then y, signs in with a,
grants requested access with u, and reconnects a failed server with r.
The chat table also missed keys the terminal handles: Ctrl+S (steers a
running turn, sends an idle draft), Shift+PageUp/PageDown, Ctrl+Insert to
copy a selection, Shift+Insert to paste text where the terminal hands it
over, Ctrl+G to keep folded pastes as files, and Ctrl+D twice in the resume
picker. The tool approval row now says its numbers follow the rows shown and
that Esc does not answer in vi mode, and plan approval gets its own row. The
Chinese table also gains the Ctrl+Enter / /steer row it never had.
Pin the three behaviours no test held: Ctrl+S on an idle draft, an approval
number with a row missing, and Esc on a plan approval.
The English remote section gained two blocks the Chinese guide never got: a WSL
distribution as a machine (kind = "wsl", what it accepts, how Studio adds and
keeps it, where its model credentials come from, which subcommands stay
SSH-only), and the rule that an agent's own shell may run only `list`, `help`
and `wsl list|test` of `reasonix remote` (exit 77 otherwise). Translate both into
the same place in GUIDE.zh-CN.md, between the config example and the CLI block,
using the Studio labels the Chinese window shows (设置 → 远程, WSL 发行版).
The guide says `wsl test` runs for an agent, but the test pinned only
`wsl list`; add the `wsl test` row. A distribution that does not exist answers
after the same `wsl --list` the `wsl list` row already runs, so no distribution
is started.
Both were raised as non-blocking notes on #13784.
validPriceSource and its page mirror sourceProblem refused control and blank
characters but not format characters (Unicode category Cf: zero-width, soft
hyphen, bidirectional controls). A source carrying one was stored as written
while the page drew something else: with U+200B or U+00AD inside the host the
kernel stores a host that is not "api-docs.deepseek.com", and the page's host
line, read through the browser's URL parser, shows "api-docs.deepseek.com";
U+202E in the path reverses the tail in the hover title.
Refuse Cf in the same loop on both sides, the check validElicitURL already
makes for a link a person is shown. The page uses \p{Cc}\p{Cf} so it also
refuses the C1 controls Go's unicode.IsControl already refused.
Raised as a non-blocking note on #13864.
Problem: CodeQL flagged direct path opens in the cancellable checkpoint readers.
Root cause: The normal and torn-tail JSONL readers followed file symlinks beyond
the parent directory supplied by their caller, even though frontend request
authorization is owned separately at the HTTP boundary.
Fix: Open both checkpoints through os.OpenInRoot, retaining cancellation and
recovery semantics while rejecting links outside that directory. Preserve
relative in-directory links. Add a deterministic boundary regression and
bilingual release notes, and separate the sidebar Retry action from its note.
Verification: The escaping-link regression fails before the change and passes
after it. Complete storage/replay/catalog/scheduler suites, focused race and
HTTP boundary tests, 13 frontend tests, TypeScript checking, release-note tests
and repository/Go lint pass. Hosted CodeQL must confirm the new head separately.
An audit found that a fold which keeps the old block used to leave a
removal unannounced (the tombstone was gated on a bookkeeping flag the
fold cleared). Deriving delivery from the visible history fixes it; this
pins that a removed instruction is still announced while the model can
read the old block.
An audit found the same hole through two more entry points: /rewind
truncates the turn that carried the block, and /switch replaces the
transcript with another branch's prefix. Deriving delivery from the
visible history fixes both, so the audit's two providers-boundary tests
now guard that they stay fixed.
A standing projection was settled as delivered when a turn was handed to the
runner, so anything that refused the turn before the provider (an extension
block, a cancel, a failed send) or a /new left the debt settled although no
request had carried the block.
Delivery is now a fact about the model-visible history: owed compares the
newest block of its tag in the visible user records, so a LocalOnly record or
compaction summary never answers for it. A refusal, cancel, fold, rewind or
session rotation changes the answer by itself; nothing is recorded at the
hand-off.
Removed: projectionDebt.{pending,delivered,settle,forget,sent,inherit},
promptFailureDebt.{composed,settle}, settleTurnProjections and runSettled, the
CompactionDone→forget handler, InheritScratchFrom and the per-owner forget
hooks. One-time failure/refusal notes are filtered by visibility instead of
settled, so a turn refused before the provider still owes them.
internal/state/memory guarded its read-modify-write sequences with a
process-local mutex only, so two processes sharing a memory directory could
duplicate a subject key, lose a rename, roll a withdrawal over a newer
revision, or drop an index line.
Take one filelock per directory the store mutates (Dir and GlobalDir, sorted
by canonical absolute path) under the user lock root, bounded by a short wait
that returns a retryable StoreLockedError. The in-process mutex is still taken
first and the file lock second, one order everywhere, so the two cannot
deadlock.
CodeQL flagged the new snapshot parser for stat'ing a caller-supplied path
(store_snapshot_identity.go:44, 'This path depends on a user-provided value').
The parser now takes the name only; loadMemory keeps the stat where it always
lived and fills the missing timestamps itself.
snapshotMemoryRevisionInDir re-read the fact file and filed those bytes
under memory.Revision without checking that the file still carried that
id and revision. Another process publishing revision N+1 in that window
made slot N hold revision N+1's bytes: Restore(id, N) then returned a
body revision N never had, and revision N became unrecoverable. The same
file name re-created with a different id was filed the same way.
The bytes the function already reads are now parsed by loadMemory's own
parser (parseMemory, factored out rather than duplicated) and the snapshot
is refused with the existing "memory revision conflict: expected %d,
found %d" wording when the id or revision no longer match. The check runs
before any slot is created, so a refusal files nothing. SaveWithOptions
and RestoreArchived already return a snapshot error before publishing, so
no caller changes. Single-process behaviour is unchanged: the
process-local mutex makes the mismatch impossible there.
Tests cover the two cross-process probes (refusal plus nothing filed),
archive-and-recreate with a new id, an ordinary save/restore control over
several revisions, and a mutation that removes only the check.
Problem: PR #14086 conflicted with studio after the upstream listing-test repair.
Root cause: Both branches changed the same cache test. Upstream #14097 replaced
timing-dependent large fixtures with direct observation of cached replay, while
this branch added an assertion that derived summaries were persisted.
Fix: Merge studio at 58c80e3f9f, retain the upstream three-conversation corruption
fixture and preview checks, and assert the matching number of persisted entries.
Keep upstream commit history and the independent progressive-loading PR scope.
Verification: Complete sessionstore/sessionv4/historycatalog/backgroundwork
suites, focused cache/cancellation race checks repeated three times, real
resume/tree HTTP effects, 27 frontend interaction tests, and repository/Go lint.
TestV4ConversationsWithoutAListingCacheAreReplayedOnce called a second
listing a replay unless it ran five times faster than the first. A busy
macOS runner measured 411ms against 1.35s and failed a PR that does not
touch sessionstore.
Garble each conversation's log in place after the first listing, keeping
the size and modification time the remembered listing is keyed on: a
listing that remembered returns the same rows, one that replays loses
them. Three small conversations now do what sixty large ones did.
userTexts, the strings a copied command is exempt for because the user
wrote them, returned each user turn's provider-visible Content. That is
the composed turn, with the host's recalled memory, instructions and
policy around the person's words, so a page's command that reached the
turn only through a host block was taken for one the user typed and was
neither counted nor put to the person.
Read each turn through sessionstore.UserMessageText, the definition the
history readers share. This only narrows the exemption.
Problem: The workbench drag change failed the action census and sidebar
text-drop regression in the frontend CI suite.
Root cause: The new dragend listener had no action identity, and the drop
router read the workspace-path payload without checking its advertised MIME
type. Transfers returning unadvertised data could be routed as file paths.
Fix: Register file-drop.finish through listenAction, require the workspace
MIME type before reading its path, and cover plain text and URI-list drops.
Verification: Reproduced the action census and both MIME regression failures
before the fix. All 752 frontend test files and 6027 tests pass, including
77 focused action/drop/intake tests. Typecheck, census, census:verify,
production build, bundle fixture exclusion, gofmt, go vet, make lint,
builtin/boot tests, and make check pass.
Refs #12771
mechanicalFoldDigest, the stand-in when the summarizer cannot digest a
fold, listed each user turn's provider-visible Content. The oldest turns
a fold takes carry the host's date and standing-instruction blocks, so
the 240-character line went on <current-date> and
<project-instructions> and the person's words were cut off, and a turn
of host blocks alone was listed as something the user said.
Read each turn through sessionstore.UserMessageText, the definition the
history index and its snippets already share, and skip a turn with
nothing left.
Add the missing bilingual release-note fragment for PR #14082 so users
can discover that workbench files and folders can be dragged into chat.
Reference the related file-to-chat issue in both languages.
Verification: release-note rendering and Markdown reference tests passed
(12 tests), alongside the required pre-commit checks.
Refs #12771
Problem: the split listing and scheduling change needs its own user-facing release fragment.
Fix: add bilingual notes for progressive summaries, incomplete coverage and foreground priority.
Verification: release-note rendering and Markdown reference tests, make check and git diff --check pass; runtime code is unchanged.
Problem: the user-visible startup behavior had no release fragment.
Fix: document in Chinese and English that ordinary resource reads wait for the authorized connection, as requested in review of #14084.
Verification: release-note rendering and Markdown reference tests pass; make check and git diff --check pass. No runtime code changed.
Problem: Files and folders in the workbench could not be dragged into chat.
Root cause: Explorer and Git rows emitted no path payload, and composer
intake only recognized external files and ordinary text.
Fix: Emit a typed source-workspace path and route it through existing
reference validation. Preserve nested repository prefixes, clear cancelled
drag hints, and keep internal drags outside external-file deduplication.
Verification: Typecheck and 689 focused frontend tests passed, including
native-drop compatibility. Browser drag checks, make check, go vet, make
lint, builtin/boot tests, and both drop-handler Go suites passed.
Refs #12771
Problem: Sidebar discovery waited for full foreign transcript replay while
history indexing competed with interactive session restoration.
Root cause: Complete-list reads owned metadata replay, identity replacement
scanned growing message slices, and derived-data producers lacked shared admission.
Fix: Return explicit progressive coverage, share single-flight summaries and
validated caches, fold replay identities linearly, and retain cancellable scan
generations. Admit background work through one foreground-aware scheduler.
Apply foreground admission at the history route group so independent readers
inherit it without coupling their implementation to background work.
Verification: Storage/scheduler/catalog suites, real Boot/resume and tree/HTTP
effects, sidebar interactions, production frontend build, race checks and lint.
Keep benchmark cleanup registered even when its bounded deadline fails.
Split from PR #14077; no startup fixes or transcript pagination are included.
sessionstore.UserSaidText stripped only the host blocks prepended to a
persisted user turn, while UserMessageText also stripped the blocks the host
appends after the person's words (<execution-policy>, <memory-recall>). The
history index and its snippets therefore indexed recalled facts and the frozen
policy as if the user had said them, so a query for a fact matched a row whose
snippet could not contain it: the same divergence #14022 fixed for the leading
blocks, one definition still giving two answers.
Move UserPreviewText's pipeline down into userAuthoredText and let UserSaidText
add the plan-mode marker strip; UserMessageText delegates to UserSaidText for
both RawContent and Content. The appended cut keeps the existing byte-exact
suffix rule, so a complete block the person quotes at the end of their own turn
is still indistinguishable from a host-appended one and is stripped; the test
pins that limit instead of hiding it.
Bump historycatalog.TokenizerVersion to 4 because the indexed vocabulary
changes again. ensureTokenizerVersion wipes history_fts/documents/sources/roots
when the stored stamp differs and the manager re-registers its roots, so the
next open re-indexes them; Rebuild stamps what it built with. Covered by
TestTokenizerVersionMismatchClearsMixedProjection and
TestRebuildSurvivesTheVersionCheck.
Tests: TestFTSIndexesWhatTheUserSaidNotTheAppendedBlocks and the extended
TestIndexAndSnippetAgreeOnTheUserText / TestSnippetBuildersReadTheIndexedUserText
pins were red before the change. TestAutoRecallNormalTurnIsUnchanged and
TestUserSaidTextQuotedExecutionPolicy are controls, green before and after.
Problem: File delivery requires explicit opt-in even when cards are useful.
Root cause: The user-only configuration key defaults to false.
Fix: Default it on while preserving explicit off choices, read boundaries
and tool exclusions. Keep this policy separate from the settings UI PR.
Verification: Config round trips, complete config/settings/builtin/boot
suites, exact request goldens, schema stability, vet and lint.
The default request fixture gains 143 schema tokens. A newly opened session
may have one cold prefix; this does not mutate the live session's schemas.
Problem: The settings PR also changed the default tool surface.
Root cause: UI discoverability and default-on policy were bundled together.
Fix: Restore the existing default, registration fixtures and configuration
rendering, retaining the searchable user-only switch below Tool approval.
Keep UI wording and route tests independent of a future default decision.
Verification: Settings and transport tests, browser captures, Go suites,
frontend build, vet, lint and repository policy checks.
Problem: Preset modules could only be added one at a time, with two different actions both labeled Add module.
Root cause: A native single-choice picker shared its label with manual blank-ID insertion, and catalog choices did not carry source provenance.
Fix: Add a shared searchable grouped checkbox picker, authoritative catalog provenance, batch selection and parameter-safe draft updates. Keep manual IDs under an explicit advanced entry and document selection semantics.
Verification: 219 frontend tests, typecheck, production build, census, owning Go packages including boot and builtin, focused Desktop preset/composition tests, vet, lint, make check and documentation checks passed. Browser checks covered themes, responsive layouts and save/reopen with MockHub.
Problem: AI file delivery cards required a manual, default-off config edit.
Root cause: present_files had no settings surface or read/write endpoint, and
the config delta renderer treated false as the default.
Fix: Add a searchable user-only switch immediately below Tool approval.
Default the tool on, preserve explicit off choices, and keep registration
changes at session opening. Update wire contracts, documentation and the
provider-visible tool fixtures for the additional 143 schema tokens.
Verification: Config round trips, route grants, UI persistence/search/order,
real boot tool-surface effects, frontend tests/build, Go tests/vet/lint and
browser checks in light/dark wide and phone layouts.
Problem: A redundant arguments envelope makes usable tool calls fail required
field validation and can cause repeated model retries. The current CI security
scan also reports GO-2026-6809 in the pinned Go 1.26.9 standard library.
Root cause: Scheduling, policy checks, execution, and history had no shared
representation of effective arguments inside a schema-invalid wrapper. The
root and Desktop modules still selected the affected Go patch release.
Fix: Unwrap one unambiguous envelope only after full target-schema validation.
Use effective arguments for host execution, evidence, decision barriers, and
surface actions while retaining the original provider call. Select Go 1.26.10
for both modules so CI and packaged Desktop use the patched standard library.
Style the compatibility notice and original-input disclosure like adjacent
tool metadata, with a visible expand marker and keyboard focus outline.
Verification: The envelope regression suites, provider history, permission,
proxy, and interactive surface tests passed. With Go 1.26.10, govulncheck ./...
reported zero reachable vulnerabilities and go vet ./... passed. Frontend type
and interaction checks passed on the rebased PR. The final card style passed
frontend type checking, 16 relevant UI tests, make check, and local browser
inspection in light and dark themes with its disclosure closed and open.
reconcileRoot treated seen_generation<>generation as proof that a transcript is
gone. A row indexed by another process's persist event carries generation 0, so a
conversation indexed after this scan listed the directory was swept health=missing:
it vanished from history search and the incomplete-index warning stayed silent,
because the root's counts still looked complete. Measure absence with stat and
adopt live rows into this generation; only a transcript confirmed gone is swept.
Problem: the release fragment mentioned static HTML support without explicitly
calling out the change from automatic live rendering.
Fix: state in Chinese and English that HTML opens with scripts disabled and
desktop users select Interactive run to execute the page, as requested in review.
Verification: release-note script tests, make check and whitespace validation.
No runtime behavior, dependencies or test expectations changed.
Problem: frontend-next CI failed 25 assertions across action discovery,
orphan detection, HTML preview races and copy feedback consumers.
Root cause: TooltipButton was not declared as an interaction primitive; new
preview controls had no action identities, and the superseded image component
remained orphaned. Neighbouring tests still expected native title tooltips and
automatic HTML execution rather than the explicit Interactive run action.
Fix: certify and name the actual UI inputs, restrict tooltip blur to local
dismissal, remove the unused image renderer, and align consumer tests with the
new contracts while retaining copy failure/retry and browser race coverage.
The unnamed-input ceiling falls from 218 to 217 rather than being relaxed.
Verification: full frontend suite (749 files, 6010 tests); supplemental action,
tooltip and browser guards; typecheck and production build; go vet, make lint
and make check. No workflow skip, timeout increase or alert suppression.
Problem: delivery-card Add to conversation bypassed the workbench's reference
validation, allowing names with token boundaries or MCP ambiguity into drafts.
Root cause: Pane independently formatted @ references from display paths.
The kernel grammar preserves literal backslashes and only escapes space/tab,
so generic backslash doubling would change the selected file.
Fix: share one grammar-specific formatter, use structured relative paths for
workspace cards, disable unrepresentable references while keeping preview and
path copying, and remove the unused intake refToken helper. Keyboard navigation
and initial focus skip disabled menu actions.
Verification: 170 related frontend tests, production build/typecheck, real
browser insertion, kernel backslash round-trip and scoped-resolution tests,
go vet, make lint and make check. Four new rejection cases fail before the fix.
Problem: the presenter was registered for every install, and review requested
default-off model behavior, repository screenshot removal and PDF hardening.
Root cause: present_files used the unconditional builtin path rather than the
existing user-scoped conditional registration and provider-surface mechanism.
Fix: gate registration with tools.present_files, protect user scope across
project merges, preserve the setting on save, and expose the tool only after
opt-in. Restore the unconditional schema budget and remove PR screenshots.
Document PDF.js 6's removal of eval/isEvalSupported, keep scripting disconnected
and explicitly disable XFA rather than passing an unsupported no-op option.
Verification: boot.Build direct/proxy off/on/forbidden/disabled/excluded effect
matrix; user-scope and save/reload tests; document tests and typecheck; real
browser PDF navigation; go vet, make lint, make check and release-note tests.
Record the user-visible preview formats, responsive delivery cards and refresh fixes for PR #14031. Verified with make check and git diff --check; no runtime behavior changed.
Problem: AI-created documents could not be handed off as validated, durable
file cards, and binary files could not be read through the text editor route.
Root cause: delivery identity lived only in ordinary tool output, while file
reading, view selection and auxiliary actions lacked a shared preview path.
Fix: add a bounded root-confined reader, Controller and raw-byte preview APIs,
and host-confirmed present_files results projected through live and stored
history. Add lazy PDF, worker CSV and sandboxed HTML viewers, responsive file
cards, type icons, current-file state and accessible shared tooltips. Preserve
dirty drafts while surfacing deletion/revocation and avoid metadata reads per
keystroke. Keep the current workbench request-consumption contract intact.
Verification: root and desktop Go tests, go vet, make lint/check, frontend
typecheck, 498 related tests and production build. Browser checks cover PDF
navigation, CSV literals, menus, request consumption and responsive themes.
The builtin schema inventory grows by 143 measured tokens; boot effect tests
verify stable schemas within a turn for direct and capability-proxy calls.
Cause: candidateTexts.of loaded a candidate's transcript only to build a
snippet. When that load failed it set failed[path] and returned, dropping the
candidate with no error, no repair, and no status change. The row's ledger
digest and size/mtime still matched, so Indexed == Total kept certifying the
projection complete and the history tool answered "No saved session history
matched ..." for a query the session did match.
Fix: record a deterministic read failure with the shape the indexer already
uses for the same fault. RecordReadFailure flips the row from ok to corrupt
with last_error (existing columns), publishes the revision so Status reports
Indexed < Total, and re-enqueues the source. The periodic scan (#13923: a
non-ok source is always revisited) then re-reads it, so a restored mode heals
without a save.
Transient failures are not distinguishable from permanent ones at this layer,
so every load failure is recorded; retries stay bounded because a row that is
already corrupt is not re-queued per search, and the scan retries it once per
interval (five minutes by default). No wording, persisted field, schema, or
tokenizer version changed.
Tests: the audit's red test now passes (the tool prefixes the existing
"history index is still building" notice and status reports Failed 1,
Indexed 0/1); a readable control that genuinely matches nothing still answers
no matches with no alarm; and after chmod is restored the next ReconcileRoot
scan serves the content again. Mutation check: reverting only the fix leaves
the first and third red with the quoted false claim.
The FTS projection indexed raw user message Content while every display path
stripped the host's prepended blocks (<response-language>, <current-date>,
<project-instructions>, <available-skills>, <workspace>): a query for a
block-only phrase matched a row whose snippet could not contain it.
Move the strip into sessionstore.UserSaidText, the one definition of what a
user turn says, and call it from both sides: the indexer
(historycatalog/documents.go) and the snippet builders (history/search.go,
history/indexed.go). The history package's private stripComposePrefixes is
gone, so the two cannot drift again.
Scope is today's snippet behaviour: the leading blocks stripComposePrefixes
took, plus the plan-mode marker. Trailing <execution-policy>/<memory-recall>
stripping is deliberately not added; whether "the user said" also excludes
host-appended blocks is a product decision left for the PR body.
Bump historycatalog.TokenizerVersion to 3 because the indexed vocabulary
changes. ensureTokenizerVersion wipes history_fts/documents/sources/roots when
the stored stamp differs, and the manager re-registers its roots, which
re-indexes them; Rebuild stamps the version it built with. Covered by
TestTokenizerVersionMismatchClearsMixedProjection and
TestRebuildSurvivesTheVersionCheck.
Tests: TestFTSIndexesWhatTheUserSaidNotTheHostBlocks (red before the change),
TestIndexAndSnippetAgreeOnTheUserText, TestIndexKeepsPlainUserTextUnchanged,
TestSnippetBuildersReadTheIndexedUserText.
Problem: selecting a custom preset captures unused skill templates and
extension packages and waits for optional MCP startup.
Root cause: resource snapshot creation also served as the runtime permission
model, making selection cost scale with installed resource payload.
Fix: add live capability-grant bindings while preserving legacy snapshots.
Resolve current skill/MCP/extension/hook/command authority at use, defer
optional startup, retain stable provider contracts, and fence new event
and archive formats from older readers. Use lightweight status summaries
and per-instance extension call leases for safe concurrent replacement.
Verification: root and Desktop suites, full boot/builtin suites, affected
storage/controller/frontend tests, lifecycle race checks, frontend interaction
tests and typecheck, lint/vet/check, actionlint, and Windows cross-compilation.
Twenty-sample real switch measurements include build, persist and publication;
64 MiB unused templates remain outside the binding and stay below the local
500 ms P95 target. Native Windows execution is delegated to hosted CI.
#13954 let /resume delete a saved session with a confirming second
Ctrl+D. The merge of #13755 (d87d0d959) reverted it: its branch's
"Merge studio into a5/schedule-api" (ba48dfe0b) resolved files only
studio had changed to the branch's older copies, taking out the picker
code, its tests, the i18n catalogue and the release note.
Restore #13954's eleven files to their content on the studio side of
that merge; none has changed since.
#13956 gave user ! commands the login shell's PATH through
shellrun.CommandEnv, shared with the bash tool. The merge of #13792
(ac1a737ee) reverted it: its branch's "Merge studio into
feat/13111-origin-ask" (ed1dfebb9) resolved files only studio had changed
to the branch's older copies, deleting shellrun/loginenv.go, the Env
line in RunShellWith, the regression test and the release note, and
moving the probe back into bash.go. A ! command again ran with only the
host's PATH, so tools from nvm or Homebrew were not found when Studio
was started from a launcher.
Restore the nine files to their content on the studio side of that
merge; none has changed since.
Studio now requires every config key to be declared once with who may change it
and when it takes effect; the two keys this branch adds were not in that table,
so loading a config panicked with `setting.undeclared: network.dns`.
Both are `Guarded` / `OnOpen`, beside `network.proxy_url`: which resolver a
request goes through is a network boundary, so the model must not change it by
itself, and a new value applies on the next start rather than mid-session.
Also rebased onto studio, which the same push asked for.
Problem:
The native Windows link gate fails when capturing a junction source with
an internal junction, despite the ordinary symlink cases passing.
Root cause:
filepath.EvalSymlinks can retain a Windows junction alias and fail when
walking its child. It is not the repository's physical-identity resolver.
Fix:
Use fileutil.ResolveExistingPath for source roots, contained entries, and
single-file hook parents. Preserve containment checks, rooted content reads,
strict retained-directory verification, and filesystem error identities.
Verification:
Local source-capture and composition regressions and Windows amd64 test
compilation pass. Native verification remains owned by the Windows
composition-links gate, including the previously failing junction case.
The Windows runner's Git Bash has no shasum, and the grader's
`shasum -c ... || exit 1` turned "command not found" into the same exit 1 as
a changed file, so the reference solution failed there with nothing printed.
The SHA-256 manifest is now checked with hashlib inside the grader's Python,
over raw bytes, and a mismatch names the files that differ.
gradeSeed now carries the grader's output in its error: the CI log said only
"exit status 1", which cannot tell a missing tool from a failed check.
Verified locally: on a PATH without shasum the reference solution grades
clean; a CRLF Makefile and an edited Go file each fail and are named; the
pristine seed still fails. snapshot.sh --check, the corpus tests and
make check pass.
Problem:
The Windows smoke suite did not include agentpreset's snapshot tests and
symlink fixtures could be skipped without proving the reported platform.
Root cause:
The platform package list omitted agentpreset, and no fixture exercised
directory junctions. One portable assertion assumed Unix executable bits.
Fix:
Add native junction capture/restore, hook bundle, escape, cycle, and retained
snapshot rejection cases. Run nine focused source-link tests on Windows and
fail the gate for skipped or missing cases. Compare retained permissions to
the actual source filesystem mode.
Verification:
agentpreset and testenv suites, Windows amd64 test compilation, go vet,
make lint, make check, and actionlint pass locally. Native Windows execution
is explicitly pending the hosted composition-links step.
Problem:
Saved personas can fail when selected with inherited skills, extensions, or
hooks. MCP startup failures lack structured server identity and guidance.
Root cause:
Ordinary discovery accepts linked sources but composition snapshot capture
rejects them. MCP startup errors carry the server only in prose instead of
the module metadata consumed by the localized refusal boundary.
Fix:
Flatten contained source links through rooted reads, retain strict snapshot
verification, and preserve module identities and sanitized failure causes
through HTTP and Chinese/English rendering. Keep required MCP failures
blocking and preserve the original session on a failed selection.
Verification:
Full root Go tests, go vet, make lint, make check, frontend typecheck, and
198 relevant frontend tests pass. Browser fixtures cover failure, retry,
management access, and light/dark wide/phone error presentation. Regressions
cover source deletion and restart, prefix stability, revocation, containment,
snapshot tampering, and credential redaction.
The e2e fixtures finish in 5-15 requests, while a real task in this tree took
deepseek-flash 38. A change aimed at cutting round trips cannot show up on
the short suite, so this adds four long tasks whose workdir is a git archive
of commit 42636ddcb (pricing, ablation, agentgraph and five stdlib-only
leaves) plus a trimmed REASONIX.md, a Makefile, a module README and a small
benchctl consumer:
- rt-cache-write-rate: a CacheWrite rate threaded through cost, fingerprint,
card identity and the catalog, plus an Anthropic row that uses it
- rt-stale-skills: Markdown-only repair of three project skills with no
recognised verifier, the shape behind #13293's lingering stale_verification
- rt-rate-lookup-signatures: five lookups moved to (RateCard, bool),
CatalogKey and ModelRef across about 20 call sites, most in _test.go
- rt-plan-ablation-module: a five-step plan-first task signed off per step
snapshot.sh rebuilds every workdir from the pin and --check detects drift.
One pinned test compares CheckedOn with the wall clock and would fail every
workdir from 2026-11-20; base/ anchors it at the snapshot day instead.
Each task ships a solution/; the corpus is registered with the authoring
guards, which check that every seed fails its grader and every solution
passes. A deepseek-flash baseline (2 trials) solved 8/8 at 13-31 requests
once two grader false negatives in rt-stale-skills were fixed; the README
records it, along with a multi_edit prompt arm and a complete_step
description arm that did not move the request count.
The guard in perf/branch-alignment.mjs was not listed in the Studio workflow, so nothing ran it; the fix itself is user-visible and carries a fragment.
The unavailable refusal carried the module and detail only inside a nested
diagnostics list, so the window filled a generic sentence from a code alone.
Flat module/detail params now cross the boundary and both languages use them.
StartForSession called m.wg.Add(1) without a lock while Close could already
be inside m.wg.Wait() after cancelling the root: Add racing Wait at a zero
count is the misuse sync.WaitGroup documents. Under
`go test -race -run TestStartRacingClose -count=40 -cpu 1,2,8` on studio it
reports a data race and panics with "WaitGroup is reused before previous Wait
has returned".
The launch moves into launchJob, which decides under m.mu, the lock both
Close paths now cancel under, whether the job joins the wait group or is
refused. A refused job never runs: it ends Interrupted, its metadata is
written, and it is recorded and published like any other completion, so a
task recorder that saw it start sees it end. The goroutine body moves
unchanged.
Split out of #13947 at the reviewer's request so the jobs manager is
reviewed on its own.
Dirs resolves the path on the far machine and lists it; when the listing
fails but a stat says the path is a directory, the answer is the directory with
Unlisted set instead of an error. The picker then names why the rows are
missing and 'use this folder' works on the confirmed path. A path that is not
there, or not a directory, stays an error. Listing a directory this account
cannot read is fs.ErrPermission on the script layer, as it is on SFTP.
Probe took SFTP before any other step, so a server that refuses the subsystem
ended the test right after 'connection OK'. With the script layer behind
Client.Files it now completes, and the report carries sftp or exec.
pkg/sftp reports the refusal as an unnamed error string, so the subsystem is
now requested in sftpfs.New and a server reply of 'failure' becomes
ErrSubsystemRefused. Client.Volume prefers SFTP and, only on that sentinel,
serves the connection from execfs over the exec channel and remembers the
answer until the connection is replaced. Any other failure (dead link, refused
channel) is returned unchanged. The exec runner quotes the script and every
argument whole into one /bin/sh command line.
The WSL file layer was a fixed set of /bin/sh scripts taking every path as a
positional argument. It is now a package of its own that takes a runner, so
another transport can reuse it. Scripts name a missing program by exit status,
refuse a file over the transfer limit by type, and check the byte count before
publishing a write. List, Download and UploadAtomic are added, and the shared
scenario now covers names with quotes, spaces, a leading dash, newlines, shell
metacharacters and a path over 3000 bytes.
`flexString` existed byte for byte in the openai, gemini and responses packages.
One type written three times diverges at the next edge case, so it moves to
`internal/base/flexstr`, which is a leaf (it imports nothing under reasonix) and
is declared in `tools/repolint/layers.go` so the layering gate allows every model
package to use it.
No wire behaviour changes: a string code, a number, an absent field and null read
exactly as before.
Problem: The CUA readiness change expands #13720 beyond its reviewed
launcher-ownership scope.
Root cause: The native CUA startup race was repaired alongside the
launcher conflict resolution, despite having a separate runtime owner.
Fix: Revert cad3c58f8a on this branch.
The CUA fix and regressions are preserved on fix/cua-socket-readiness,
based directly on studio. Keep the original launcher fix and conflict
resolution unchanged.
Verification: The resulting tree exactly matches e67fe2ce96. Electron
tests pass (189 passed, one existing platform skip), as do go vet,
make lint, make check, and the builtin/boot package tests.
Document the user-visible startup and restart fix from #13985 in the unreleased Studio notes, in Chinese and English. Verified with make check and the Studio release-notes renderer tests.
Problem: Embedded CUA startup can fail while a valid driver is still
initializing its socket permissions.
Root cause: The driver binds its socket before chmod sets 0600, while the
host treats the intermediate permissions as a terminal startup error.
Fix: Wait within the existing readiness deadline, preserving socket-type
and identity checks and forbidding metadata requests before private
permissions are observed. Cover initial startup and abort replacement
through the shared launch loop.
Verification: Deterministic permission-ordering and deadline regressions,
file/symlink rejection, 188 passing Electron tests with one platform skip,
native macOS CUA acceptance, go vet, make lint, make check, and builtin/boot
package tests. Split from #13720 at the maintainer's request.
- The native bridge answers only the kernel's page and the one computer the
person scanned a code for; `openInApp` loads an origin once, and only that one.
- Non-web URLs are routed only for mailto: and tel: — an intent:// URL can start
any exported activity.
- The `reasonix://` filter that nothing handled is gone.
- A release with no key is unsigned instead of signed with the debug key, and the
debug build no longer wears the release key.
- The Gradle wrapper is pinned by checksum and the mirrors are out of the
repository.
- The scripts find the kernel and the page at the repository root, speak English,
and copy LICENSE from it instead of committing a second copy.
- The README is what a builder needs, and states what the permissions cost.
Problem: Native macOS CUA acceptance can reject a valid driver startup with
"Invalid private CUA socket".
Root cause: The pinned driver binds its socket before chmod sets 0600.
The host treats the intermediate mode as a terminal error. The previous
fixture creates its socket with private permissions immediately, hiding
the production ordering.
Fix: Treat non-private socket permissions as pending readiness within the
existing deadline. Keep non-socket rejection and identity validation, and
never request metadata until private permissions have been observed.
The shared launch loop also covers abort-triggered replacement.
Verification: Deterministic regressions force observation before chmod on
initial launch and restart, and cover deadline expiry, child cleanup, files,
and symlinks. Both permission regressions fail before the fix. Electron
tests pass (193 passed, 1 existing skip), as do native macOS CUA acceptance,
go vet, make lint, make check, and the builtin/boot package tests.
- The native bridge answers only the kernel's page and the one computer the
person scanned a code for; `openInApp` loads an origin once, and only that one.
- Non-web URLs are routed only for mailto: and tel: — an intent:// URL can start
any exported activity.
- The `reasonix://` filter that nothing handled is gone.
- A release with no key is unsigned instead of signed with the debug key, and the
debug build no longer wears the release key.
- The Gradle wrapper is pinned by checksum and the mirrors are out of the
repository.
- The scripts find the kernel and the page at the repository root, speak English,
and copy LICENSE from it instead of committing a second copy.
- The README is what a builder needs, and states what the permissions cost.
- The native bridge answers only the kernel's page and the one computer the
person scanned a code for; `openInApp` loads an origin once, and only that one.
- Non-web URLs are routed only for mailto: and tel: — an intent:// URL can start
any exported activity.
- The `reasonix://` filter that nothing handled is gone.
- A release with no key is unsigned instead of signed with the debug key, and the
debug build no longer wears the release key.
- The Gradle wrapper is pinned by checksum and the mirrors are out of the
repository.
- The scripts find the kernel and the page at the repository root, speak English,
and copy LICENSE from it instead of committing a second copy.
- The README is what a builder needs, and states what the permissions cost.
The kernel-side pieces this branch came with are their own PRs now, so this keeps
only `android/`. Rebasing onto studio also drops the mailbox delivery code and the
mailbox credentials that the review asked to remove.
Problem: PR #13720 conflicts with the current studio branch in the Electron
unit test suite.
Root cause: Both branches inserted independent regression tests at the same
location after the host startup failure tests.
Fix: Preserve the launcher ownership, relaunch handover, and closed-console
tests together with studio's credential-cookie restoration test. Retain the
automatically merged shell changes and updated Electron dependencies.
Verification: Electron unit and Linux packaging tests passed (189 passed,
1 existing skip). Formatting, go vet ./..., make lint, make check, and the
builtin and boot package tests passed.
TestEffectTruncatedSummaryOnPressureKeepsCodeAndRetries and
TestEffectModelSwitchKeepsTheCompactionProjection pin what happens turn
by turn around the compaction trigger, but the turn the trigger falls on
depended on how large the first request happened to be. Measured on
studio: about 550 more characters of prompt or tool schema flip the first
to "7 failed summaries" and the second to "switch spent 1 summarization
call", with no change to compaction. So any prompt or tool-description
edit, or a host whose tool surface is a few hundred characters larger,
failed them, and a change could pass on one runner and fail on another.
Each fixture now builds once, measures its first request the way the
prompt estimator does, and pads its system prompt up to a fixed size, so
the trigger and pressure turns are the fixture's. The sizes sit in the
middle of ranges where the outcome does not move: 30000-40000 characters
for the truncated-summary fixture (6 attempts throughout), 40500-48000 for
the model-switch fixture (3 folds before the switch, none after). With
0-5000 characters of extra first-request weight injected ahead of the
measurement, both now give the same result; before, both flipped near
550. The bounds are unchanged.
waitForDone returns on the turn-done event, which the controller emits before it drops its running flag, so ClearSession could answer "cannot clear while a turn is running". Under 8 parallel race-enabled processes at one CPU the unchanged test failed 3 times in 1200 runs; with waitIdle it failed 0 times in 1200.
Windows has a backend now, so Available() is true, but it enforces only after the
user turns on windows_integrity. Tests and the e2e bench that ask whether a default
configuration is confined use ConfinesByDefault (false on Windows), and a caller's
override to enforce (an observed run asks for one) is honoured on Windows only
when the user opted in, since enforcing edits permissions on their folders.
A relocation moves only the entries StateRootEntries names. The grant record sat in
sandbox/ outside it, so a move would have left the record behind while the
permission entries it lists stayed on the folders, with nothing to take them back.
The restricted token now drops every privilege but the traverse check, so backup
semantics cannot write outside the roots even from an elevated session. A SID is
synthetic only if no account resolves it. The launcher refuses to run under a
restricted token (a command's own launcher inherits it), grants only session temp
folders, hands the child just the standard handles, runs it in a job with UI
limits, and the default DACL and the grant mask are narrower. Links are not
followed at grant time and a link root is refused. REASONIX.md, the guide, the
Settings text and the release note list what is and is not bounded.
The host already confines bash's $TMPDIR to a per-session directory, the
file tools resolve a leading $TMPDIR to it, and the host deletes it when
the session ends. The model was never told the last two, so it verified
with `scratch=$(mktemp -d); ...; rm -rf "$scratch"` or wrote tests to /tmp,
and the host refused them (shell.delete_nonliteral, shell.delete_sequence,
write scope): 26 refused calls over 36 deepseek-flash benchmark runs, one
model round each.
A <session-scratch> turn block now says: put throwaway files at
`$TMPDIR/<name>`, spelled that way in bash and in the file tools, and do
not delete them. The host path is deliberately not projected: under bwrap
bash sees the directory at /tmp, and on Windows a backslashed literal does
not survive a bash command line, so $TMPDIR is the one spelling every tool
resolves. The block follows the dateAnchor/projectionDebt pattern: it rides
the turn and never the prefix, settles only when a turn reaches the runner,
is re-owed after a fold, retracts itself once if the directory can no
longer be created, is projected only for a manager the assembly supplied,
and is inherited across a rebuild. Its bytes carry no per-session value,
so two sessions over the same state project identically.
No shell-safety rule changes; the effect test asserts that a recursive
delete through $TMPDIR or a mktemp variable is still refused.
supersedeStandingState judged only the fold region's kept turns, so a
standing block the region held once survived beside newer copies in the
recent tail. A fold whose region kept a single user turn dropped nothing:
TestEffectFoldDropsSupersededStandingState went from 3 <workspace> copies
to 4 across such a fold once a turn block shifted the fold one turn
earlier. partitionFoldWith now takes the tail, and the tail's user-turn
blocks are marked seen before the kept slice is walked newest-first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every CompactionDone forgot what the model had been told -- the date,
the skills listing, the project instructions and rules -- so the next
turn sent them again. A truncated or refused digest also emits
CompactionDone, with Messages == 0: nothing was summarised and the turn
that carried those blocks is still verbatim, so re-sending them only
grew the prompt the next attempt had to fold, by one skills listing per
failed fold.
The producer already says which case it is: a successful fold reports
Messages = len(fold) and emitCompactionAborted reports 0. The sink now
reads that instead of treating every completion as a fold.
TestEffectTruncatedSummaryOnPressureKeepsCodeAndRetries goes from 5 to 6
failed summaries over its 10 turns: the prompt no longer grows by a
re-sent listing after each failure, so the pressure fold lands a turn
later. That is inside the test's bound of 6 and is the growth the retry
gate is meant to read.
Inside run_script, call("read_file", ...) returned the model-facing form:
every line prefixed " N→", CRLF folded to LF, the default 2000-line page,
and — past 32 KiB — a window with a "read on from here" marker. A script
that read a file, edited the text and wrote it back put all of that into
the file. With deepseek-flash on migrate-signature-3packages and
agent.code_mode on, one bulk rewrite corrupted 36 files and cost ~15 rounds
to repair.
Two layers each rendered for the model a result whose reader is a program:
- read_file: a call another tool made (tool.IsNested) gets the decoded text
verbatim — line endings and final newline kept, no default limit, offset
and limit selecting whole lines. The model's own read is unchanged.
- nestedInvoker: the calling tool reads what the tool returned, not the
bound fitted to the model's context. The nested card still shows the
bounded form; toolOutcome.keepFull records both originals in one place.
write_file already preserves the existing file's encoding, so a BOM file
round-trips once the read stops adding to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
model_rounds and round_outcomes treated every call run_script made as a
top-level dispatch, so each inner result reopened a model gap and each inner
dispatch closed one. One recorded code_mode run with 24 executor requests
reported a median of 87.5 rounds. Nested calls carry no ParentID — only an id
derived from the caller's, inside its dispatch→result window — so the digest
had nothing structural to tell them apart.
The nested id is now declared in internal/contract/tool (NestedCallID /
IsNestedCallOf) and the agent's nestedInvoker builds ids through it, so the
producer and the reader share one definition. e2ebench's callNesting tracks
open top-level calls and treats subagent calls (ParentID) and invoker calls
(derived id) alike: neither cuts a round, joins a batch or adds tool time.
roundEnds and splitAtCorrect use the same tracker. NestedCalls reports how
many there were, and ModelRounds' doc now says what it counts.
With -trials N every trial wrote <task>.trajectory.jsonl, so only the last
survived and every trial's digest read it. Trials now write
<task>.trial-N.trajectory.jsonl (legs: <task>.trial-N.segM...); a single
trial keeps the old name. sft mode resolves the task id through
trajectoryTaskID, which strips trial and leg marks; traj, serve and barrier
modes glob *.trajectory.jsonl and label each file by its stem, so trials
read as distinct entries.
Cause: the digest had no identity for tool-made calls; the trajectory path
had no trial in it.
Blast radius: cmd/e2ebench digest and file naming; nestedInvoker's id string
is unchanged (same "<parent>.<n>" format, now built by the contract helper).
Neighbouring behaviours tested: subagent ParentID exclusion, segment paths,
answer-isolation audit across legs, existing round decomposition tests.
Why this layer: the id convention is owned where nested calls are created;
the digest reads it rather than restating the format.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: Windows CI rejects the retained-hook source-deletion fixture as a
credential-bearing launch before reaching its restoration assertions.
Root cause: credential projection scans the complete quoted shell string as a
generic value, splitting a Windows drive colon inside a quoted file argument.
That creates an unmatched quote and falsely marks the command as sensitive.
Fix: reduce shell launches to static arguments before credential inspection,
preserve exec and legacy argv launches, and continue refusing literal token and
header arguments or shell forms that cannot be reduced safely. Keep source
commands and retention/restoration semantics unchanged.
Verification: regression reproduces both quoted Windows path failures before
the fix, while exec/legacy argv and token/header refusal cases stay covered.
Focused retention/security regressions and their race run pass; full builtin
and boot tests, gofmt, vet, lint and changed-file checks pass locally.
Problem: CodeQL reported new path and allocation alerts, and Windows repeatedly
failed the retained-hook restoration test.
Root cause: Checked selectors were still passed onward as raw request values;
some generated paths and capacity hints did not expose their bounds at the IO
owner. The HTTP DTO also carried an internal inherited binding. The hook fixture
fed Windows backslashes through Bash resource parsing.
Fix: Resolve import/export selections to registered values, forward checked
relative names for resume/migration, and guard the generated session slug before
joining it to state storage. Use rooted declaration/resource reads and copies,
bound resource reads, and avoid unchecked capacity addition. Pass inherited
bindings outside the wire DTO. Normalize and quote the hook fixture path, including
spaces, while retaining source-deletion and exact-binding assertions.
Verification: Hosted alert locations reproduced locally with the path-injection
and allocation queries; before/after query results and the base scan are retained
separately. Boundary regressions fail on the original head and pass with the fix.
Owning HTTP, store, config, hook, sidecar, plugin, specification and encoding suites;
full builtin/boot tests; gofmt, vet, lint and changed-file checks. Hosted Windows
and CodeQL results remain required for exact-head acceptance.
Problem: The composer exposed the new mode picker to users who had not opted in,
contrary to the maintainer's product-visibility decision.
Root cause: Removing the persistent visibility preference also removed its
settings/search action and the RunLine mounting gate.
Fix: Restore the original on/off storage key and default-off behavior. Add the
switch and concise explanation to Persona management, restore settings search
and the action, and gate both mounting and catalog reads. Preserve card defaults,
catalog/editor design, persona copy and current conversation bindings. Failed
storage writes retain the previous choice and report an error.
Verification: Missing/disabled/unknown and toggle regressions fail before the
fix. Focused Chinese/English navigation, persistence, storage-failure and binding
tests pass. Full frontend: 729 files / 5,857 tests; build/typecheck, UI census and
ten identical fixture reports; gofmt, vet, lint, make check, builtin and boot
suites. Browser visual acceptance remains unavailable under the URL policy.
Problem: Built-in preset names and descriptions read like configuration modes
rather than collaborators, and their relationship to Harness prompts was unclear.
Root cause: Technical copy was reused across the catalog, composer and persona
editor without explaining the existing short-identity prompt adaptation.
Fix: Rename the built-ins to Everyday partner and Lightweight partner, update
their bilingual descriptions and guidance, and derive the unbound picker label
from its canonical catalog definition. Keep IDs, persona bytes and selections
unchanged, retain historical translations, refresh revision metadata and document
the pinned official Harness reference and native prompt boundary.
Verification: Frontend 729 files / 5,852 tests and production build/typecheck;
contract, store and HTTP suites; gofmt, vet, lint, changed-file check, builtin and
boot suites. Fix a discovered worktree test race by waiting for the asynchronous
checkout state instead of the creation title shared by initial and checkout
phases. Browser visual acceptance remains unavailable under the URL policy.
Problem: Built-in presets use flat rows, choosing a future task mode requires
repetition, and persona fields are hidden behind an advanced disclosure.
Root cause: Management navigation and new-task launch intent have no shared
explicit preference; the editor groups basic persona fields with identifiers.
Fix: Match the selected two-column card hierarchy with badges, IDs, descriptions
and separate details, usage and copy actions. Persist an explicit built-in card
choice in a new machine-local key and inject it through the local/remote open
owners only for new tasks. Explicit request choices override it; restoring
history keeps its binding. Existing server preferences alone remain inert.
Expose both persona textareas, runtime context, scope and ID directly, rename
settings navigation to Persona, and keep literal persona fields authoritative.
Verification: Full frontend suite plus real MockHub launch effects, blank-pane
reuse, local/remote history, explicit overrides, storage compatibility/refusal,
cross-window refresh and independent footer actions. Production build/typecheck,
UI census/fixtures, format, vet, lint, changed-file and builtin/boot checks.
Browser-rendered visual comparison remains blocked by the URL policy, as
recorded in design-qa.md.
Problem: "After the first-party guidance" exposes prompt-construction jargon
without explaining what users should enter.
Root cause: Both persona editors use a position-based label; the composition
editor has no suffix helper text.
Fix: Label both suffix inputs "Additional instructions" and explain that the
text follows the built-in system instructions and may be left blank. Add the
Chinese/English copy and associate the helper with each textarea. Keep the
existing literal suffix binding and update affected interaction expectations.
Verification: 24 focused persona, preset and localization tests; production
build/typecheck; formatting, vet, lint, changed-file checks and builtin/boot
suites.
Problem: The catalog's create icon and label stack inside a cramped button.
Root cause: Studio icons render as blocks while the button lacks a horizontal
layout; the title and description also share a flex item beside the button.
Fix: Give the button an inline-flex row with no wrapping, a 36px minimum height
and 16px horizontal padding. Align title and action in one grid row and place
the description across the next row, retaining the primary theme colors.
Verification: 27 focused frontend tests, production build/typecheck, formatting,
vet, lint, changed-file checks, and builtin/boot suites. The local preview stays
running; browser observation remains blocked by its URL security policy.
Problem: Preset browsing, creation and the Standard persona were mixed in one
long Settings page, hiding the save flow and exposing configuration too early.
Root cause: Catalog selection and editable drafts shared one inline surface,
without an owner-level guard for Settings navigation.
Fix: Follow the selected full-content editor reference. Group the catalog by
scope; open a separate editor with common fields first, expandable capabilities
and advanced options, and a persistent Cancel/Save footer. Preserve literal
persona bytes, revision checks, import/export/delete and session bindings.
Guard dirty exits across return, Settings close, category and search paths.
Keep pending saves mounted and reject stale import results. Reusable switches
declare button type so toggles cannot accidentally submit the editor form.
Verification: Full frontend suite: 727 files, 5843 tests; focused editor,
navigation, pending/refused save, import ownership and binding regressions;
production build, typecheck, UI census and fixture verification; root format,
vet, lint, changed-file checks and builtin/boot suites. Browser visual acceptance
remains blocked by the local URL security gate and is recorded in design-qa.md.
Every open tab keeps its pane mounted, and a hidden pane was only
visibility:hidden, so it still took part in layout. The rail width is animated
per frame, so each frame laid out every pane's transcript again. With three
panes the median frame at 4x CPU slowdown went from 94 ms to 18 ms.
Problem: the mode picker required a separate settings toggle before users
could discover or use it.
Root cause: both the picker and its run-status row depended on a persistent
visibility preference unrelated to the conversation's selected mode.
Fix: render the picker directly, remove the retired setting, preference state,
search entry and action, and update the bilingual documentation. Standard
remains the default; displaying the catalog never selects a composition.
Verification: failing-first tests for absent and disabled legacy preferences;
default Standard with a saved Minimal preference; picker, settings, remote-open
and settings-search interaction suites; frontend typecheck, full suite, census,
fixture verification and build; browser preview; go vet, make lint, make check,
builtin tools and boot assembly tests.
Problem: Publishing Studio did not regenerate its website changelog.
Root cause: Product publication and the website deployment had no explicit handoff, and Pages only accepts runs from the website branch.
Fix: Dispatch the website workflow after stable publication with the required version and a unique request ID. Wait for that deployment and propagate failures. Register dispatch on the default branch without enabling deployment from it, and use the workflow commit when recovering old tags.
Verification: All 13 publication tests, shellcheck, signing workflow contracts, go vet, make lint, make check, and the required builtin/boot suites pass.
Problem: the custom-preset form grouped storage scope and ID under one label,
leaving their purpose unclear.
Root cause: two independent controls shared the Preset ID field container
without visible labels or usage guidance for each control.
Fix: give each control its own label and short translated description, and
associate the descriptions with the controls for assistive technology.
Verification: AgentPresets interaction tests, frontend typecheck, production
build, census and fixture verification; browser layout and console checks;
go vet, make lint, make check, builtin tools and boot assembly tests.
Problem: PR #13613 conflicted with the current studio branch.
Root cause: both branches extended session status, ACP configuration, and
project dispatch while the shared stylesheet and session-new function reached
their existing size limits.
Fix: retain composition bindings alongside tariff status, apply the ACP
approval ceiling, and reuse pending dispatch panes without dropping inherited
composition bindings. Move the picker styles to their own sheet and compose
the ACP config projections without increasing the ratchet budgets.
Verification: frontend typecheck, 5,830 frontend tests, production build,
census invariants and fixtures; go vet, make lint, make check; ACP, HTTP serve,
composition contracts/storage, session storage/control, delegation, builtin
tools, and boot assembly tests.
Merged studio 3d9f9ff74c into PR head
ae6a9de150.
The kernel refuses every request without it, and the cookie belongs to the
host rather than the port, so a removal left the window refused until a
relaunch. The shell holds the token and puts it back, logging the cause.
.gitattributes pins *.go to eol=lf but not *.golden, so a Windows checkout turns
the byte-compared goldens into CRLF and TestCompareOutputUnchangedWithoutUnaccountedRuns
fails there. Pin *.golden to eol=lf as well.
The macOS tray loaded the full-colour tray.png, so the menu bar showed the
whole app badge. A Template-suffixed black-on-transparent pair lets macOS tint
it for light and dark bars; Windows and Linux keep their files.
aggregateArm dropped an unaccounted run's wall before adding it to
s.WallMs, so compare's Solved/hour and global wall/task divided by a
smaller wall than the suite report uses for the same -json artifact:
report.go adds every run's wall, and a killed run's wall is measured
unconditionally in main.go (only its tokens never landed). The
per-class accumulator ran before the skip, so one section printed two
populations, and the sign of "which arm is cheaper" could flip.
WallMs is now the all-run wall and AccountedWallMs the accounted
subset: Solved/hour and wall/task divide the all-run total, while
"Wall seconds / solved" keeps dividing accounted wall by accounted
solves because that denominator is solved runs. No new persisted
state, and neither how Unaccounted is decided (segmentrun.go) nor the
suite report changes.
This is the residual of #13446 applied to a field that is NOT
unmeasured. That PR decided which runs are unaccounted and kept their
spend out of the token/cost ratios; it never touched wall, and this
change does not revisit that decision. It fixes the wall population
those adjacent rate lines divide, measured for every run including the
ones #13446 excluded elsewhere. Not a re-report of #13446.
Verified: both probes are red on 53a8db913 (compare Solved/hour 120.0
vs the report's 3.9; global wall/task -30.0s vs the class row's
+870.0s) and green after; a no-unaccounted fixture renders compare
byte-identically (goldens); "Wall seconds / solved" divides accounted
wall by accounted solves under either rate line reverted to the
accounted total, while the matching probe turns red; the suite
report's Solved/hour convention is pinned unchanged. gofmt,
GOOS=windows go vet ./cmd/e2ebench/, go test ./cmd/e2ebench (only the
pre-existing environment TestStateHomeIsOutsideWorkdirParent fails,
identically with this change stashed), make check (repolint: clean),
and golangci-lint run ./cmd/e2ebench/... (0 issues) pass.
Cause: a transcript that could not be read was stamped health=corrupt while its
root was left state=ready with the failing size/mtime signature. Both automatic
repair paths trusted that row: reconcileRoot returned before indexPath ran and a
restart reopened the same ready row. Equal size/mtime says nothing about
readability, so a restored mode or a remounted transcript was never revalidated;
status stayed Indexed 0 / Total 1 until a save or a manual `catalogs reindex
history`.
Fix: chose the scan's skip predicate — reconcileRoot only short-circuits when
state=ready, the signature matches, indexed==total, and no source of that root
is non-ok (health<>'ok' or missing_since<>0). That is the decision that
prevented indexPath from ever being consulted. The same rule is applied at
source granularity: sourceProjectionUnchanged now also requires the stored
health to be 'ok', because after the root is rescanned a legacy source with no
ledger digest (digest=="") would otherwise be certified healthy from its
fingerprints without being read.
Fast path kept: a root whose last scan was complete and healthy still returns
before any transcript is opened, so the five-minute tick remains a directory
walk plus stat, not a re-read of every transcript. A source that stays corrupt
is retried once per scan (one failed read per corrupt source per tick) and
scan_generation advances exactly once per scan, so a permanently unreadable
transcript cannot wedge or loop the worker.
Tests: the audit's TestCorruptSourceIsRetriedOnceTheTranscriptIsReadable (red
before: health stays corrupt after the transcript heals) plus controls for a
healthy unchanged root still skipping without a read (scan_generation, revision
and document ids unchanged; a now-unreadable transcript keeps health=ok) and for
a permanently corrupt source retried on every scan while its healthy sibling is
not re-read. Permission fixtures skip on Windows and root, and every catalog in
these tests points at a temp path.
golangci-lint's intrange rule rejects a C-style loop whose index is never
read. Both loops in the new cases just repeat an emit, so they take the
Go 1.22 form. Behaviour is unchanged.
Store.MigrateV2WithVersion returned on the first fact whose os.ReadFile
failed, so facts later in the same directory and every later directory
were never migrated. MigrationReport carried only { Migrated int } and the
boot caller discarded it, so nothing named the skipped files: the audit
observed {Migrated:1} with zeta and omega never reached.
The contract says otherwise. docs/SESSION_MEMORY_RETRIEVAL.md:259-261: a
read condition is "reported instead of returning partial lists or
calculating a revision from incomplete history". :268-269: "These
conditions ... do not establish data deletion." The pass therefore records
each failed basename in MigrationReport.Unreadable, sorted, and continues
with the rest; boot's warning names them instead of printing only the
first path.
A per-fact read failure returns a nil error on purpose: the field is the
channel for "skipped", and folding it into err would make a skipped fact
indistinguishable from a pass that could not run. Directory and index
failures still return an error, because those leave the pass's own work
incomplete.
Tests cover: the audit fixture (alpha readable, beta chmod 000, zeta after
it, omega in the second directory) now migrating alpha/zeta/omega with
Unreadable=[beta.md], cross-directory sort order, a fully readable control
with no error and an empty list, the repair path for an already-migrated
fact behind an unreadable one, and the boot warning naming beta.md.
Review asked for comments to describe what holds now and leave how the code
got here to the commit history. The behaviour is unchanged.
The history this replaces, kept where it belongs:
* Leaving Source in the zero-probe kept ToolProgress on the passthrough
path, so the first attempt at merging progress merged nothing in
practice. Source is identity — it names the emitting party — so it
belongs in the key, and keeping it there is also what stops a planner's
text merging into an executor's.
* The e2e run over a real kernel is what caught that; the unit tests did
not, because they emitted deltas with no Source at all. Both are now
covered: TestCoalesceMergesDeltasCarryingSource and
TestCoalesceSeparatesBySource pin the key's shape.
Cause: persistCompactionStateLocked reached SaveCompactionState, a blind
atomic overwrite carrying no writer identity, while transcript saves fail
closed with ErrSessionWriteAuthorityMissing. A controller in the
stale-authority window (rebind, controller replace, lease generation change)
therefore installed and published a fold whose transcript save was refused:
it replaced the owner's installed digest and paid for the same span twice.
Enforced in the store rather than in the agent: (*Session).SaveCompactionState
presents exactly the permit save.go presents (requireWriteAuthorityForSave,
including BeginSave's in-flight accounting that lease Release waits for), so
there is one adjudication of the permit and no second check re-reading
WriteAuthority() in the agent. Every write to the owner's sidecar already
funnels through the agent's single persistCompactionStateLocked, which now
calls that method. The raw SaveCompactionState remains for paths that write a
fresh recovery lane and for low-level CAS tests that never bind authority.
The CAS on (generation, projection_version, covered_prefix_hash) is untouched:
this is an additional gate, not a replacement.
On refusal the typed authority error propagates as
errProjectionNotPersisted: commitSummaryProjection restores the previous
projection, so nothing is installed or published, and the next attempt under a
valid permit folds once.
Tests: internal/runtime/agent/compaction_sidecar_authority_test.go proves the
no-authority refusal (sidecar and digest unchanged), the authorized control
(one summary, one digest), and that a refused publish costs one normal retry,
not a second summary.
reanchorPattern stripped the backslash from \!name and \#name before
handing a root pattern to the matcher, which then read the first as a
negation and the second as a comment. Files named !name or #name that git
ignores through the root .gitignore, .git/info/exclude or the global
excludes file were therefore searched and listed. Nested .gitignore files
were not affected, since their patterns are re-anchored under /dir/.
Keep the escape on root patterns when it guards a leading ! or #.
The global ignore file was taken from the first core.excludesFile found:
$XDG_CONFIG_HOME/git/config before ~/.gitconfig, and the first value in a
file. Git reads the files in that order and keeps the last value, so a
user's ~/.gitconfig setting lost to the XDG one. The value was also used
nearly raw, so an inline comment or escaped backslashes produced a path
that does not exist, and the user's global ignore rules were silently
dropped.
Keep the last value across files and within a file, and decode the value
like git: strip unquoted # and ; comments, remove quotes, and unescape
\\ and \". Other backslashes stay as written so raw Windows paths still
work.
useRevealed paces a streamed answer on the frame clock and keeps its cursor
in a ref. The effect returned early when the cursor had caught up and only
handled a shrink inside step(), which the early return made unreachable. A
replacement shorter than what was already revealed — regenerate, rewind,
history restore — therefore left the cursor above the new length, and every
later growth below it was short-circuited: the typing animation silently
stopped for that message while the text itself still arrived in one piece.
Handling the shrink before the early return fixes it. The cursor comes back
with the text, so the next growth is paced exactly like the first one.
The existing three tests did not cover this: they only grew the text. The
new case fails against the old code with "expected 0 to be greater than 0".
A shell emits one ToolProgress per output line, so a build log crossed the
coalescer as one frame per line. Downstream that filled a subscriber's
64-frame queue and the hub then dropped answer text beside it, because text
and progress are both droppable. Measured on a real 500-line command: 285
frames unrepaired, 2 with this.
streamDelta replaces isStreamDelta and returns the key a burst merges under
plus its payload, so progress joins text, reasoning and compaction progress:
* the key carries Source. Every progress chunk an executor emits sets
Source="executor", and leaving it in the zero-probe kept the event on the
passthrough path — the first attempt at this merged nothing in practice.
Keeping it in the key also stops a planner's text merging into an
executor's.
* the key carries the call id, so two tools reporting at once never land
in one another's card.
* progress carrying more than ID+Output — a sub-agent's status channel sets
Name, ParentID and a rate — fails the probe and still crosses untouched.
* progress without an id is not buffered under an empty key.
Verified against a real kernel over SSE: a 500-line command emits 2 frames
that concatenate to the same 500 lines in order, and a 3000-line command
emits 4. Alternating text and progress is unchanged (the key switch flushes
either way, so that burst still cannot merge).
The kill test proves the leftover is removed and the live fact is untouched, but
nothing pinned the branch itself: forcing an active id down the rename-back path
now fails because ListArchived lists the live fact as archived again.
RestoreArchived moved the archive entry away only after the active file
and its index line were published. A process killed in between left one
id both active and archived, so ListArchived reported a live fact and a
retry was refused as "already active".
The entry now moves to a hidden .restoring-<id>-<stamp> name before the
active file is written. The stamp is the archive time, so the name
carries both the id and the original archive name with no journal and no
content comparison. Every restore sweeps leftovers first: an active id
means the dead restore had published, so only the redundant copy goes;
an absent id means it never published, so the entry is renamed back. A
failed sweep reports its error and publishes nothing.
Resolve .reasonix/reasonix.toml only when the root project config is absent. Share selection across loading, local edits, installs and repair; retain project-scope restrictions and reject symlink escapes.
Fixes#6007
chmod 000 does not remove read permission on Windows, so the fixtures that make
MEMORY.md unreadable would fail their own self-check there. The classification and
aside behaviour are unchanged on Unix; the directory-named case stays cross-platform.
After an unreadable index is moved aside and the derived index republished, the
store must list exactly the fact files and the index must not name the aside.
MigrateV2WithVersion is the only entry that recomputes the derived
MEMORY.md, and it returned readIndexIn's error. Every mutation path reads
that same file first, so a restored store whose index has a foreign owner
or a changed ACL froze every memory write for good: boot only warned, and
the docs' promised re-index has no other entry.
Classify the read error by identity: only fs.ErrPermission (permission
denied, a foreign owner, an ACL) and a typed not-regular-file error (a
directory named MEMORY.md) reach the rebuild. Every other error -- an I/O
failure, a resource limit, a momentary lock -- still returns, because the
file may be perfectly good and republishing would overwrite hand-written
text a retry can still read.
Before publishing, atomically rename the unreadable entry to a stamped
name in the same directory (MEMORY.md.unreadable-<unixnano>), so the bytes
survive for a later repair; a rename needs write access to the directory,
not read access to the file. If the rename fails, return the original
error and publish nothing. flushIndexIn keeps the readable path, so
hand-written text is still preserved line for line when it can be read.
No new persisted state beyond the aside copy; the MigrateV2 signature and
boot call site are unchanged, and an unreadable store directory or fact
file still aborts the pass.
Tests: the audit probe is red before and green after; controls cover the
stale readable index, hand-written survival, byte-identical healthy boot,
missing-index rebuild, and a later save succeeding after the repair. New:
a transient read error leaves the index byte-identical (red against the
previous revision), the unreadable bytes survive under the stamped name, a
directory index repairs, and a failed aside rename publishes nothing. Each
new guard was reverted alone to confirm its control goes red.
The turn-start workspace snapshot was awaited with no bound, so in a very large
workspace (a drive root: 7 s on a warm SSD for the first turn, 0.5 s after) the
first tool could not start until the walk finished, and a command's own
timeout_seconds, which only starts when the command does, never covered it.
The wait now ends at snapshotWait, the bound the end-of-turn snapshot already
has: the walk is cancelled, the snapshot records itself incomplete, and the
bundle says the turn's changes were not compared.
The row sized the name, the model chip and the status to their content and gave
the task only what was left, so with the chip present the task was squeezed to
8 px at a 360 px panel and 0 px at 280 px. Below 460 px of the list's own width
the row now takes up to three lines: name and status first, the task second,
the chip third.
profileForSkill set only the name and count, so a skill sub-agent (explore,
locate, run_skill, read_only_skill) showed no task while a task sub-agent
showed its first line. The label helper moves to the event package, next to
the Profile it fills, and both producers use it.
A task text that names a file in the host's own .reasonix/attachments directory
now gives the delegate that image (judged by the resolved path, read through the
same checks an @-reference gets), on the vision role with the note that it rides
the message, including on a follow-up turn that carried no image. The
read_only_skill runner took the turn's candidates without routing the model or
saying so, so a text-only skill sub-agent dropped them silently.
Each delta did s += piece on the row's text, so a long stream cost time and
memory quadratic in its length: 20000 progress events of one command (4.5 MB)
took about 13 s of the UI's own time. The text now grows in a buffer kept beside
the row, trusted only while the row still holds exactly what it last handed out.
The product keeps its 15 second bound on one ssh -G; the source now carries it
as a field with that default, and the test that runs the installed ssh sets it
to testenv.Budget, a bound that only reports a hang.
internal/session/control/git_push.go measured 0 from the control package's own
tests, so declaring it sensitive recorded a floor of 0 that could never fail.
The tests cover the trust refusal for both calls, a repository that is not
there, a branch with no upstream, and the commit lock released after a refused
push; the floor is now 100.
Two project folders under one git repository share a writer domain on purpose
(the lock and the visibility of each other's claims), but an undeclared writer
claimed the domain root, so sibling projects waited on each other for bash and
MCP writes, and the refusal read held X, requested X. It now claims the
selected folder; a session on the repository root still overlaps both, and a
declared path that cannot be resolved still claims the whole repository.
Problem: trusting a publisher whose plugin source has a mistyped commit
reports a misleading local-or-unsafe-source installation failure.
Root cause: plugin source validation collapsed its refusal into the generic
bad-source class before the HTTP and presentation layers could explain it.
Fix: preserve a distinct invalid-plugin-source error through publication and
installation, translate its complete-commit requirement in both languages,
and align the shared client validator with existing registry path checks.
Verification: Go vet, repository lint, owning market/serve tests, the required
builtin/boot tests, 391 frontend market/i18n tests, and TypeScript checking.
The push is the one git action Reasonix performs that leaves the machine, and
the host-only route list decides which requests a paired device may make. Both
now demand review plus security_review and are held to the coverage they have:
gitpush 84.0, serve/git_push.go 77.0, serve/host_routes.go 93.0, and
control/git_push.go 0 (its behaviour is covered by the serve and boot effect
tests, which the per-package gate does not count).
An Auto Guard card and a call that asks before it writes now give the claim
back for the wait; the lease records granted claims so the resume can name
who took the ground, the model gets workspace.claim_contended and the receipt
workspace_claim_yielded. A blocked acquisition ends at a user-set maximum wait
with workspace.claim_timeout.
closeBootTestHistoryCatalog gave the history and usage catalog closes a
fixed 5 s. Closing a SQLite catalog, and waiting out an open still in
flight, can take longer on a loaded Windows runner, and the posture
effect test failed there in cleanup with "close usage catalogs: context
deadline exceeded". The wait only exists to report a hang, so use
testenv.Budget; a close that never returns still fails the same way.
A child that shares the parent's provider starts on the depth in force now, and one
that inherits the parent's model without an effort of its own is built for it, as a
rebuild would have given. The status line reads the depth the session is on.
A provider that exposes the depths it accepts in Request.EffortOverride
(provider.RequestEffortProvider; the OpenAI-shaped adapter does) lets a session change
depth without a rebuild: the controller sets a session depth every main request
carries, updates its running selection, and accepts the change mid-turn. Serve (and so
the terminal and the desktop) and ACP both apply it in place and keep their rebuild for
a depth the provider does not take per request. The level is still saved to the user's
config so a restart opens on it.
Problem: the advancing Studio branch conflicted with the mod-turn settlement
test and changed queue pause and child-dispatch reporting contracts beside the
reviewed composition work.
Root cause: both branches added turn-completion waiting, the combined inbox
owner exceeded the file ceiling, and the effort audit still expected concrete
runtime identity in dispatch profiles after Studio restored selection profiles.
Fix: integrate exact Studio da9fbe7307 normally.
Keep both queued TurnDone and durable idle settlement. Group pause operations
in inbox_pause.go without behavior changes. Assert requested selections on
dispatch events and concrete identities on status events, retaining real
provider effort, rejection and persisted-child checks. Preserve landed queue,
billing, phone and remote protection work and the default-off composition UI.
Verification: format/vet/lint/make check; all 229 root Go packages and required
builtin/boot; affected HTTP/storage/controller and focused boot races; pause
controller regression; Desktop/SDK; 699 frontend files and 5670 tests,
typecheck/build/census; 12 browser/eight Electron renderer scenes; Electron
units (177 pass, one existing skip); 13 official ACP SDK conformance tests.
No threshold or authority expansion; no hosted check result is substituted.
The vendor's published schedule already prices every request; the status bar
now says, live, whether the model is billed at the peak or the off-peak rate
and when that changes, where the model is billed at the vendor's own rate.
Problem: repeated New session reset a blank pane's mode, copied built-in
descriptions ignored the window language, and identifier fields missed theme
styles. The picker was always visible and native archives were parsed in the
renderer. Unrelated transitions also repositioned every open popup.
Root cause: pane reuse resolved an unrequested default; editor styles depended
on a container the identifier row did not share; interface opt-in was missing;
archive transport carried parsed objects; placement had no anchor scope.
Fix: change a reused pane only for an explicit selection and preserve focus on
refusal, translate built-in copies, share field tokens, add a default-off local
picker switch that preserves bindings and the original hidden-state layout,
upload opaque files to the existing host decoder with typed localized errors,
keep the prior JSON envelope, and scope transitions to each popup's anchor or
container. Show catalog unavailability and wrap the editor components.
Verification: failing-first regressions; format/vet/lint/make check; all 228
root Go packages, required builtin/boot, HTTP/storage races and exact-binding
archive transport tests; Desktop/SDK; 691 frontend files and 5608 tests,
typecheck/build/census; 12 browser and eight Electron renderer scenes across
languages/themes/widths; Electron units (176 pass, one existing skip).
No authority, schema, safety policy or verification threshold is widened.
A balance_url on a host that is not a vendor's own now names its format
(balance_format) or is not read, and the key is not sent to it. The connection
editor edits the address and format. Existing relay entries are declared as
deepseek once, and a format the 1.x line drops from the shared file is put back
at the next start.
Problem: ungranted HTTP clients could mutate presets or switch compositions,
and empty hook selections could remove configured safety guards. A paired
remote pane could also borrow the host credential, and a selected native
extension could override the host's denied read.
Root cause: preset routes bypassed request-scoped edit grants, proxying erased
device authority, and frozen automation was treated as the whole safety policy.
Fix: gate preset mutations before store/admission access, refuse paired preset
writes before remote credential forwarding, overlay current authorized guards
outside retained automation, update their shared runner on live edits, resolve
hook inspection/save/approval/reload against one home, and mark bound runtime
packages tighten-only. Preserve ordinary unbound extension behavior, immutable
bindings, optional retained automation and the accepted Standard/Minimal UI.
Verification: failing-first HTTP, proxy and real boot/provider regressions;
format, vet, lint, make check, full root and required builtin/boot suites;
affected package and focused boot races; Desktop/SDK; frontend 689 files/5598
tests, typecheck/build and census; browser eight/Electron renderer four scenes;
Electron units. Correct an inherited mod fixture's premature teardown using
the existing durable-idle notification, with 20 independent process runs.
No test threshold, approval gate or product Close behavior is weakened.
The queued prompt starts a turn of its own, still writing under the test's temp
directory when the test returned, so the directory's cleanup failed with 'directory not
empty'. Seen as 30 failures in 40 runs with eight test processes at once, 0 in 40 after.
The test gave a cold Git Bash two seconds to write the heredoc's file; on a shared
Windows runner the start can take longer, and the failure (4.25 s) was that wait plus
the cancel. The bound is now 15 seconds, only to keep a hang from lasting forever.
The test compared the host's measured thought to the provider's 60 ms sleep minus 10,
which a loaded -race runner breaks (42 ms against 50). The size of the measurement is
the thought clock's own unit test with an injected clock; this test keeps its claim that
a measurement is taken, kept with the turn and framed identically.
A summary request the provider rejects as larger than its context (the documented
context_length_exceeded code, nothing else) is retried at 70%, 50% and 35% of the
refused input, shortened by the same deterministic steps as before; a refused request
costs nothing, so the digest is not given up until three smaller inputs were refused.
A provider that refuses every size ends as summary_input_too_large.
The inbox manifest now records why a queue is paused, as a typed code beside
the old boolean. A message approved on its card is claimed over any pause; one
let in by a standing release or dispatch switch only over a pause the host set
to guard stale input, and stays queued over one the person set. The pause and
older queued items are left as they were.
#13812 made the dispatch-time profile the concrete runtime. The effort-switch audit
test (merged beside it) holds that profile to what the call selected (empty model and
effort when it inherits), so studio went red. The selection is audited as the model
asked it; what the child ran on is on its status events, which the window already
reads, so the dispatch profile goes back to the selection.
Problem: Studio's shared remote-open lifecycle conflicts with the composition
callback, and the HTTP adapter omits the selected composition from its body.
Root cause: composition selection was carried by an App-local path rather than
the shared open-ordering owner and the transport serialization boundary.
Fix: merge exact Studio 5b1c282fed, extend
useOpenRemotePane and SseHub with the selection, preserve newest-open ordering,
duplicate-click coalescing and refusal cleanup, and keep editor styles with
their existing catalog stylesheet without widening the shared CSS budget.
Align the child-effort effect assertion with Studio's concrete dispatch model
and effort reporting while retaining provider, refusal and transcript checks.
Keep Standard/Minimal cards, the mode-only left trigger and right run state,
default-off semantics and all existing source/workspace authorization gates.
Verification: failing-first remote Hook/HTTP body regressions; format, vet,
lint, make check, full root and required builtin/boot tests; affected package
and focused boot races; Desktop/SDK Go; frontend typecheck, 689 files/5598 tests
and build; census fixtures; browser eight and Electron renderer four scenes;
Electron unit suite (176 pass, one existing skip). Local review/security review
does not replace maintainer approval or hosted CI.
[tools].disabled removes a tool from every request and refuses a call to it with
the reason; tools another feature requires declare so and stay on with a note;
a project file can only add names. Settings lists every tool with a switch.
The panel row, the full-record header and the nested card of the spawning call show
provider/model · effort, with the full reference as the title. The card takes it
from the child's status events, so a failed or interrupted run keeps it; a batch's
items each carry their own.
The dispatch profile now names the concrete runtime a call resolves to (an alias
expanded, the parent's model when the call inherits), and each child's status events,
from queued to the terminal one, carry what its run actually started with, per item
for a batch. A failed or interrupted child therefore still says what it ran on.
Bumps the go group in /desktop with 1 update: [golang.org/x/mod](https://github.com/golang/mod).
Updates `golang.org/x/mod` from 0.41.0 to 0.42.0
- [Commits](https://github.com/golang/mod/compare/v0.41.0...v0.42.0)
---
updated-dependencies:
- dependency-name: golang.org/x/mod
dependency-version: 0.42.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: go
...
Signed-off-by: dependabot[bot] <[email protected]>
Two controls the audit's contract implies but nothing pinned: a staged copy whose
retired twin came from a different publish is left untouched (no pair, no writes),
and a fact created at the old name after the kill is never overwritten — the retry
refuses and keeps both artifacts for a later attempt. The refusal only named the
conflict once the exist check unwrapped the error, so it now uses
errors.Is(err, fs.ErrExist).
The approval carries the reason code origin_text, so Studio words it in the reader's
language, hides the full-access button (which cannot answer it), and shows the call
whole; the terminal wraps the subject and reason instead of clipping them; ACP
gets a fresh allow-once or reject. The label names up to three origins, then counts
the rest. The guide says headless YOLO refuses such a call.
PermissionLists carries askOnExternalOrigin; a save without it leaves it as it is,
the rules endpoint's rebuild applies it, and it keeps the endpoint's provider-edit
grant. The desktop type and mock mirror it.
A fold that loses the transcript race records FailContextChanged, but no later
turn could release it: retriesOnGrowth did not name the code, the receipt holds
until the generation advances, and the generation only advances on a commit the
held receipt prevents. One racing append then latched automatic compaction off
for the rest of the session, across restarts, and the next overflow failed the
turn instead of folding.
Name the code in retriesOnGrowth so the append that made the fold stale is the
growth that releases the receipt. The receipt already carries InputHash and
InputTokens, and every other code keeps its hold. A plan/provider refusal still
holds, and the retry buys exactly one fold: without further growth the next
prepare neither folds again nor re-records the block.
The recovery may either finish the rename forward or roll it back. Only rollback
keeps an ExpectedRevision from before the rename valid, so the choice is a
contract, not an implementation detail; forcing the forward path makes this test
fail with name=beta revision=2.
publishRename retires the old fact file, flushes the index, then publishes
the staged new file. A kill in that window leaves the fact's only copies as
hidden .retired-* and .staged-* files: Read/List/Revisions report nothing,
the save retry by id reports "memory id not found", and a retry by name
mints a second identity for the same name.
A save by id that finds no active file now looks for the hidden pair a
single publishRename call writes, matched by the nanosecond stamp in the
two file names, with the staged copy's frontmatter carrying that id. The
rule is disk-driven and rollback-first: readable retired bytes are restored
under the old name (same bytes and mode) and reindexed, so the caller's
expected revision still names the fact and the retried save redoes the
rename itself. Only when the retired copy cannot be parsed does recovery
publish the staged copy forward under its new name. With no matching pair
nothing is written and the id is still reported not found, so a retired
copy left by a committed rename is not revived.
Covered by the audit's kill probe, an uninterrupted-rename control, a name
retry after recovery, a failed (not killed) publish, an unpaired-artifact
guard, and a cross-scope move whose pair spans two directories.
With permissions.ask_on_external_origin on (user file only, off by default), a call
that is not a read and whose arguments repeat, byte for byte, text the task's
external results gave it is put to a person as a fresh approval when only the
writer fallback (Auto, YOLO) would have let it run. Explicit allow rules, session
allows, reads and authorized MCP servers are untouched; with nobody to ask the call
is refused naming the origin.
Review follow-up after rebasing onto studio: messages_en/zh/zh_tw.go sit at the
800-line ceiling, so the two new strings move into the embedded QueueMessages and
NotifyMessages catalogues, which have their own files. No call site changes.
session.read and session.submit are separate capabilities, so a mod that only
reads usage cannot put words in front of the model. One prompt a mod queues is
bounded by [mods] submit_bytes (8 KiB, 0 for none) and refused with a typed
identity that does not spend the per-turn count. The docs say the turn a queued
prompt starts is held to this session's own approvals and sandbox.
Problem: Studio browser guards failed on 17f816d11 when the ordering guard
counted SSH rows as conversations in its local project. The remaining quick
action fixture also expected two trailing Archive/Delete buttons.
Root cause: Browser acceptance scripts still encoded the former separate
machine lists, trailing Delete action and individual folding chevrons. The
English wording scan also lacked a newly visible demo conversation title.
Fix: Scope ordering to the named local project and independently assert SSH
rows and keyboard focus are preserved. Exercise default-off and explicit
quick-action choices for local and remote rows, including hit targets,
leading Pin/trailing Archive geometry and menu access. Load the real row
stylesheet in the compact fixture and match its existing 1px spacing while
retaining 34px rows, ellipsis and contained controls. Update manual tree
checks to use project-row folding and five-item previews.
Recognize the existing demo title as user content in the wording fixture list.
Verification: Reproduced the ordering, quick-action and fixture failures
before the repair. Ran all 55 CI browser-guard commands against the built
perf harness and reran repaired guards, the affected manual tree scripts,
gofmt, root vet, make lint,
make check and the required builtin/boot suites. This changes acceptance
scripts only; production UI, workflows and budgets are unchanged.
The reviewer's blocking item: rebindInbox (called from New) started a goroutine
that read c.sink while New was writing it. Capture the sink before starting the
goroutine, for the new unrecovered notice and for the pre-existing recovered one
(it had the same shape and the same race).
Also give the refusal an identity: ErrUnrecovered, wrapped by commitManifestLocked,
with a case in the serve switch so the frontend gets inbox.unrecovered instead of an
internal fault. repolint's refusal-path rule requires that case.
The store already told a caller that the queue's bodies could not be read
(InboxSnapshot.Unrecovered), but nothing user-facing consumed it: the TUI's
/queue line and the notice stream both dropped the field, so an unreadable
queue still read as "inbox empty" and the user could not tell it from a
genuinely empty one.
The TUI mirror and renderQueueList now carry and say the unknown state, and
the controller emits a distinct inbox_unrecovered warn notice (with an
InboxUnrecovered payload naming the queue directory) beside the existing
inbox_recovered one, so its sentence is not confused with "recovered N".
Cause: the frontend contract field had no consumer on the TUI or notice
surfaces.
Blast radius: TUI /queue line, TUI and desktop notice wording, and the two
controller inbox open paths; additive field, code and payload, no on-disk
schema change, recovered behaviour unchanged.
Neighbouring behaviours tested: empty-but-readable queue, recovered salvage
count, TUI mirror JSON decoding of the real GET /inbox body, and both the
serve and control boundaries.
Why this layer: the store owns "unknown vs empty"; the surfaces that render
a queue are where a user can act on it.
Provider-visible prefix: none
salvageOrphanBlobsLocked returned nil for both "no blobs" and "blobs
unreadable", so a corrupt manifest plus an unreadable blobs directory
committed an empty paused manifest (recovered=true, recoveredN=0,
items=null) over the quarantined one. The queued bodies stayed on disk but
were unreachable, the RecoveredN>0 notice never fired, and the queue looked
empty.
The salvage now reports the read failure, and that failure is not turned
into a manifest: the corrupt bytes stay in place as evidence, the store
comes up paused with unrecovered=true on the snapshot, and no commit can
overwrite the evidence while the bodies are unreadable. An absent or empty
blobs directory is still an empty queue, not a failure.
Cause: (items, err) collapsed to (nil, nil) for directory-level and
per-entry read errors.
Blast radius: sessioninbox Open/Snapshot plus the commit path; new
additive InboxSnapshot.Unrecovered field, no on-disk schema change.
Neighbouring behaviours tested: readable-dir salvage, absent/empty blobs
dir, healthy manifest untouched, symlink blob refusal.
Why this layer: the store owns the manifest/blob transaction, so the
"unknown vs empty" distinction belongs where the read fails.
Found while demonstrating the install through Discover: until the switch and
both components read Running there is no reasonix tool, and the model falls
back to running reasonix -p in its own shell, which only looks like a
delegation.
An @token in a peer message or a background-job notice was frozen into the
prompt as a file reference, so a sender could make the receiver read a file
into its prompt, and an unreadable one paused the queue behind it. Items the
host authored are data and are no longer scanned.
Problem: current Studio conflicts in the state-root inventory and adds a move
path that strips the system message containing an immutable composition binding.
That could silently reopen a bound conversation with ordinary capabilities.
Root cause: cross-project adoption intentionally omits project instructions and
permissions, but the new mover did not distinguish immutable composition metadata
from an ordinary system prompt.
Fix: normally merge exact Studio0363720bd. Retain both rules and agent-presets
state roots. Refuse bound conversations in the common MoveSession owner before
copy/delete, preserve the source, and project the typed refusal with bilingual
HTTP wording. Keep ordinary unbound moves, ACP authentication/new-conversation
contracts, directory grants, exact dispatch inheritance and the accepted mode UI.
The preceding source-authority fix remains intact.
Verification: failing-first store and HTTP move tests now refuse blank/started
bound moves and retain the original. All226 root packages pass: mandatory2 plus
the inventoried other224; boot340.765s. Affected race suites pass. The full boot
race invocation previously reached its10minute aggregate limit; final evidence
uses one compiled source snapshot and four disjoint groups covering all753
tests exactly once, without raising the timeout. Static gates, Desktop/SDK,
frontend675files5489tests/typecheck/build/census and browser8/native4 scenes pass.
Local review/security review recorded separately from required maintainer review.
The host keeps the text of a task's external results and counts, per turn, how
many were recorded and how many later non-read-only calls carried a verbatim
string from one. The numbers ride the completion-report audit; nothing is
blocked or shown to the model.
Problem: Review requested WSL boundary evidence and removal of the unrelated
design-qa.md diff after studio introduced non-SSH machines.
Root cause: The new WSL transport supersedes a blanket rejection at shared
remote endpoints. SSH construction must still reject WSL entries, while
generic probe, browse and open must select their configured transport.
The earlier QA cleanup also deleted a document already owned by studio.
Fix: Integrate studio through 0363720bd and cover the real Hub/remotehost/pool
route with disposable WSL and SSH factories. Assert WSL failures survive the
HTTP boundary and never construct an SSH client. Cover Windows, distribution,
empty and fallback default paths. Restore design-qa.md byte-for-byte from
studio so it no longer appears in the PR diff. Retain the upstream SSH form's
refusal to overwrite non-SSH entries.
Verification: Frontend 5529 tests and production build; full serve,
remotehost, remote, attach, wslconn, config, builtin and boot suites; gofmt,
root vet, make lint and make check. No live Windows/WSL qualification claimed.
Problem: PR #13638 became conflicting after studio introduced moving a
conversation to another project.
Root cause: HubPort acquired moveSessionTo beside the archive declaration
that this branch extends with an optional host. Automatic merging also
omitted the errors import needed by the upstream WSL edit guard.
Fix: Keep both capabilities and restore the required import. Retain the
upstream local-only move workflow and the host-qualified pin/archive routes.
Remove an inaccurate rename comment so the combined component stays within
its existing size ceiling without widening the baseline.
Verification: Frontend suite (5529 tests), production build, full serve,
sessionstore, config, builtin and boot tests; gofmt, root vet, make lint and
make check. Transport coverage exercises local project moves beside remote
flags; an interaction regression covers the shared keyboard menu and dialog.
An isolated development browser confirms menu access, project selection and
cancel with hover controls off. Hosted CI remains a separate observation.
The folder in a draft came from the request and went straight to Abs, EvalSymlinks
and Stat. It is now matched, as text, against the remembered projects the person
trusted (scheduler.Folders), and everything after the match uses the list's own
spelling. Anything else is refused as schedule.workspace_unknown, in the kernel,
the CLI and the board's folder list. A run re-checks trust before it starts.
Problem: selecting a repository preset could change an already authorized MCP
server's package arguments or destination and reuse its executable authority or
credentials. The same provenance issue applies to extension args and env.
Root cause: composition parameter validation checked shape and credential
references but did not distinguish repository declarations from personal ones.
Pinning an executable does not authorize the program selected by its arguments.
Fix: enforce source authority in the shared contract. Project MCP/extension
parameters allow only supported timeouts. Validate declarations, imports, old
revisions, and both definition/resolved halves of retained executable bindings.
Keep structural history validation separate so legacy unsafe bindings remain
readable without regaining execution. Align Mock save/import, document the source
table in both languages, and add the release note. Preserve Standard/Minimal,
unbound defaults, persona, additional directories and exact dispatch bindings.
Verification: failing-first boot tests prove changed args start a subprocess and
changed URL contacts another endpoint before the fix; both now refuse before
startup. User positive controls, scope/parameter table, store/revision/old-record
and event-log history tests pass. Format/vet/lint/makecheck, required builtin/boot,
full root, contract/store/sessionstore/serve/ACP race, Desktop/SDK Go and frontend typecheck/build pass;
671 frontend files / 5472 tests pass. Local review/security review recorded;
maintainer approval and new-head hosted CI remain separate requirements.
The full boot race invocation reached its existing aggregate 10-minute limit
as another test started; disjoint test-list groups are used to finish the whole
inventory without raising that timeout or skipping tests.
The index pins the commit that last changed the package; scripts/dsh-market-index.mjs
pins it and validates it with DSH's own validator. docs/DSH.md explains Discover
and the Studio release runbook gains a precondition that keeps the pin current.
A configuration-only package (no code) in integrations/dsh-plugin, installed
from this repository's subdirectory with pnpm's path: selector, plus the
integration guide docs/DSH.md.
How to wire dsh's subagent-acp provider to reasonix acp, what each permission
policy does to Reasonix, what the parent sees, and the limits, verified against
dsh 0.2.1-rc.2.
Problem: newer Studio changes conflict with the ACP session contract and HTTP
runtime opening after the previous assembly integration.
Root cause: Studio adds a new-conversation execution-mode flag where this branch
also owns explicit composition selection and immutable inherited bindings.
Fix: normally merge Studio 4d3c09211. Add NewConversation to the one SessionParams
definition and retain it in HTTP build options. Resolve moved transcript paths
before reading either branch settings or the original composition. Keep exact
dispatch inheritance, workspace refusal, candidate rollback, ACP directories and
the accepted Standard/Minimal composer and management design.
Verification: format, vet, lint, make check, mandatory builtin/boot, full root
Go, Desktop Go, frontend typecheck/build and census pass. Frontend 671 files /
5465 tests and eight browser/four native renderer scenes pass. Affected
boot/ACP/serve/control/remote/delegation race tests pass. Provider request bytes
equal actual Studio with and without persona. Local review/security review are
recorded separately from maintainer review. The unchanged macOS computer
coverage baseline remains 88.2% below 89.8%; no floor is changed.
session/new answered -32603 for a model with no API key or no configured
provider, although initialize advertises a setup auth method. The cause is now
a provider.ErrKeyMissing / ErrNoProvider identity and the frontend maps it to
the ACP schema's -32000.
Problem: Studio advancement made the Agent composition PR conflict in boot
assembly while adding trace export and a workspace-aware mod runtime source.
Root cause: wireMCP now has a composition error result, and extension preflight
filters immutable bindings where Studio introduced its package source.
Fix: normally merge Studio ed1c127d6. Keep OTLP cleanup before returning from
wireMCP and retain workspace-aware discovery before exact composition filtering.
Preserve upstream tighten-only dispatch, replay protection, ACP elicitation and
the withheld mod switch. Keep Standard/Minimal, opt-in binding, ACP directories,
exact dispatch inheritance and the accepted composer layout.
Verification: gofmt, vet, lint, make check, required builtin/boot, full root Go,
affected boot/extension/modhost/ACP/serve/control race suites, Desktop and SDK
tests pass. Frontend typecheck/build, census, 668 files / 5450 tests, Electron
unit tests and eight browser/four native renderer scenes pass. Raw provider
requests equal actual Studio with both persona and empty persona. Local review
and security review found no new issue. Coverage gate still reports only the
unchanged macOS computer baseline, 88.2% versus 89.8%; no floor is lowered.
Rule files under .reasonix/rules, .claude/rules and the user's rules folder are
read from disk each turn. Rules without paths: join the turn as a block owed
like the standing instructions; rules with paths: are owed once the session has
touched a matching file and retracted when they stop applying. Glob parsing and
matching stay with skills' paths: through one exported entry point. Neither layer
reaches the cache-stable prefix; capability diagnostics report what was skipped.
agent.default_mode (user file only, off by default) is applied by the assembly
for builds that open a fresh conversation: Studio panes without a saved
session, the TUI without a resume, and ACP session/new. A resume, a headless
run and sub-agents never take it. Settings gets the switch.
The unverified-change gap names the command the host declined, bounded
at 80 bytes by command[:80]. A check with non-ASCII paths, common in a
Chinese project, could be cut inside a character, and the delivery card
then showed a replacement glyph before the cut mark. Step back to the
start of the character instead, keeping the budget and the mark.
Subprocesses the host starts for the agent carry REASONIX_AGENT_CHILD=1, and the
person-only schedule commands refuse when it is set, so a pty opened by a tool
no longer passes the terminal check. add prints the whole plan and requires its
short id typed back; list shows how each schedule was confirmed. The Windows
preview test compares against the resolved workspace path.
Problem: Agent compositions still carried the persona prerequisite diff and
missed newly added session lifecycle contracts on Studio.
Root cause: the branch predated the landed persona, ACP additional directories
and project-dispatch assembly paths. Combining their independent additions
also exceeded existing file/scalar-state budgets.
Fix: normally merge Studio 896552483 after verifying #13588 actually landed.
Resolve the Persona/composition imports and keep one ACP SessionParams owner.
Clone vetted extra directories through composition switches. Pass the exact
private binding into dispatched runtime assembly; reject cross-workspace
bindings before opening, and reject fresh assembly errors without a history
fallback. Ordinary unbound dispatch remains unchanged. Extract the command
catalog and Mock memory projection, and place the MCP guard with catalog
refresh state. No budget baseline is raised.
Keep Standard/Minimal cards, the left mode-name-only picker, right run status,
default-off/unbound Standard behavior and the landed user-config persona.
Update bilingual behavior/API documentation and release notes.
Verification: format, vet, lint, make check, required builtin/boot tests, full
root count1 atomic-coverage tests, the complete CI race package group, Desktop
and SDK suites; frontend typecheck/build and 667 files / 5443 tests. Electron
unit176 pass with one existing skip. Browser8 and Electron4 controlled rendering
scenes cover locale/theme/width, selection, mode cards, locked/new flows and
left/right layout. Real HTTP requests match landed Studio byte-for-byte with
user persona29139 bytes and empty persona29110 bytes, with fixed probe PATH.
ACP/dispatch regressions fail before propagation and pass afterward. Local
review and security review cover MCP contracts and dispatch/ingress authority.
Known local gate: make coverage-gate reports the untouched macOS computer
package at88.2 below89.8. The exact command on landed Studio reproduces the
same result and uncovered lines. No floor is lowered; hosted Linux gates
remain required. Hosted results for this new head are reported separately.
One draft shape and one set of confirmation facts for every frontend; a
creation is accepted only with the token of the preview the person saw; only
the machine's owner may create or resume, any reader may pause or delete.
The host also answers 'schedule exec' by running the CLI's entry, so the
supervisor can start it as the child. One kernel at a time ticks, under the
machine's ticker lock.
Scheduler.Tick reaps dead runs, claims at most one due slot inside its grace
and hands it to the supervisor; the schedule add, list, pause, resume, renew,
rm and tick commands drive it. Creating, resuming and deleting need a
terminal.
$/cancel_request cancels only the named request and answers -32800;
agent message chunks carry an opaque messageId; an MCP server's form or
page goes through elicitation/create when the client advertised the mode.
Problem: Studio's new SearXNG connection probe conflicts with the persona
types in three transport import lines, blocking this PR again.
Root cause: independent settings features append types to the same line.
Fix: normally merge Studio 6f68a14a9, preserving the search probe and persona
contracts. Keep persona-only imports separate so subsequent upstream type
additions do not repeatedly conflict. Remove one upstream trailing space.
Retain the upstream MCP scope upgrades, ACP session directories, Gemini
provider, session ordering and rail-search behavior without modifying their
implementations. Persona prompt, user-only scope and guarded writes remain
unchanged, as do the approved composition UI and default-off behavior.
Verification: three-way simulations of future settings imports merge cleanly
in all three files. Format, vet, lint, make check, required builtin/boot tests,
full root Go tests with atomic coverage, full CI race package group and Desktop
Go pass. Frontend typecheck/build and 662 files / 5425 tests pass. Chromium
checks cover persona navigation in both locales, light/dark, wide/390px;
rail search, session ordering, web access, settings rows and font scaling.
Local review and security review check the integration and fresh MCP consent;
they do not replace maintainer approval. No gate or budget was relaxed.
Problem: the sidebar PR conflicts with studio's new per-session move actions.
Root cause: both branches extend the row key handler and the mock tree's
session projection. Sorting shortcuts also bubbled into tree navigation,
moving focus to a different session after a reorder.
Fix: retain keyboard context menus and session moves together, compose mock
placement with pin/archive decoration, and consume sorting keys at their
owning row. Keep the existing remote host and body-size guards unchanged.
Verification: 5478 frontend tests across 669 files; production frontend
build; full serve, sessionstore, builtin and boot suites; gofmt, root vet,
make lint and make check. A failing-first keyboard regression and isolated
browser checks prove reordered rows retain focus and keyboard menus still
expose pin, archive and move actions.
A 403 insufficient_scope from a signed-in server now stops the model and
tells the user; granting more is a fresh approval asked every time, from
the server row or reasonix mcp auth --upgrade.
Problem: the latest Studio session-reference settings overlap the persona
port imports and development proxy, leaving this PR in conflict.
Root cause: both features add settings types at the same transport boundary.
Fix: normally merge Studio 8c311b02b, retaining both Persona and SessionRef
contracts, Mock state, SSE methods, routes and settings panels. The upstream
dispatch-consent, bounded-fold and subagent-view implementations stay intact.
Persona prompt assembly, user-only scope, guarded settings and save timing
remain unchanged. The preceding test-only commit synchronizes cancellation
observation without changing the mock provider or relaxing error/close checks.
Verification: formatting, vet, lint, make check, required builtin/boot tests,
full root Go tests with atomic coverage, the full CI race package group and
Desktop Go tests. Frontend typecheck/build and 660 files / 5411 tests pass.
Chromium checks cover persona/settings navigation in Chinese and English,
light/dark and wide/390px layouts; upstream session-reference interactions,
settings rows, font scaling and explorer counts are covered as well.
Problem: the hosted race group failed the mock provider cancellation test
when the next received chunk was an already-pending tool call.
Root cause: cancellation does not prioritize the Done case over a send that
is also ready in select. The test canceled the context and immediately read
again without establishing that the producer had observed cancellation.
Fix: acknowledge cancellation through the test context's Err method before
draining the stream. Retain the strict cancellation-error and subsequent
channel-close assertions, plus the unchanged pre-canceled request test.
The hang guard uses the existing test budget. No mock/product behavior,
runtime prompt, UI, thresholds or workflow approvals change.
Verification: reproduced one failure in 10000 race-instrumented iterations
with GOMAXPROCS=2 before the change; 10000 final iterations pass afterward.
A temporary explicit-marker mutation that ignores cancellation is rejected.
The complete CI race package group passes, alongside gofmt, vet, lint,
make check and builtin/boot tests. Prior frontend/coverage evidence applies
to the unchanged product tree.
The prompt result carried a top-level transcriptPath the ACP schema does not
allow, and status_update / mcpStatus_update went to every client, which
standard SDK clients answer with a method-not-found error each. The two
notifications now need clientCapabilities._meta["reasonix.io"].statusUpdates
and the path moves under _meta["reasonix.io"].
Problem
A development Studio shell started with `pnpm start` (`electron .`) had two
ways to outlive the launch that owned it:
- with its launcher killed outright, the shell and its kernel kept running
with nobody holding them;
- with the launcher's console closed underneath it (a terminal gone, a
background job's reader exited), the next kernel stderr line the shell
echoed raised an EPIPE that nothing handled. Electron's modal "A JavaScript
error occurred in the main process" box then held the main thread, so the
shell stopped draining the kernel and ignored SIGTERM (still alive 30 s
after it, 2/2 runs). The kernel logs a request every few seconds, so this
followed within seconds of the console closing.
Root cause
main.js echoed kernel stderr with a bare process.stderr.write and attached
no error listener, and nothing in the shell watched for the loss of the
process that launched it. The kernel already ends with the shell through its
stdin lease; the shell had no equivalent towards its own launcher.
Fix
- src/launcher.js owns both: watchLauncher ends an unpackaged shell once its
launcher is gone (POSIX reparents an orphan; Windows keeps the dead pid, so
there it is probed), and consoleEcho drops output for a console that failed
or backed up instead of throwing or queueing. shell.log and host.log keep
every line.
- Both relaunch paths (the kernel's relaunch act and the X11 relaunch) hand
the next instance over first: the launcher exits with this process, so a
relaunched shell would otherwise read its own start as the launcher dying.
- shell.log records the shell's and the kernel's pids, which is how the live
check finds them.
Verification
- node --test test/unit.mjs test/linux-packaging.test.mjs: 181 pass, 1 skip.
The new real-pipe test fails (child exits 1 on EPIPE) without consoleEcho.
- pnpm launcher-live (new, real shell + kernel, macOS arm64): launcher
SIGKILL after and before the handshake, closed console then SIGTERM, plain
SIGTERM all pass; with the fix removed the first three fail.
- pnpm smoke: 62 ok, 1 fail ("the bridge exposes verbs and nothing else"),
identical on unmodified studio.
Problem: newer Studio settings UI and persona translations both appended to
the shared English catalog, leaving the PR conflicting. Its new setting-open
notices also lacked a destination for the guarded persona fields.
Root cause: translations for an independent settings domain shared an append
point, and the newly introduced frontend routing table did not know persona.
Fix: normally merge Studio 15de9ec33, preserve both sides' translations and
extract persona's unchanged entries into its own catalog module. Route the
three guarded persona setting ids to their editor and cover actual notice
clicks. Preserve the persona's user scope, supplemental prompt semantics,
save timing, settings navigation and existing authorization boundaries.
Verification: format, vet, lint, make check, complete root Go tests with
atomic coverage, Desktop Go, frontend typecheck/build and 657 files / 5400
tests. Verified all 25 extracted translations unchanged. Chromium navigation
passed in Chinese/English, light/dark and wide/390px layouts; settings rows
and font scaling pass. The later dispatch config/catalog combination also
passes an explicit-marker build overlay. No thresholds or approvals changed.
The final-readiness pause answered session/prompt with the non-standard
stopReason "error", so a client that reads only message text took unverified
work for finished. The turn now ends with end_turn, the typed outcome in _meta
and a host-written [paused] line at the end of the streamed text.
Problem: newer Studio introduces the model-facing setting tool across the
same catalog, boot assembly and controller used by the persona feature.
Root cause: validating only the earlier base would leave their combined
authorization and request contracts untested.
Fix: normally merge Studio 4840df982 and retain the existing guarded persona
entries, user-only loading and supplemental prompt assembly. Add a real
boot.Build effect test for all three persona fields: even in YOLO a model
proposal receives a guarded refusal, produces no approval card, preserves
the user file and discloses no value through refusal or settings listing.
Verification: gofmt, vet, lint, make check, full root Go tests with atomic
coverage, Desktop Go and SDK suites, frontend typecheck/build and 654 files
/ 5384 tests; Chromium settings navigation, row layout and font-scaling
checks in light/dark and wide/narrow layouts. Earlier Electron checks apply
unchanged. No baselines, thresholds, workflow approvals or draft state change.
Problem: the approved persona head failed hosted Go tests and coverage, and
the combined Settings/config files exceeded their line budgets.
Root cause: CI tested synthetic merge 6309314327 against newer Studio
5e53be32a. Its attachment ceiling key had no settings declaration, while
independent additions accumulated in the shared settings/config files.
Fix: normally merge Studio 667f2ca16, including the upstream guarded
attachment-key declaration and ProviderEntry method extraction. Keep all
three guarded persona keys and their user-scope/prompt behavior. Extract
the unchanged settings navigation into SettingsNav, retaining keyboard
navigation, status values and the new version dot. Assert that the
attachment storage ceiling stays guarded.
Verification: gofmt, vet, lint, make check, complete root Go tests with
atomic coverage, independent Desktop Go tests, Electron tests, frontend
typecheck/build and 654 files / 5381 tests. Chromium fs-scale and settings
row guards pass, with light/dark 1280px/390px navigation checks. No budgets,
test thresholds, workflow approvals or draft state are changed.
stopReason "error" is not in the ACP prompt-turn enumeration, and the cause
travelled only on a vendor notification, so a text-only client saw an
unexplained failure. The turn now ends with -32603, the redacted cause as the
message and data.code turn_failed.
Problem: remote sidebar mutations could dial an unconfigured SSH target,
accept unbounded JSON, and enable hover actions without a user choice.
Root cause: a missing host resolved to an empty workspace without an error;
remote JSON handlers lacked a shared size boundary, and the preference
treated every value except explicit off as enabled.
Fix: classify unknown hosts before link reuse or acquisition, share a
64 KiB single-body decoder across remote writes, and restore explicit-on
hover preferences with a safe storage-error default. Preserve configured
hosts, far-kernel refusals, menu access and declared workspace resolution.
Align synchronization fixtures and keep translated directory prose in the
interface font.
Verification: failing-first unknown-host and oversized-body regressions;
full serve, builtin and boot tests; 5389 frontend tests; production build;
gofmt, root go vet, make lint and make check; isolated browser interaction
checks for default-off, opt-in persistence, menu access and opt-out.
Problem: The SSH project menu re-derived a default from the project list when
the host response omitted workspace.
Root cause: A client fallback duplicated the kernel's configured-default rule.
Fix: Pass the host response's workspace directly to the menu. Align the fixture
with the server response and cover both an explicit default that differs from
list order and an omitted default that must stay unset.
Verification: The omitted-default regression failed before repair. All 17
remote project/worktree interaction tests and the production build passed.
gofmt, root go vet, make lint, make check and builtin/boot tests passed.
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.
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.
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: 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: 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
# An unsigned package is not a lesser APK, it is an unusable one: Android's
# package manager rejects it outright rather than reporting a signature
# mismatch. Say so where the run summary can be read, instead of letting a
# green build imply a shippable artifact.
- name:Say what an unsigned build means
if:steps.key.outputs.signed != 'true'
run:|
echo "::warning::no signing key configured, so this APK is unsigned. Android refuses to install an unsigned package, so the release will carry no APK; the file stays a workflow artifact for whoever signs it."
{
echo "### No APK on this release"
echo ""
echo "The build produced an unsigned APK, which Android refuses to install."
echo "It is attached to this run as a workflow artifact only. Add the four"
go test -count=1 -timeout=3m -json -run '^Test(CaptureDirectoryMaterializesContainedLinks|CaptureDirectoryRefusesUnsafeLinks|CaptureHookFileFlattensContainedLink|MaterializeStillRejectsLinkedRetainedFilesAndRoot|WindowsCompositionJunctions|WindowsLiveSkillJunctions|CompositionRetainsLinkedSkillAcrossRestart|CompositionLinkedSkillRejectsLinkedResources|CompositionLinkedExtensionResources|CompositionLinkedHookResources)$' ./internal/state/agentpreset ./internal/ext/skill ./internal/assembly/boot | tee "$RUNNER_TEMP/composition-links.jsonl"
go test -count=1 -timeout=3m -json -run '^Test(UsageScope.*)$' ./internal/contract/event ./internal/session/control ./internal/assembly/boot | tee "$RUNNER_TEMP/goal-usage-scope.jsonl"
jq -se '
["TestUsageScopeKeepsEarlierEventOwner",
"TestUsageScopeWithoutCapability" ] as $required |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $passed |
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
all(.[]; .Action != "fail" and .Action != "skip") and
go test -count=1 -timeout=3m -json -run '^Test(PreparedRootBudget|CoordinatorRootBudget|EffectCoordinatorRoot)' ./internal/runtime/agent ./internal/runtime/coordinator | tee "$RUNNER_TEMP/coordinator-root-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/runtime/coordinator"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(GoalResumeRenewsPlannerBudget|GoalNonBudgetResumeKeepsPlannerBudget|GoalTokenBudgetPausesAndResumes|EffectGoalResumeRenewsPlannerBudget)$' ./internal/session/control ./internal/assembly/boot | tee "$RUNNER_TEMP/goal-planner-resume.jsonl"
go test -count=1 -timeout=3m -json -run '^Test(RootWall|EffectRootWall)' ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/root-wall-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RootToken|EffectRootToken|BudgetCountsKnownReportedTokens|ReportedToken)' ./internal/runtime/agent | tee "$RUNNER_TEMP/root-token-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/runtime/agent"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RequestOnlyUsage|RunBudgetMissingUsage|TaskBudgetKnownCost|RunBudgetExplicitZero|TaskBudgetUsageCompleteness|TaskBudgetCostUses|EffectBudgetCompleteness|TaskBudgetGateIgnoresCostWhenUnpriced)' ./internal/contract/provider ./internal/runtime/agent ./internal/assembly/boot | tee "$RUNNER_TEMP/budget-usage.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/contract/provider", "reasonix/internal/runtime/agent", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(HostStorageEnvironment|HostObjectStorage|HostExplicitStorageControls|DetachedStorageEnvironment)$' ./internal/platform/gitcmd ./internal/platform/worktree ./internal/assembly/boot | tee "$RUNNER_TEMP/git-storage-environment.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/platform/gitcmd", "reasonix/internal/platform/worktree", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(InterruptedUsage|EffectAnthropicPartialUsageRecovery)' ./internal/model/anthropic ./internal/assembly/boot | tee "$RUNNER_TEMP/anthropic-interrupted-usage.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/model/anthropic", "reasonix/internal/assembly/boot"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(RootWebSearch|EffectRootWebSearch)' ./internal/tools/websearch | tee "$RUNNER_TEMP/root-websearch-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/tools/websearch"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^(TestProfileRootBudgetLifetime|TestProfileBudgetPreparationUsesTurnAdmission|TestEffectExplicitSkillRootBudget)$' ./internal/session/control | tee "$RUNNER_TEMP/explicit-skill-root-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
(["reasonix/internal/session/control"] - $packages | length) == 0 and
go test -count=1 -timeout=3m -json -run '^Test(LocalBudgetUsesOccurrenceQuote|LocalCost.*|UsageQuote.*)$' ./internal/contract/event ./internal/runtime/agent | tee "$RUNNER_TEMP/local-cost-quote.jsonl"
go test -count=1 -timeout=3m -json -run '^Test(AuxiliaryUsageObserver|BoundedUsageObserver|RootBounded|EffectRootBounded)' ./internal/contract/provider ./internal/model/boundedllm ./internal/runtime/agent | tee "$RUNNER_TEMP/bounded-root-budget.jsonl"
jq -se '
[.[] | select(.Test == null and .Action == "pass") | .Package] as $packages |
[.[] | select(.Test != null and .Action == "pass") | .Test] as $tests |
all(.[]; .Action != "skip" and .Action != "fail") and
bwrap --ro-bind / / --dev /dev --proc /proc -- true || echo "::warning::bubblewrap is installed but this runner denies user namespaces; unavailable-backend tests will run fail-closed"
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.
@@ -159,6 +169,19 @@ Every package ships with a `.minisig` signature, and the release carries
The same release also carries the 2.x `reasonix` CLI as archives for
`darwin|linux|windows × amd64|arm64`.
The Android build ships on that release as `ReasonixMobile-<version>.apk`
(`arm64`, Android 8.0 or newer). The kernel and the page run on the phone, and
the agent reaches your servers over SSH; pairing with a computer is done by
scanning the code it shows.
The APK is signed with the Android release key rather than minisign, so it
carries a `.sha256` and no `.minisig`.
When that key is absent the build is named
`ReasonixMobile-<version>-unsigned.apk`. Android refuses to install an unsigned
package, so those builds stay a workflow artifact for whoever wants to sign one,
and the release carries no APK at all.
### Reasonix 1.x (stable)
The 1.x CLI installs through npm on any supported platform, or Homebrew on
@@ -240,12 +263,14 @@ For advanced CLI usage and configuration, see the **[CLI reference](./docs/CLI.m
`verify-page.sh` is the one to run after a change: it starts the kernel with the
same arguments `Kernel.kt` uses (`--auth token`, `--token-file`, `--port-file`,
`-page`) and checks that it binds a port and answers 200. Without a device, that
is as deep as the chain can be verified.
## Where the code is
| File | Owns |
| --- | --- |
| `MainActivity.kt` | The WebView, the back gesture, downloads, external links, renderer restart |
| `Kernel.kt` | Unpacking the page, starting and stopping the kernel, waiting for the port, health probes, logs, the workspace directory |
| `KernelService.kt` | The foreground service that keeps a long turn alive |
| `NativeBridge.kt` | `@JavascriptInterface`: what the page may ask the phone for |
| `BrowserShellServer.kt` | Loopback HTTP server the kernel's browser tools drive the overlay WebView through |
| `Downloader.kt` | Writing a kernel-produced file through the system document picker |
The page reaches the shell through `window.reasonixHost` and `ReasonixNative`,
defined in `desktop/frontend-next/src/port/host.ts` (`AndroidHost`).
**Phone and desktop share one page build.** What differs only on a phone is
decided at runtime, not by a second set of components. `phoneShell()` in
`src/port/host.ts` and the `[data-shell="android"]` selector in `mobile.css` both
read `<html data-shell="android">`, which `index.html` sets before the bundle
loads.
One attribute feeding both is what keeps style and components from disagreeing
about what a phone is.
## What the page may ask the phone for
`NativeBridge` is reachable from whatever document the WebView holds, so every
method that touches the device asks `trusted()` first.
The activity answers by comparing the **main frame's** origin against the
kernel's and against the one paired computer the person scanned a code for. A
page that navigated elsewhere is not trusted, even though it may still hold the
object.
Five methods answer without the check — `version`, `systemTheme`, `safeArea`,
`setLayerOpen`, `setRunning` — because they report back what the page already
knows about its own window and grant nothing.
`openInApp(url)` is the pairing decision, not a navigation one: it loads a URL in
this window only for the origin the scanner just returned, and only once.
Without that, any page holding the kernel origin could hand itself the bridge.
External links leave through the browser. A non-web scheme is routed to the
system only for `mailto:` and `tel:`; everything else is dropped, because an
`intent://` URL can start an arbitrary exported activity with caller-supplied
extras.
## Permissions, and what they cost
`MANAGE_EXTERNAL_STORAGE` — opening a folder on the phone as a workspace. The
kernel is a Go binary: it opens real paths with `open(2)`, and SAF hands back a
`content://` URI it cannot open.
Requested from the folder-picker action, never at launch. Android shows no dialog
for it and opens system settings instead.
**There is no OS-level sandbox around this kernel.** The PR sets the sandbox to
off on Android, so confining the agent to the chosen folder is Reasonix's own
write-root check and nothing else. A shell tool with all-files access can reach
all shared storage.
`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` — Doze freezes the app group, which stops
a turn that is still running. Asked once when a turn actually runs, never at
launch, and the person can refuse. Refusing costs battery life during long turns
and nothing else.
**Google Play restricts this permission**: fine for a sideloaded build, a
store-policy risk if this is ever listed.
`FOREGROUND_SERVICE` / `FOREGROUND_SERVICE_DATA_SYNC` — keeping the kernel alive
while the screen is off. `dataSync` is capped per 24 hours from Android 15. It is
the closest honest description of "an agent is working".
`CAMERA` — reading the pairing code a computer shows. Requested when the person
asks to scan, never at launch. `ScanActivity` is not exported.
`READ_EXTERNAL_STORAGE` / `WRITE_EXTERNAL_STORAGE` — the same folder walk below
API 33. `maxSdkVersion`-gated; above 33 they are superseded by the permission
above.
`allowBackup=false` with explicit extraction rules, so a `keystore` or a token in
app storage never leaves the device in a cloud backup.
## Cleartext HTTP
The kernel serves its page on `127.0.0.1:<port>`, and a paired computer serves
the same page over its LAN address. Both are plain HTTP.
Neither address can be written into a `network-security-config`: the port is
chosen at start, the LAN address at scan time, and the format matches literals
and domains, not subnets.
So the config permits cleartext and the boundary is in the code instead.
`shouldOverrideUrlLoading` sends every other http(s) origin to the browser, and
the bridge answers only the kernel and the just-paired computer. A plaintext page
on the LAN cannot become the document holding the bridge.
## What goes into the APK
| Content | Size | Why it is shaped this way |
| --- | --- | --- |
| `lib/arm64-v8a/libreasonix.so` | 45 MB | Go static binary, stripped. It is a `.so` rather than a file in `assets/` because only `nativeLibraryDir` is guaranteed executable — a binary copied to app storage fails on a device whose data partition is mounted `noexec`. |
| `assets/page/` | 14 MB | The frontend build, including the Inter and Noto Sans SC fonts, `noCompress` |
| `assets/LICENSE` | 1 KB | The repository's MIT licence, verbatim. MIT requires the notice and the licence text to accompany a binary distribution. |
`assets/LICENSE` is generated by `sync-page.sh` from the repository's root
`LICENSE`, not committed a second time: a checked-in copy drifts when the root
file is updated.
It sits in `assets/` rather than `assets/page/` because that directory is removed
and rebuilt on every build.
A release APK is about 31 MB.
## Building
**The Gradle wrapper is committed**, jar and all. `build.sh` refuses to run
without `gradlew` rather than generating one from whatever `gradle` is on `PATH`:
the dependency versions are part of the build.
`gradle-wrapper.properties` pins `distributionSha256Sum`, so a mirror that serves
a different distribution zip fails verification instead of being unpacked and
run. Dependency repositories are the official ones only — a mirror belongs in a
user-level init script.
**Signing.** A release with no key configured comes out **unsigned**
(`app-release-unsigned.apk`) with a warning. It used to fall back to the debug
key, which produced an artifact that looked shippable and was signed by a
per-machine key nobody chose: it could not install over the previous build.
Point the build at a real key by writing `keystore/keystore.properties`
(`storeFile`, `storePassword`, `keyAlias`, and optionally `keyPassword`) or by
setting the matching `REASONIX_KEYSTORE*` environment variables. Key material
stays out of the repository and out of build logs.
Debug builds always use the debug key. Signing a debug build with the release key
would let anyone who can read the keystore produce a build that installs over the
shipped one under the same signature.
**Non-ASCII checkout paths.** AGP refuses to build under a path with non-ASCII
characters on Windows unless `android.overridePathCheck=true` is set. That is a
property of one machine's disk layout, not of this project, so it is not in the
repository's `gradle.properties`.
Set it in a user-level `~/.gradle/gradle.properties` or pass
// The extraction script: prefer article/main for content pages,
// fall back to body. innerText preserves visual line breaks. It
// also stamps a ref on every element a person could act on, so the
// kernel can name one in an act step the way the desktop backend
// names an accessibility node. A ref already on an element is kept,
// so it stays the same across snapshots of one document; a fresh
// document starts the numbering over, and the kernel only accepts a
// ref from the snapshot it last read.
valscript="""
(function(){
var seq = window.__rxRefSeq || 0;
var used = {};
var marked = document.querySelectorAll('[data-rx-ref]');
for (var i = 0; i < marked.length; i++) { used[marked[i].getAttribute('data-rx-ref')] = true; }
function roleOf(el) {
var tag = el.tagName.toLowerCase();
var role = (el.getAttribute('role') || '').toLowerCase();
if (role) return role;
if (tag === 'a') return 'link';
if (tag === 'button') return 'button';
if (tag === 'select') return 'combobox';
if (tag === 'textarea') return 'textbox';
if (tag === 'input') {
var t = (el.getAttribute('type') || 'text').toLowerCase();
if (t === 'checkbox' || t === 'radio' || t === 'submit' || t === 'button' || t === 'reset') return t;
return 'textbox';
}
return 'generic';
}
function nameOf(el) {
var n = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('name') || '';
if (!n) { n = (el.innerText || el.textContent || '').trim(); }
return n.replace(/\s+/g, ' ').slice(0, 160);
}
var sel = 'a[href],button,input,select,textarea,[role=button],[role=link],[role=checkbox],[role=radio],[role=tab],[role=menuitem],[contenteditable=true]';
var els = document.querySelectorAll(sel);
var elements = [];
for (var j = 0; j < els.length; j++) {
var el = els[j];
var ref = el.getAttribute('data-rx-ref');
if (!ref) {
do { seq++; ref = 'e' + seq; } while (used[ref]);
el.setAttribute('data-rx-ref', ref);
used[ref] = true;
}
var raw = (el.value != null && String(el.value) !== '') ? String(el.value).slice(0, 160) : '';
Five harnesses live under `benchmarks/`; `cmd/e2ebench` also exposes a
Six harnesses live under `benchmarks/`; `cmd/e2ebench` also exposes a
SWE-bench Verified mode:
-`e2e/` — the committed end-to-end task suite, driven by
@@ -19,6 +19,10 @@ SWE-bench Verified mode:
-`fanout-width/` — a shape-instructed fleet whose members share nothing, for
pricing what running them at the same time is worth. See
[Pricing the run graph](#pricing-the-run-graph) below.
-`realrepo/` — long tasks over a pinned snapshot of this repository, for
measuring changes that cut model round trips (13–31 requests per task on
`deepseek-flash`). See
[its README](realrepo/README.md).
## Directory layout
@@ -416,7 +420,7 @@ own outcome).
| Flag | Default | Purpose |
| --- | --- | --- |
| `-mode` | `suite` | `suite` \| `diff` \| `swebench` \| `compare` \| `traj` (`diff` generates tests for the PR diff; `swebench` runs the official per-instance evaluation; `compare` renders KPI/Pareto readouts from 2+ `-json` reports; `traj` re-digests recorded trajectory files without spending tokens). |
| `-mode` | `suite` | `suite` \| `diff` \| `swebench` \| `compare` \| `traj` (`diff` generates tests for the PR diff; `swebench` runs the official per-instance evaluation; `compare` renders KPI/Pareto readouts from 2+ `-json` reports; `traj` re-digests recorded trajectory files without spending tokens; `audit` checks a batch's published figures against its retained rows). |
| `-task` | *(all)* | Suite mode: run only these comma-separated task IDs (e.g. `-task fix-add-bug`); unknown IDs fail with the available list. |
| `-attempts` | `1` | Suite and diff modes: retry a task until an attempt passes, up to N; enables the `Pass@≤N` KPI, and TTCS charges a retried solve with its failed attempts' wall. |
@@ -429,6 +433,7 @@ own outcome).
| `-ablate` | *(none)* | Ablation arm: comma-separated subsystems to switch off — `evidence`, `planner`, `subagent`, `retrieval`, `compaction`, `upstream` and `recall-search`; `none` \| `all`. |
| `-json` | *(none)* | Write the JSON report here (optional). |
| `-batch` | *(none)* | Suite mode: write `results.jsonl` (one row per attempt) and `batch.json` (manifest v1) here, so the report's figures can be audited later. Audit mode: read the batch from here. See [Auditing a batch](#auditing-a-batch). |
| `-trajectories` | *(none)* | Suite mode: write one `<task-id>.trajectory.jsonl` per task into this directory (the agent's full event stream with timestamps — see `reasonix run --trajectory`). The report gains a time-attribution line (tools vs. model) and each JSON result a `trajectory` digest. |
| `-force-planner` | `false` | Suite mode: prefix each prompt with a plan-first directive so the two-model turn engages regardless of the planner gate. Use for the "with planner" arm of an A/B; results carry `plan_forced` so arms are only comparable with equal forcing. |
| `-anchor` | `blind` | Suite mode: which hypothesis the agent holds before it looks at anything — `blind` (none, the control) \| `correct` \| `wrong`. The seeded arms prefix each prompt with the task's authored seed and **skip** tasks that have none, so an unseeded control run never lands in a seeded denominator. Results carry `anchor`. See [Anchor resistance](#anchor-resistance). |
from `results.jsonl`, prints the manifest's claim beside the recomputed value,
and exits non-zero naming any figure that moved. It also refuses a batch whose
manifest is missing, records no commit, or whose rows are truncated.
This is an **aggregation audit, not a replay**: it proves the published figures
follow from the retained rows. It does not re-execute the model, tools or
environment, and cannot show the experiment would reproduce. A real replay would
need the commit, a corpus fingerprint, the model and provider configuration, the
seed, the flags, the raw results and the runtime version; it is not implemented.
`schema_version` is top level. The rest is three groups:
| Group | Fields | Answers |
| --- | --- | --- |
| `over` | `command.mode` and `command.axes` (every flag that changes what a figure means; paths omitted); `tasks.count` and `tasks.digest` (sha256 over sorted, NUL-separated task ids — the corpus identity, never the task text); `filters`; `denominator` (`unit`, `definition`, `value`) | what the figures are over |
| `producer` | `commit` (or an explicit `unknown`, never a guess), `commit_source`, `dirty`, `model`, `provider`, `seed`, `started_at`, `finished_at`, `harness_version`, `go_version` | which run produced them |
| `trust` | `results_sha256`, `claimed` (the headline figures), `ledger`, `retention` | why they can still be checked |
The ledger accounts for every planned run, and the audit checks that the counts
form a funnel (`scored ≤ completed ≤ attempted`, `failed ≤ scored`,
`scored = pass1 + failed`, `rows = attempted + skipped`). A row count is never a
denominator: `denominator.value` is a count of scored tasks, and `rows` is
published only to show what was retained.
| Count | Meaning |
| --- | --- |
| `planned` | graded runs the batch set out to attempt: filtered tasks × trials |
| `attempted` | retained rows for runs that started (skipped rows are not attempts) |
| `completed` | attempts that ended and left a row; this harness writes a row only after an attempt ends, so it equals `attempted` and the equality is checked, not assumed |
| `scored` | completed first attempts of solvable, non-skipped tasks — the pass@1 denominator |
| `failed` | scored attempts the grader did not pass |
| `unmeasured` | attempts whose cost and tokens are unknown because no metrics landed; kept out of spend totals, never counted as zero |
| `excluded` | rows kept out of the accuracy denominator: skipped, `no_solution`, and retry entries |
Spend and cache fields are omitted from `claimed` when nothing was measured, so
an absent figure reads as unknown rather than as a measured zero. `seed` is
omitted for the same reason: the harness does not set a sampling seed.
`results_sha256` covers the values and the presence of every row; line endings,
indentation and key order are normalised away because none of them is evidence,
while number spelling is kept (a rewritten `1` is not `1.0`).
A successful audit records the verifier version (`audit.verifier_version`) and
the digest it checked in `batch.json`, so a re-verification can tell which code
checked the batch.
`results.jsonl` is retained unredacted beside the manifest — task prompts and
agent notes included — and is not public. The manifest itself carries a task-id
digest, no prompt text, no absolute paths, no endpoints or keys and no model
output. A claim whose rows are gone cannot be audited: there is nothing to
recompute from, and this tool will not certify it.
## Dataset retention
Keep every `-json` report and`-trajectories` directory from real runs: they
are the accumulating corpus — per-task contracts-to-be, full event
Keep every `-json` report,`-trajectories` directory and `-batch` directory from
real runs: they are the accumulating corpus — per-task contracts-to-be, full event
trajectories, checkpoint oracle verdicts, stop curves and phase traces — that
any future offline learning (routing, stop policies, budgets) would train
and evaluate on. The control plane stays deterministic and interpretable
Prompt:"What ownership token did we settle on for the retry boundary, and under what tag did we file the reason for not moving retries above framing?",
go run ./cmd/e2ebench -suite benchmarks/realrepo -bin "$PWD/bin/reasonix" -task rt-stale-skills
```
The agent reads its provider config from `REASONIX_HOME` (default
`~/.reasonix`); point it at a disposable one to keep benchmark sessions out of
your own.
The default `-budget` (800k tokens) can stop the suite partway through, hence
`-budget 0`. Under `-trials`, each trial keeps its own
`<task>.trial-N.trajectory.jsonl`.
## The snapshot
Every `workdir/` is the same Go module (`module reasonix`, stdlib only, no
network needed to build or test):
- **Pinned code.** `git archive` of commit `42636ddcb425` for
`internal/contract/{pricing,ablation,agentgraph}` and
`internal/base/{retrieval,rulelist,tokencount,neterr,nilutil}` — about 5k
lines of real kernel code with its real tests and doc comments.
- **Fixture-authored files** in `base/`: `go.mod`, a trimmed `REASONIX.md` (the
project's own conventions, so the agent loads standing instructions the way
it does here), a `Makefile` with `check`, a module `README.md`, the
`cmd/benchctl` command that consumes `pricing` and `ablation` from outside
their package, and one project skill.
- **One pinned test anchored to the snapshot day.**
`TestCatalogPricesHaveBeenCheckedRecently` compares each row's `CheckedOn`
with the wall clock, which would fail every workdir 90 days after the oldest
pinned date (2026-11-20). `base/` replaces that file with one that reads
staleness at 2026-10-19 instead; nothing else in the pin is edited.
- **Per-task overlay** in `overlay/<task-id>/`, applied last. Only
`rt-stale-skills` has one.
`snapshot.sh` rebuilds every workdir from those three sources and
`snapshot.sh --check` fails if a committed workdir has drifted from them. Edit
`base/` or `overlay/`, never a workdir, then rerun the script. Moving the pin is
a new suite version: results from two pins do not compare.
## Tasks
| Task | Kind | What it exercises |
| --- | --- | --- |
| `rt-cache-write-rate` | multi-file Go change, test, README | Adds a `CacheWrite` rate to `RateCard` and threads it through cost, fingerprint, card identity and the catalog, then prices Anthropic's `claude-sonnet-4-5` with it: a new `officialRates` row, documentation and checked-on constants, an endpoint host, and `benchctl rate`/`cost` output. The fingerprint has to stay byte-identical for every existing card, so the change to `PricingFingerprint` cannot be the obvious one. |
| `rt-stale-skills` | documentation only, no recognised verifier | Three project skills name paths, functions, `make` targets and `benchctl` subcommands and flags that do not exist, and one contradicts the append-only rule in `REASONIX.md`. Fixing them takes reading `rates.go`, `ablation.go`, both `benchctl` files and the `Makefile`, then editing only Markdown — the shape behind #13293, where `stale_verification` stays owed after a skill edit because no check covers it. |
| `rt-rate-lookup-signatures` | API migration with a likely compile-error loop | Changes `CurrentRate` to `(RateCard, bool)`, `LookupCatalog` to a `CatalogKey` struct, and `IsOfficialCard`, `MatchesCatalog` and `LiveTariff` to a `ModelRef`: about 20 call sites across 11 files. Most are in `_test.go` files and dereference the old pointer, so `go build ./...` can pass while `go vet` and `go test` fail. |
| `rt-plan-ablation-module` | plan-mode with `complete_step` | Five numbered steps adding a `memory` ablation module, summaries, `ModuleHelp` and `benchctl arm -list`. The prompt opens with the host's own plan-first marker (`control.PlannerRouteMarker`), which routes the turn through the planner, and asks for each step to be signed off before the next. An existing test pins the `all` arm name and has to be updated, which adds a red-test step. |
Every grader stages hidden tests and checks after the run: `gofmt -l`, `go vet
./...` and `go test ./...` for the Go tasks; for `rt-stale-skills`, every
backticked path, identifier, `make` target and `benchctl` command line in each
skill must resolve against the tree, and a SHA-256 manifest proves no Go source, `go.mod`,
`Makefile` or `REASONIX.md` changed. Each task fails its pristine seed and
passes its `solution/`, both checked by `cmd/e2ebench`'s corpus tests.
## Reading the numbers
```text
Metric: requests per task
Anchor: the trajectory digest's requests_by_source, summed over sources
Population: every graded trial of a task that ran to its own stop
Excludes: tasks the token budget skipped, and runs that hit timeout_sec
```
Requests is the headline because round trips are what these tasks exist to
move. Read it beside the solve rate and the report's round outcomes: a change
that drops requests by giving up on verification has not cut a round trip, it
has cut a check. `rt-stale-skills` is read for whether the run finishes at all
and how many rounds go to re-verifying after its last write.
## Baseline
`deepseek-flash`, 2 trials per task, at studio `0b8de9e63`. Requests are the
trajectory's executor requests; all 8 runs ran to their own stop.
t.Fatal("api.anthropic.com is not recognised as Anthropic's endpoint")
}
}
Loaded 100 of 4672 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.