Repository navigation
[fix] Close the regressions the mobile-extraction lane introduced - #6112
Merged
ardaerzin merged 537 commits intoAug 20, 2026
Merged
Conversation
A hand-added model id absent from Pi's registry is registered into the per-run models.json with its base model's real metadata, so it runs as a first-class model; the operator's mounted Pi folder is never written. The Pi probe also names which provider logins the mount holds (OAuth entries only, family names only), the service passes them through a closed allow-list, and subscription pairs derive from the live answer, which is how a ChatGPT login in Pi becomes a visible pair.
The connect gate opens the providers drawer directly, and a connection created from onboarding auto-switches the agent to its first model and commits, leaving the user typing; a second connection added mid-session never touches the current pick. The dead duplicate gate hook is deleted rather than synced. The settings table matches its siblings (Provider and Name columns, Created, an actions menu), Preferences reads Feature flags with a Developer mode toggle describing exactly what it gates, the eval stepper's all-rows hover highlight is fixed at its CSS root, and the dev api gains the graceful-shutdown bound that ends the reload deadlock.
The onboarding auto-switch pulses the config section through the shared draft signal; the new origin keeps it out of the approval dock's Undo.
The runner gains an authenticated GET /subscription-status that inspects the mounted login location per harness (codex, claude, pi_core, pi_agenta) and returns only state words. The Python agent service proxies it at POST /runtime/subscription-status behind the same auth as invoke, mapping network failure to unavailable and an old runner to incompatible. The self-managed credential card shows the status for the selected harness, polls while visible, and offers Check again. The status never claims provider access; a run remains the proof.
…check by provider The fake secrets DAO discarded project_id and organization_id, so every test shared one namespace: provider numbering and saved-policy carry-over could cross a project boundary and the naming test only passed because of it. Records now carry their scope and every read filters on it, with two cases pinning the boundary. The catalog subset check unioned every provider's catalog ids, so a default under one provider could pass on an id that only exists under another. Scoped to the entry's own provider. Also shortens a two-line comment in the configure-provider modal to one, per the repo rule.
… and provider The public status proxy allow-listed state words and the `providers` list, but two runner-controlled strings still passed through untouched: the harness map's KEY, which becomes an object key in the browser's JSON, and the singular `provider`. A future or faulty runner could put an account name, a path, or a credential in either. Both are now closed sets. Keys must name a known harness (`HarnessKind`) and unknown ones are dropped, which also caps the map — a runner cannot push more entries than there are harnesses. `provider` gets the same treatment `providers` already had: a family the card cannot render is dropped rather than failing the entry, since the state word is still good. Both sets already match what the runner sends (its own SUBSCRIPTION_HARNESSES and its constant provider values), so this narrows the contract without changing any real response.
…el spelling Five review findings on the provider-connections surface: - The vault shadow filter compared provider kinds raw, so MISTRALAI_API_KEY survived next to a stored `mistral` connection and — locals coming first in the combined list — then won the resolver's tiebreak, beating the key the user saved. Both sides now normalize through a shared `normalize_provider_kind`, moved to `sdk/utils/providers.py` because the manager already imports the vault middleware and could not be imported back. - The family tiebreak fell through to the first record, which could be one the user narrowed away from the requested model while a later list-less record still offered it. A record with no saved list follows Agenta's defaults, so it now wins over a narrowed one; an explicit `[]` still means "offer nothing" and wins nothing. - The probe applied its 10s budget per httpx request, and OpenRouter issues two, so the worst case behind the button was double. The budget now covers the whole probe; running out of it maps to `unknown` + `failed`, which is what it honestly proved. - The playground picker offered a credential-set connection its bare saved slugs when it carried both spellings, but the SDK matches those connections on `model_keys` only, so the pick resolved to no provider settings. - `together_ai/moonshotai/Kimi-K2-Instruct` is gone from Together's serverless API and from the agent catalog; replaced with `Kimi-K2.6`, which the agent catalog carries. Plus a doc comment that stated the opposite of the save rule, and a catalog call moved inside its own try so an exception degrades to an empty set instead of failing module collection.
…del-plan docs: plan provider connection model lists
…tus-plan docs: plan runner subscription status path
The failed-drive state rendered a full antd Alert — a bordered amber box across a ~350px pane, and the only Alert in the Drives surface. It now uses the same centred icon + two lines as the empty state beside it, with only the glyph carrying the warning tone. The search/filter toolbar is hidden in both terminal states: there is nothing to search or filter, and its bottom border made an empty pane read as broken chrome. It stays during loading, where content is still coming.
RightPanelSplit nests — the Inspector split sits inside the Files split's chat column — so a collapsed panel kept painting its hairline ~1px from the open one's, reading as a double border. A collapsed panel now hides its bar, via visibility so the open panel's slide is undisturbed, and holds it until the close slide finishes so the line leaves with the pane.
Each id item carried its raw uuid on a second line, making every item two lines tall and stretching the menu to uuid width. The value still goes to the clipboard, which is the item's whole job, so the label alone is enough. The ids are a debugging affordance, so they now ride the same playground-inspector switch as the session inspector and the turn inspector. With it off the menu is just "Download all".
feat(web): the AI providers experience - settings, picker, and run-path wiring
feat(agents): runner subscription status on the credential card
feat(vault): provider connections carry models, harnesses, and stable slugs
fix(frontend): tidy the files pane chrome in the agent playground
The banner used --ag-c-F5F7FA, which the warm recolor repointed at the same value as the rail ground (#f6f5f3), so the card had no edge at all. Give it the elevated surface plus the shell hairline, the same language the rail's selected nav pill already uses. The container was also a fixed 215px centred box, so it drifted further from the nav rows the wider the resizable rail got. Make it track the rail instead.
The menu was a fixed 220px panel, so on a narrow rail it hung out over the content area and on a wide one it sat well short of the trigger. antd pins the popup root's min-width to the trigger, so w-full makes it exactly as wide as the button at any rail width; collapsed keeps a readable fixed width. box-border because preflight is off here — w-full plus a border overflowed 2px. The trigger's own px-1.5 stacked on the wrapper padding and left the avatar 6px inside the nav icon column; px-3 puts it on the column, and trimming the panel padding does the same for the menu's own rows.
…button Review follow-up. The dismiss button washed itself with bg-black/5 on hover and focus, which is invisible against the elevated surface the card now uses in dark mode. Swap it for colorFillTertiary, and take the title and icon colour from colorText rather than the gray-900 scale. Also make the banner wrapper's box-border explicit. The app only gets border-box from antd's '.ant-layout *' reset, and that does not reach portalled trees, so a w-full element with padding should not rely on inheriting it.
fix(observability): report failure rate as a percentage
fix(frontend): Make the sidebar banner and switcher menu fit the rail
docs: add iamhaseebn as a contributor for code
…tests test(runner): platform-guidance tests learn the always-on file-citation sentence
Prioritised WP-3 by lane churn: the agenta-ui diff against 112.2 is dominated by files the lane MOVED into the package, and date-range-picker (633 lines) was the biggest interactive one. The Observability range panel gave no indication of which range was applied. Prod paints the active row with the primary fill; local painted nothing, and the first row showed the browser's blue focus ring instead of the app's. 112.2 renders this from Filters/Sort.tsx, which sets popupSelectedItem when sort matches, so the extraction dropped it. The state was never wrong - aria-checked reads true at runtime and the JSX already branches on it. The bug is one layer down: @agenta/ui/styles exports a cn that is a plain concatenator while @agenta/ui/components/ui/utils exports one built on extendTailwindMerge. rowClassName set bg-transparent unconditionally and bg-secondary conditionally, which only works under tailwind-merge; with the concatenator both classes land and CSS order left every row transparent. Fixed by making the base conditional rather than swapping cn, which would start deduping every other class in the file and needs its own verification. Also gave the rows the kit focus treatment, since they are real buttons where prod's were inert divs and preflight is off. Verified live. Checked the blast radius too: 35 files import the non-merging cn, three combine conditional and unconditional colour classes, and all three are safe on inspection - so this was the only live instance.
…ct still uncompared CheckboxTree powers an Edit columns control prod does not have, so it is an addition rather than drift and the useful test is whether it works. Driven: the popover opens with the full tree and unchecking Cost removes it from the header set. Then had to undo my own trap - that toggle persists to observability-table-columns, the key 4h warns about, and my restore click missed, leaving local a column short until I cleared the keys. Anything that writes a table preference must be cleaned up in the same breath. Root/LLM/All is the DriveExplorer pattern again: prod renders labels, local buttons, same control and local more accessible. Same for the unlabelled ant-switch vs a labelled Auto-refresh button. The filter panel is at parity - same box, same structure - and prod is only taller because it carries a saved filter. But I did not manage to open the TreeSelect dropdown itself: several attempts hit the wrong control or dismissed the panel, so the 645-line file this was meant to exercise is still uncompared. Recorded as uncompared rather than implied covered. Cross-check worth keeping: prod's condition dropdown highlights its selected option with the olive primary fill, which is the convention U-01 restored.
TreeSelect opened properly once the press was scoped to the panel and done as one open-panel/open-field/read sequence; the earlier failure was my coordinate-guessing, not the component. Both trees render the same thirteen entries, icons, submenu chevrons and hover treatment, same y and height. Prod's panel is 180 wide against local's 148 and nothing truncates on either, so the 32px is cosmetic. Agent Home first read 5.20% across 221 regions, almost all of it because prod had two pending approvals and local none, so only prod rendered the waiting badge, the WAITING ON YOU group and the Approval chips. Instead of writing that off as data I drove an approval on local; it then renders the same badge, group, chip, RECENT divider and row layout. Both builds carry the same five sections. Method note kept: the first read would have supported either 'local dropped the waiting group' or 'it is just data'. Producing the state on the quiet build is what settled it, and it exercises the code besides.
Opened the drawer on both from the first traces row. Same 1200px width and the same control set in the same order, and the right panel matches section for section - Annotations with its empty text, Trace info with Type and Status. The 4.55% is the trace payloads, which are different traces. Left inconclusive rather than filed: local's right panel scrolls with a 6px classic scrollbar where prod shows none. But prod has seven overflow-y auto containers and none is currently overflowing, because its trace carries less content - so 'prod hides its bar' and 'prod had nothing to scroll' are indistinguishable here. Worth resolving since 112.2 moved the config pane onto ag-scroll-no-bar; if that is the convention a classic 6px bar is inconsistent. Needs a prod trace of comparable size.
…xed build
The variant/version dropdown is effectively pixel-identical - same box, same
Search field, same default group with v3/v2/v1, 0.21% over the panel.
D-22 was previously pinned only by a render test. TestcaseDrawer needed a
testset and neither project had one, so I created qa112-parity on both and
opened the drawer: prod's header carries NINE buttons including a bare Close on
top of the drawer's own Close drawer, local's carries eight. Prod renders two
close affordances stacked, which is exactly what closeIcon={null} exists to
prevent and exactly what the deferred (rare / unused) docstring cost. Drawer
width is 736 on both and every other control matches.
Worth the detour: a render test cannot tell you the real drawer stacks two X's.
Re-measured live after a day of churn: avatar slot 32, code lines each block, markdown h1/h2/body 16/14/14 with gaps matching prod's, range picker's active row painted. D-21 and D-22 were both re-verified today with two-build controls and D-23 by unit test. Records one harness trap that cost several turns: an unsaved testcase installs a beforeunload guard and both builds then refuse every goto.sh, because the daemon auto-dismisses the dialog and cancels the navigation while goto.sh just reports MISS. In-app clicks are blocked too while the drawer is open. Escape is browse dialog-accept before setting location.href. The trace-drawer scrollbar question stays inconclusive: it needs a prod trace large enough to overflow and prod's observability lists no rows under any range I could set.
Auth was the last unexamined area and did not need a sign-out - /auth renders while authenticated, so both pages were captured with the sessions intact. The page reads 2.93%, which is one difference plus its knock-on shift: prod shows a DATA RESIDENCY block with an EU/US toggle that local lacks, so every control below sits higher. Host-gated rather than drift, confirmed in source: shouldShowRegionSelector returns true only when classifyHost matches a recognised cloud host, and localhost is not one. Everything else matches. The guard itself had a blind spot and blocked this comparison outright, claiming one tab was shot twice when shot.sh had logged two different hosts. It compares the rail's version stamp and the auth screens have no rail, so both crops were flat background and compared equal. A blank patch is not evidence of sameness: same_environment now returns False on a uniform crop, verified both ways - still catches a genuine duplicate on a stamped page, no longer fires on /auth. Also fixed arr.ptp(), removed in NumPy 2. Second false-PASS-adjacent bug in the harness's own safety net after the tab pin. Guards need their own controls, in both directions.
Both had been left open for the same bad reason - the state did not exist and I treated that as a blocker instead of producing it. Each took one action. Tools/Skills expansion: added a tool to both. The picker is byte-identical and Tool definition needs no external connection, so it works on a self-hosted build. The editor reads 0.10% over its own box and the populated Tools section is identical - icon, count, TOOL DEFINITIONS, and the card with its New and definition chips. Trace drawer scrollbar: generating the same 1200-word-essay trace on both shows the same container at overflowBy 0 and barPx 0 on each, stable across three samples. The earlier 6px bar was local caught mid-load - the async-surface trap again, and the first time it reached the inventory instead of being caught in the moment. Prod's trace list was empty because nothing recent had run, not because traces were unavailable. Recording an item as blocked on missing state, when the state is one action away, is a slower way of not doing the work.
The get_weather draft tool is removed from both agents. It was uncommitted, so no revision was created on either; both are back to Saved with Tools: None. The qa112-parity testsets never existed. I had reported leaving one on each project; both report Testsets 0, because the create flow lands in an unsaved editor and navigating away discards it. The D-22 verification worked anyway - the testcase drawer opens from the unsaved editor's first row and needs no persisted data, which is worth knowing for anyone re-running it. Both projects are otherwise back to their prior state, apart from the sessions and traces the live runs produced.
…its behind Two premises this inventory rested on were wrong, both now checked rather than assumed. Prod is not v0.112.1. The rail stamps v0.112.2 and has done for every capture from 14:02 onward, compared pixel-wise across six prod shots. That does not invalidate the comparisons - it makes most of them stronger, since comparing against prod was comparing against 112.2 - and it resolves the places I wrote '112.2 has X, prod lacks it, so prod is older': prod runs a 112.2 build, not necessarily the tip. The same-environment guard's tell died with that premise. With both builds on 112.2 the version stamp no longer discriminates; on three real pairs it was only working on incidental sidebar pixels. shot.sh now writes the source URL beside each capture and vrt.py compares hosts, verified both ways. A host does not quietly converge; a version string does. The functional gap: this branch is 113 commits behind 112.2 and the lane is 445. None of the seven files I fixed were touched by those commits, so nothing conflicts. But among them is a tool-activity naming and summary workstream, and 112.2's toolDisplay.ts is 665 lines where this branch's is 26. That answers 5e's open lead. Prod showed 'Reading a file failed' plus the filename where local showed a bare Read, and I could not find the phrase in any source because I was grepping this branch and the 112.2 ref at my merge base. It was never drift - the branch is missing feature work. Visual parity is closed; functional parity cannot be judged from this branch until 112.2 is merged into the lane.
Visual parity is closed; the functional half is not. This branch is 113 commits behind release/v0.112.2 and the lane is 445, and one thing filed as a visual difference turned out to be missing feature work - 112.2's toolDisplay.ts is 665 lines against this branch's 26, because a tool-activity naming and summary workstream landed after our merge base. The plan records the real conflict set from a dry-run merge-tree - 18 conflicts, grouped into the chat slice (the actual work), three relocations where 112.2 edited files the lane moved, and five small self-contained ones. It also lists the seven fixes that must survive with their files, the gates including the 83-tests-across-11-files check that catches a vitest config regression, and exactly which comparisons to re-run versus leave alone. The prompt tells the next session to merge into this branch rather than the lane, to take 112.2's tool-summary system whole rather than hand-merging it, to work continuously instead of checkpointing after each unit, and to ask what the right behaviour is rather than which build to match.
The branch was 113 commits behind 112.2, and one thing filed in the diff inventory as a visual difference turned out to be missing feature work: the tool-activity naming/summary workstream (~10 commits) plus the observed startup states and the #6042 first-message dispatch fixes. Every one of the 18 conflicts had the same shape — the lane moved code into packages, 112.2 kept editing it at the old path — so the resolution is almost always 112.2's substance re-applied where the lane put the code. - toolDisplay: 112.2 grew the 147-line resolver to 685; the lane had copied it into @agenta/chat/skin. The system moves to skin/registry.ts, with 112.2's BY_TOOL_NAME as built-in defaults a registered entry merges onto. A verbatim port dropped the skin contract's label/source override, which the package's own pins caught; restored. 112.2's toolDisplay.test.ts (74) and toolRow.test.ts (29) pass unmodified against the package resolver. - ToolActivity rebuilt from 112.2's structure with the lane's package imports and de-antd'd markup; its Build-mode source-chip suppression is now a prop on 112.2's ToolSource. - The approvals copy changes land on @agenta/chat's ApprovalCard. - Auth: 112.2's attempt sync READS a host tag that its PasswordlessAuth WROTE. The lane had already extracted that form, so the write now happens in @agenta/auth's requestEmailCode — otherwise the resume path is dead. - AgentOverviewBody grows automationSessionsHref; navigation-ui's EntryList takes 112.2's 7-row cap; the sessions barrel exports the new handoff list atoms and pendingHandoffs.test.ts points at the package. - The relocated organization api test is ported to axios under tests/unit/, where that package's vitest config actually collects. Gates: lint-fix clean, oss and mobile tsc 0 errors, @agenta/ui 85/12 (above the 83/11 floor; the extra file is 112.2's own), all package suites green. Also fixes a pre-existing red unrelated to the merge: sessions.delete's mock omitted sessionRowsFromPages, so that suite never imported. The lane (PR #6065) is still 445 commits behind 112.2. That is real and it is a separate job.
Driving the merged build against prod found the ask reading two different ways: with the chat panel maximized it said "The agent needs your approval before writing a file", and with the config pane open it fell back to the raw wire name plus "The agent wants to run this tool before it can keep going". That split is not 112.2's. The merge-base dock already used one humanized sentence in both modes and branched on whether a registry body is present; the extraction into @agenta/chat's ApprovalCard reintroduced a friendly- gated raw-name row, friendly being chatPanelMaximizedAtom. It stayed invisible while the two sentences happened to match, and 112.2 changing the copy pulled them apart. The card now renders one sentence either way and drops the raw-name row — the wire name stays reachable via the title attribute and the payload expander. So a registered body that brings no headline of its own still gets the generic ask, the dock passes it explicitly rather than relying on undefined, which cannot tell that case from "no body at all". `friendly` now only chooses between "Details" and "Payload". Verified live: local reads the same sentence prod does in both modes. Also records the post-merge comparison in the inventory: the tool rows are at parity (the point of the whole exercise), session open/start, auth and elicitation likewise, and the seven fixes on this branch all still hold.
ardaerzin
merged commit Aug 20, 2026
7d18982
into
lane/mobile-extracted-packages
36 of 42 checks passed
This was referenced Aug 20, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
#6065 moved the desktop UI into the
@agenta/*packages. A number of features did not survive the move, and they were not removed by any commit. They were dropped in merge resolutions, which is whygit log -S <symbol>finds only the commit that ADDED them. Every one leaves the same fingerprint: a symbol that still exists, still type-checks, and has zero consumers.They were found by putting local EE next to deployed v0.112.1, surface by surface, and diffing screenshots per page strip.
What a user had lost:
/in the agent composer did nothing. The whole palette was unwired.This branch also merges
release/v0.112.2, which the lane does not have. Part of the diff is that merge.Changes
Features the merge dropped, restored as 112.2 ships them
/command paletteuseChatSlashCommandshad no consumer.AgentComposerDocklost the hook, both picker panels and theslashCommandsprop. The sharedChatComposeralso did not forward the prop, so it takes one now.»)SessionTagBarlost itsleftExtraslot when the strip moved to@agenta/sessions-ui, leavingShowConfigPanelButtonunrendered.AgentChatPanellostRightPanelSplit+SessionFilesPane, andAgentConversationhad gained aSessionFilesDrawerthat 112.2 does not have.iconPosition="end"EnhancedButtonfacade listed it as "rare / unused" and dropped it. Five call sites pass it, so every trailing icon rendered leading.Learn More →had become→ Learn More.The trace drawer
Clicking a trace did nothing. Three separate read-modify-write races on the URL, each of which had to be fixed before the next became visible:
traceandspanas two navigations.requestNavigationAtomis a single slot, so the second command replaced the first, and eachRouter.replacebuilt its query from one the other had not committed.Router.replacereturnedfalse. Now onepatchQuery({trace, span}).selectedTraceIdeffect fired 50ms later with a staletraceParamand cancelled that navigation. It now skips the selection the click already queued.router.query, a render time snapshot, so they pushed a query that predated the click and wiped it. They read the live URL now, and return early when the URL would not change. That last guard matters: without it a no-op write pushes an identical URL, which re-renders, which calls the seam again.Closing had its own problem. The params were cleared 320ms after the slide started, and
syncTraceStateFromUrlreopens a closed drawer while?trace=is still present, so those 320ms were a window in which the drawer reopened itself. Clearing now happens in the same action that closes, and the sync opens on a param CHANGE rather than re-asserting on every sync.Virtual table
Header cells were painted with
colorFillQuaternary, a 4% white wash. Fine on a static header, wrong on a sticky one: scrolled sideways, the pinnedNameheader showedInputsstraight through it and both were legible at once. Every header cell is opaque now, same tone, painted as a gradient layer overcolorBgContainer.The selection and expand columns were not treated as columns. They never got
border-r, so the row select column ran intoNamewith no rule between them, and the table had no outer border at all. They also stacked in the opposite order in the header and the body.Panel motion
Collapsing the config panel did not animate. The transition latch watched
chatPanelMaximizedAtom, but the«control writesconfigPanelCollapsedAtom, so the only reachable control was the one that did not ease. TheRightPanelSplitnever animated either, because it detected the flip with a render phasesetStatethat React collapses before commit.Animating the width also reflowed the contents: tiles reflowed into fewer columns, labels rewrapped, the column outgrew the pane. During the slide the content now leaves the flow at a fixed pixel width, pinned to the edge the box collapses towards, so the shrink reads as a translation. It goes back to responsive the moment the slide ends, because during a drag the reflow is the feedback.
Two closed right hand panels were also painting 18px of gutter against nothing, because
RightPanelSplitnever passedbarHidden.Earlier in the branch
Settings and sidebar details the extraction dropped, the v0.112 drift remediation, and ports of #5927, #5942, #5943, #5946 and #5974 into the lane's idiom.
Tests / notes
pnpm lint-fix,tscfor oss and mobile, and the@agenta/uiand@agenta/sessions-uisuites are green.transitionstart/transitionendelapsedTimefor the motion. SamplingrequestAnimationFrameis useless on the dev build, which starves it badly enough to make a working transition look like a snap.docs/design/sessions-ux-stack/ee-vs-112.1-diff-inventory.mdcarries the full record, including the findings that turned out to be wrong and why.@agenta/uiButton, the virtual table, the drawer facade), so their blast radius is wider than the surface each was found on.@agenta/entity-ui(86 changed files against 112.2), agent Home and the onboarding canvas, auth, and the shared drawers.What to QA
/. The palette opens with/model,/permissionsand/new. Enter on/modeldocks the model picker to the composer./newadds a session tab.«in the Configuration header. The panel slides shut, and»appears at the left of the session bar. Click it. The panel slides back. The contents must not reflow while it moves.«at the right. Files docks as a resizable pane and pushes the chat aside, rather than opening a drawer over it.?trace=and?span=. Close it. The params go and the drawer stays shut.Learn More →, arrow last. Same for the agent Home composer and the template strip actions.